plopで作るコンポーネント生成ワークフロー
この記事はポート株式会社 サービス開発部 Advent Calendar 2024 の7日目です。
はじめに
ポート株式会社でフロントエンドエンジニアをしているminamiです。
普段はキャリアパークや就活BOXなどのサービス開発を担当しています。
この記事では私が現在開発時に利用しているplopというパッケージを紹介します。
plopとは、一貫性を持ったファイルを簡単に作成できるツールになります。
設定ファイルであるplopfile.jsを調整することで、様々なカスタマイズが可能です。
前回の問題点
実は去年のアドカレでもplopについて書きました。基本的な作成手順はほぼ同じです。
これはこれで便利だったのですが、以下のような問題点がありました。
- 全部自由入力なので手間がかかり面倒😕
- コンポーネントを作りたいディレクトリが、既に存在するのかわからない
- 既に存在するディレクトリに作りたい場合も、手入力する必要がある
- 誤字があっても気づけないので、不要なディレクトリを作ってしまう😣
結論VSCodeのUI上で作った方が一目でわかって楽!となり、直近はほぼ使っていませんでした。
今回は上記の問題点を踏まえて、より使いやすいものを目指しました。
今回つくったもの
今回私が作成したplopの動作は以下のとおりです。
-
npm run generate-component
叩く -
features
かcomponents
どちらに作成するか選択する - featuresを選択した場合、既存ディレクトリまたは新規作成するかを選択する
- コンポーネント名を入力する
- 入力内容に応じたディレクトリ階層に、指定のファイルが作成される
このように質問に答えていくだけで、コンポーネントファイルの雛形を作成することができます🎉
上記画像の動作例では、app/src/features/AdventCalendar/components/Notificationというディレクトリが作成され、その中にindex.tsx、index.stories.tsx、index.test.tsxの雛形ファイルが作成されます。
plopfile.jsの中身
では今回作成したplopfile.jsの中身を解説します。
prompts
promptsではユーザの入力データを収集します。
const fs = require('fs');
const path = require('path');
const featuresPath = path.resolve(__dirname, './app/src/features');
module.exports = function (
/** @type {import('plop').NodePlopAPI} */
plop,
) {
plop.setGenerator('component', {
description: 'Create a new component',
prompts: [
{
type: "list",
name: "kind",
message: "どちらに作成しますか?",
choices: ["features", "components"],
},
{
type: 'list',
name: 'path',
message: '既存機能に追加 または 新規で作成しますか?',
choices: () => {
const directories = fs.readdirSync(featuresPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
directories.push('新規で作成');
return directories.map(dir => ({
name: dir,
value: dir
}));
},
when: ({ kind }) => kind === 'features',
},
{
type: 'input',
name: 'newPath',
message: '機能名は何にしますか? (ex: Signup, Mypage)',
when: ({ path }) => path === '新規で作成',
},
{
type: 'input',
name: 'name',
message: 'コンポーネント名は何にしますか? (ex: Button)',
},
{
type: 'input',
name: 'name',
message: 'コンポーネント名は何にしますか? (ex: Button)',
},
],
typeは形式、nameは変数名、messageは質問文が入ります。type内容に応じてその他のプロパティが指定できます。
1つ目の質問では、type: "list"
とし、choices: ["features", "components"]
を指定することで、ディレクトリを選択できるようにしました。
{
type: "list",
name: "kind",
message: "どちらに作成しますか?",
choices: ["features", "components"],
},
2つ目の質問ではchoices
の配列を現在のディレクトリから取得するようにしています。
fsモジュールのreaddirSync
を用いて、指定ディレクトリの一覧を取得し、
また、when
でこの質問が表示される条件を指定しています。今回の場合は、1つ目の質問でfeatures
と回答した時のみ表示したいので、変数名kind
がfeatures
のときを指定しています。
{
type: 'list',
name: 'path',
message: '既存機能に追加 または 新規で作成しますか?',
choices: () => {
const directories = fs.readdirSync(featuresPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
directories.push('新規で作成');
return directories.map(dir => ({
name: dir,
value: dir
}));
},
when: ({ kind }) => kind === 'features',
},
3つ目以降の質問はtype: 'input'
を指定して、自由入力で値を受け取るようにしています。
{
type: 'input',
name: 'newPath',
message: '機能名は何にしますか? (ex: Signup, Mypage)',
when: ({ path }) => path === '新規で作成',
},
actions
actionsでは、promptsの内容を元にファイルを作成します。
actions: function (data) {
var actions = [];
if (data.kind === 'features') {
if (data.path === '新規で作成') {
actions.push({
type: 'add',
path: 'app/src/features/{{pascalCase newPath}}/components/{{pascalCase name}}/index.tsx',
templateFile: 'plop-templates/features/newFeatures/component.tsx.hbs',
});
} else {
actions.push({
type: 'add',
path: 'app/src/features/{{pascalCase path}}/components/{{pascalCase name}}/index.tsx',
templateFile: 'plop-templates/features/component.tsx.hbs',
});
}
} else {
actions.push({
type: 'add',
path: 'app/src/components/{{pascalCase name}}/index.tsx',
templateFile: 'plop-templates/components/component.tsx.hbs',
});
}
return actions;
}
promptsで指定した変数を条件として利用できるので、1つ目の質問の回答に応じて分岐させています。
type
には形式、path
にはファイルを作成する場所、templateFile
にはファイルを作成する際のテンプレートファイルを指定します。
※ここでは省略していますが、テンプレートファイルの書き方や、index.tsx以外のpath指定は前回の記事に記載してあります🙏
さいごに
いかがでしたでしょうか?
私は最近、新規機能の開発を行うことが多いため、plopを使うことでスムーズにコンポーネント生成ができ満足しています。
メンバーが多いプロジェクトでも、plopにコンポーネント作成時のルールを組み込むことで、一貫性のあるコンポーネントディレクトリを構築することができると考えています。
今回紹介した以外にもカスタマイズは色々できそうなので、改善を重ねてより便利にしていきたいと思います🥳
Discussion