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

schematicsを使っていつもの作業を効率化しよう!
Reactのコンポーネント、テスト、ストーリーブックの作成をスキャッフォルドしてみた!
![component-gen.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204698/26779149-f5d1-720f-26fb-bf9015136c65.gif)


## 自己紹介
![icon](https://avatars0.githubusercontent.com/u/24843808?s=460&v=4)

- 名前: 福田京平
- github: [hand-dot](https://github.com/hand-dot) 
- twitter: [hand_dot](https://twitter.com/hand_dot)

よろしくお願いします！


---

## はじめに

Angular CLIで利用されている[Schematics](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/README.md) を使って、いつもの作業をいい感じに効率化できたので紹介させていただきます。

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では副作用はありません

---

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

![68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f3137303237302f3739343530302f34316532643439382d656361322d313165322d386466352d3936326639666139346336332e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204698/2a9e8e37-0c56-7e5f-f312-bcef58814122.png)


できることは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](https://github.com/hand-dot/component-gen)
![component-gen.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204698/26779149-f5d1-720f-26fb-bf9015136c65.gif)

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というファイルを作ってみましょう。

```typescript
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追記: 次のセクションにて解説します。

- [SchematicsのREADME.md](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/README.md)
- @puku0x さんの[Schematicsを作ってみよう](https://qiita.com/puku0x/items/462a038133e7233dfaed)

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

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

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

公式のREADME.mdの[Templating](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/README.md#templating-1)を参考にテンプレートを呼び出す例として下記の処理を見てみましょう。

```typescript:files/__name@dasherize__.ts

export class <%= classify(name) %> {
}
```

```typescript: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`は使用されている`dasherize`と`classify`関数をテンプレート内とファイル名に提供しています。(ファイル名に関数を適応する場合は`@`をつける)

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

```typescript: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版も作りたいと思った。
- チーム開発でもかなり強力に使えると思った。

 
---

## おわり
ご静聴ありがとうございました!
![icon](https://avatars0.githubusercontent.com/u/24843808?s=460&v=4)

- github: [hand-dot](https://github.com/hand-dot) 
- twitter: [hand_dot](https://twitter.com/hand_dot)

---

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

- [SchematicsのREADME.md](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/README.md)
- @puku0x さんの[Schematicsを作ってみよう](https://qiita.com/puku0x/items/462a038133e7233dfaed)
- @Quramy さんの[実践！Schematics](https://qiita.com/Quramy/items/a06d62132007807062df)
- [Your first @angular/schematics
](https://medium.com/@jorgeucano/your-fist-angular-schematics-f711d70cb37c)
- [Angular Schematics: Simple Schematic
](https://medium.com/rocket-fuel/angular-schematics-simple-schematic-76be2aa72850)