【開発を効率化しよう】Reactで実践!Schematics


schematicsを使っていつもの作業を効率化しよう!
Reactのコンポーネント、テスト、ストーリーブックの作成をスキャッフォルドしてみた!

自己紹介

よろしくお願いします!


はじめに

Angular CLIで利用されているSchematics を使って、いつもの作業をいい感じに効率化できたので紹介させていただきます。

Angular CLIはこんな感じのやつです!使ったことある人は多いのではないでしょうか?

$ ng g component my-hero
installing component
  create src/app/my-hero/my-hero.component.css
  create src/app/my-hero/my-hero.component.html
  create src/app/my-hero/my-hero.component.spec.ts
  create src/app/my-hero/my-hero.component.ts
  update src/app/app.module.ts

AngularユーザーでなくてもSchematicsは使えるのでいつもの作業をスキャッフォルディグっちゃいましょう!


Schematicsって?

README.mdには下記のように書いてあります。

モダンウェブのスキャッフォルドライブラリです。

ファイルの作成、既存のファイルのリファクタリングや移動などができます。
とのこと。

そして説明には同様のライブラリとの違いとして下記を挙げています。

  • まさしく記述的
  • すべてがコミットされる準備ができるまで、実際のファイルシステムを変更しない
  • Schematicsでは副作用はありません

ところであのおじさん元気かなー

できることはyeomanおじさんと似てますが、Schematicsには「お、いけてる」と思う点がたくさんありました。
実際に作ったものを紹介しながら良さも伝えていきたいと思います。


いつもの作業...

私が欲しかったものは

src/components/Header/
├── Header.jsx
├── Header.stories.js
├── Header.test.js
└── index.js

こんな感じのコンポーネントのワンセットを


コマンドでサクッと

  • 作りたいコンポーネント名
  • パス

を下記のように指定したら生成してくれて、

$ npm run gen -- --name=Header --path=src/components

あわよくば実行可能なストーリーブックとスナップショットテストが欲しい。


できる!

https://github.com/hand-dot/component-gen

npmで公開していますが、現状ほぼ自分のために作ったので悪しからず...


思った以上に簡単だった...!

初めて触ったんですが、自分のやりたいことのレベルならnpmに公開して別プロジェクトで使うまで2,3時間くらいで作れたので良かった。

もし同じようなことがやりたい人がいたら...と思い、やったことを下記で簡単に説明していきます。


セットアップ

  • schematicsをインストールして
  • 空のschematics cli「my-schematics」を作って
  • 移動します
$ npm install -g @angular-devkit/schematics-cli
$ schematics blank my-schematics
$ cd my-schematics

複数のSchematicを使いたい場合や、実装をみてみたい場合は

$ schematics schematic --name=my-schematics

でセットアップするといいかも。丁寧にコメントしてあってわかりやすいです。


srcの中のファイルはこんな感じ

src/
├── collection.json <-Schematicsの定義ファイル
└── my-schematics
    ├── index.ts
    └── index_spec.ts

ビルドして実行

$ npm run build
$ schematics .:my-schematics

テストもついてる

$ npm run test

一番簡単なファイルの作成

Hello world!という内容のhiというファイルを作ってみましょう。

import { Rule, SchematicContext, Tree } from "@angular-devkit/schematics";

export function mySchematics(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    tree.create("/hi", "Hello world!");
    return tree;
  };
}

Treeは変更のためのステージング領域。Treeにあんなことやこんなことをして返却して変更をリクエストする。

時間の関係上、機能は紹介しませんが ←2019/05/25追記: 次のセクションにて解説します。

などを参考にしていただければと思います。(APIもシンプルなので良きです。)


今回作成したSchematicsの簡単な解説

やりたいことはReactのテンプレートを用意し、ファイル名や一部の文字を置き換え、任意の場所にコピーするだけなので単純です。

公式のREADME.mdのTemplatingを参考にテンプレートを呼び出す例として下記の処理を見てみましょう。

