Open9

Nx で generator を作ったときのメモ

tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

目的: テンプレに沿った新しいコンポーネントを scaffold する generator new-componentを作りたい。最終的に実行コマンドは下記のようにしたい
nx workspace-generator new-component ComponentName --prj=project-name --dir=directory-name

tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

generator の生成

nx generate @nrwl/workspace:workspace-generator new-component
tools/generators 配下に new-component というディレクトリと、その直下に下記のファイルが生成される。

  • schema.json: コマンドの引数関連の定義
  • index.ts: generator を生成するロジックを記述するファイル
tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

schema.json の変更

$default というキーは generate 実行時のデフォルトのパラメータを示している。
nx workspace-generator new-component ComponentName --prj=project-name --dir=directory-name
というコマンドの例だと、デフォルトのパラメータは ComponentName が相当。
定義したい引数に合わせて下記のように変更

"componentName": {
  "type": "string",
  "description": "Component Name",
  "$default": {
    "$source": "argv",
  "index": 0
},
"prj": {
  "type": "string",
  "description": "Project Name"
},
"dir": {
  "type": "string",
  "description": "Directory Name"
},
tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

index.ts の schema の型が any なので修正しておく

interface NewComponentSchema {
  componentName: string;
  prj: string;
  dir: string;
}
tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

generateFiles, joinPathFragments, names@nrwl/devkit からインポートする。
libraryGenerator, installPackagesTask は不要。
generateFiles はその名の通りファイル生成をする。

generateFiles(
    tree, // virtual file system。そのまま渡せば良い
    srcFolder, // テンプレートを定義しているファイル(たち)のディレクトリ
    target, // 生成したいディレクトリ
    substitutions, // テンプレートで置換される変数たち
  );

実装例。テンプレートの形式は後述するが、 files/__className__.tsx__tmpl__ というファイルに定義しておくことを念頭におく。

generateFiles(
    tree,
    joinPathFragments(__dirname, './files'),
    joinPathFragments('apps', schema.prj, 'components', schema.dir),
   {
      ...names(schema.componentName),
      tmpl: '',
    }
);

names という関数で、componentName を加工して渡している。例えば "myName" のとき、

names("myName") // {name: 'myName', className: 'MyName', propertyName: 'myName', constantName: 'MY_NAME', fileName: 'my-name'}

と展開される。
これを使うと、生成コマンドで foo-bar と指定された場合でも、FooBar という文字列が使える。

tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

単純な files/__className__.tsx__tmpl__ の例。<%=className%=> とすることで、引数で渡した componentName が例えば "foo-bar" のとき、FooBar が埋め込まれる。

import React from 'react';

type <%=className%>Props = {
  prop1: string;
};

const <%=className%>: React.VFC<<%=className%>Props> = (props) => {
  return <>Code Your Component</>;
};

export default <%=className%>;

このときの生成結果

import React from 'react';

type FooBarProps = {
  prop1: string;
};

const FooBar: React.VFC<FooBarProps> = (props) => {
  return <>Code Your Component</>;
};

export default FooBar;
tonanatz (Ryuichi Natori)tonanatz (Ryuichi Natori)

files 直下のファイルがすべて生成されるので、同様にして test や Storybook も一緒に配置することもできる