Nx で generator を作ったときのメモ
目的: テンプレに沿った新しいコンポーネントを scaffold する generator new-component
を作りたい。最終的に実行コマンドは下記のようにしたい
nx workspace-generator new-component ComponentName --prj=project-name --dir=directory-name
generator の生成
nx generate @nrwl/workspace:workspace-generator new-component
tools/generators
配下に new-component
というディレクトリと、その直下に下記のファイルが生成される。
-
schema.json
: コマンドの引数関連の定義 -
index.ts
: generator を生成するロジックを記述するファイル
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"
},
index.ts
の schema の型が any なので修正しておく
interface NewComponentSchema {
componentName: string;
prj: string;
dir: string;
}
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
という文字列が使える。
単純な 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;
files
直下のファイルがすべて生成されるので、同様にして test や Storybook も一緒に配置することもできる