files/__name@dasherize__.ts

export class <%= classify(name) %> {
}
index.ts

import { strings } from '@angular-devkit/core';
import {
  Rule, SchematicContext, SchematicsException, Tree,
  apply, mergeWith, template, url,
} from '@angular-devkit/schematics';
import { Schema as ClassOptions } from './schema';

export default function (options: ClassOptions): Rule {
  return (tree: Tree, context: SchematicContext) => {
    if (!options.name) {
      throw new SchematicsException('Option (name) is required.');
    }

    const templateSource = apply(
      url('./files'),
      [
        template({
          ...strings,
          ...options,
        }),
      ]
    );

    return mergeWith(templateSource);
  };
}

なんとなく予想つきますが、テンプレートのプレースホルダを埋めます。

template関数を見てみると渡しているのは...options...stringsです。

optionsに関してはテンプレート内<%= classify(name) %>とファイル名の__name@dasherize__nameで渡した何かしらの文字列に置き換えます。

stringsは使用されているdasherizeclassify関数をテンプレート内とファイル名に提供しています。(ファイル名に関数を適応する場合は@をつける)

なのでoptions.namefooBarを渡して実行した場合、実行したディレクトリに下記のようなファイルが作成されます。

foo-bar.ts
export class FooBar {
}

これを理解しておくと私がやったことはなんとなく想像ができるのではないでしょうか?

src/
├── collection.json
└── component-gen
    ├── files
    │   └── react
    │       ├── __name@classify__.jsx
    │       ├── __name@classify__.stories.js
    │       ├── __name@classify__.test.js
    │       └── index.js
    └── react
        ├── index.d.ts
        ├── index.ts
        ├── index_spec.d.ts
        └── index_spec.ts

ソースコードは(こちら)[https://github.com/hand-dot/component-gen]でご覧いただけます。


Schematicsのここが好き!

Dry Run!
例えばさっきの一番簡単なファイルの作成を実行すると実際にファイルは作成されないが、下記のようなフィードバックがある。

$ schematics .:my-schematics
CREATE /hi (12 bytes)

--dry-run=false のオプションを渡すと本当にファイル作ります。テストも同様でsetUpやtearDownを書かなくていいため、ファイルを消して再実行!みたいな手間がない。

そして、下記のように

npm run build -- -w

でwatchしながら実装すればサクサク試せるので楽しかった。


Schematicsのここで困った...

$ schematics .:my-schematics
実行時の.:
これなに?ってなってシカトしてたらnpmパッケージにした後に他プロジェクトで実行できなくて少し迷った。

Helpにはサフィックスでセミコロンをつけろとのこと。

schematics [CollectionName:]SchematicName [options, ...]

.:の場合は同階層のpackage.jsonのschematicsプロパティからcollection.jsonを見つけて指定されたSchematicNameを実行する。
そして例としてあげた「my-schematics」をnpmで公開して別プロジェクトでnpm installした場合の実行は

$ schematics my-schematics:my-schematics

となる。

READMEにも CollectionにNPMパッケージを使用します。との記載があるが、
最初はCollectionName? SchematicName? え? となるので困った。

ちなみに実行時にCollectionNameに.: を使うとデフォルトがDry Runになる。


まとめ

  • 定型作業をサクッと効率化できる。
  • え?yeoman!?(プークスクス)ってならない。
  • え? Don't repeat yourself ですよ?(キャーカクイイ)ってなるかもしれない。
  • AngularのいいところがAngular以外で使えて嬉しい。
  • Schematicsの開発自体が結構楽しい。(ここ重要)
  • SchematicNameは複数定義できるので自分のネームスペースでnpmに公開おいて、プロジェクトに合わせて使ったり、今回はコンポーネント版でしたがRedux版やVue版も作りたいと思った。
  • チーム開発でもかなり強力に使えると思った。

おわり

ご静聴ありがとうございました!


参考にさせていただきました!