🏭

PLOPでComponentファイルを雛形から生成しよう

2022/06/30に公開約4,100字

こんにちはmofmofでエンジニアをしているshwldです。

Componentを作るときに、storybook, jest, graphqlなどのファイルをひとまとめにして作ることが増えてきました。

└ components
  └ todo
    └ TodoCreateForm
       ├ index.tsx
       ├ TodoCreateForm.graphql
       ├ TodoCreateForm.stories.tsx
       ├ TodoCreateForm.test.tsx
       ├ TodoCreateForm.tsx

これを解消するためにHygenを使っていたのですが、PLOPのほうが好きだなと感じたので共有します。

PLOPでコンポーネントを作る体験

> yarn plop component
...
? src/components/{path please}
? component name please

? の質問に回答していくとコンポーネントが作成されます。

todo
TodoDeleteButton

と回答すると、

✔  ++ /src/components/todo/TodoDeleteButton/index.tsx
✔  ++ /src/components/todo/TodoDeleteButton/TodoDeleteButton.tsx
✔  ++ /src/components/todo/TodoDeleteButton/TodoDeleteButton.stories.tsx
✔  ++ /src/components/todo/TodoDeleteButton/TodoDeleteButton.test.tsx
✔  ++ /src/components/todo/TodoDeleteButton/TodoDeleteButton.graphql

このように生成されます。

この雛形を作るための設定

/plopfile.js
module.exports = function (
  /** @type {import('plop').NodePlopAPI} */
  plop
) {
  plop.setGenerator('component', {
    description: 'react component',
    prompts: [
      // 入力させたい値につけたnameをactionsやtemplate内で参照できます
      {
        type: 'input',
        name: 'parentPath',
        message: 'src/components/{path please}',
      },
      {
        type: 'input',
        name: 'name',
        message: 'component name please',
      },
    ],
    actions: [
      {
        type: 'add',
        path: 'src/components/{{parentPath}}/{{name}}/index.tsx',
        templateFile: 'plop-templates/component/index.tsx.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{parentPath}}/{{name}}/{{name}}.tsx',
        templateFile: 'plop-templates/component/Component.tsx.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{parentPath}}/{{name}}/{{name}}.stories.tsx',
        templateFile: 'plop-templates/component/Component.stories.tsx.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{parentPath}}/{{name}}/{{name}}.test.tsx',
        templateFile: 'plop-templates/component/Component.test.tsx.hbs',
      },
      {
        type: 'add',
        path: 'src/components/{{parentPath}}/{{name}}/{{name}}.graphql',
        templateFile: 'plop-templates/component/Component.graphql.hbs',
      },
    ],
  });
};

テンプレート内で{{pascalCase name}}, {{name}}のようにして参照できます。またpascalCaseなどがデフォルトで利用できるようになっているのでよく使います。

/plop-templates/component/Component.tsx.hbs
import { ReactNode, VFC } from 'react';

export const {{pascalCase name}}: VFC<{ children?: ReactNode }> = () => {
  return (
    <div></div>
  );
};

好きなところ

  • テンプレートと生成の設定が完全に分かれている
  • promptで定義したnameをテンプレートと合わせるだけなので直感的に使える。生成のために書くコードが最小

TIPS

フォルダを選択させる

/propfile.js
const fs = require('fs');

const features = fs
  .readdirSync('src/features')
  .map((it) => ({ name: it, value: it }));

module.exports = function (
  /** @type {import('plop').NodePlopAPI} */
  plop
) {
  plop.setGenerator('query', {
    description: 'graphql query',
    prompts: [
      {
        type: 'list',
        name: 'name',
        message: 'feature name please',
        choices: features,
      },
    ],
    ...

このようにpromptで選択できる

? feature name please (Use arrow keys)
❯ todo
  account

index.ts等のファイルにexportだけ追記したい

└ components
  └ Hoge
     ├ index.tsx
     ├ Button.tsx
     ├ Card.tsx
     ├ ListItem.tsx

上記のようなフォルダで

/components/Hoge/index.tsx
export * from 'Button.tsx'
export * from 'Card.tsx'
export * from 'ListItem.tsx'

のようなindex.tsxが存在しているとき。

Buttonを生成したら、index.tsxに1行書き加えてもらいたいときありますよね。
そのようなときは、actionsにappendを書きます

propfile.js
    ...
    actions: [
      {
        type: 'add',
        path: 'src/components/{{module}}/{{name}}.tsx',
        templateFile: 'plop-templates/component.tsx.hbs',
      },
      {
        type: 'append',
        path: 'src/components/{{module}}/index.tsx',
        template: "export * from './{{name}}';",
      },
    ]
    ...

まとめ

使いやすい!おすすめです

Discussion

ログインするとコメントできます