はじめに
こんにちは、Gakken LEAPのフロントエンドエンジニアの Okuma です。
今回はフロントエンド開発などでよく使用するコンポーネントファイルの雛形をコマンドで自動生成するhygenというツールについて紹介します。
今回はNext.jsのプロジェクトを想定して実装していますが、package.json
を使うプロジェクトであれば実装可能ですので、ぜひ導入を考えてみてはいかがでしょうか? 今回は対話型のコードジェネレータを使って任意のディレクトリ内にファイルを生成したいと思います!
hygenとは
hygenとは、開発プロセスを加速させるシンプルかつ高速なコードジェネレーター。つまり、プロジェクト内のファイルなどを自動生成するためのツールです。
hygenを導入することで、カスタマイズ可能なテンプレートを使って自動生成タスクを実行することができます。
ReactのコンポーネントファイルやStorybookファイル、コンポーネントのテストファイルなどのある程度ベースとなるコードが決まっているファイルを自動生成するのにとても有効です!
hygenには一般的なコマンドライン引数を用いた方法と対話型の入力方法の2種類の生成方法が存在し、用途によって使い分けることができます。
hygenの導入によるメリット
hygen導入によるメリットには以下のようなものがあると考えられます。
- CLIでファイルを生成できるため、コンポーネントファイル作成時にゼロからコードを記述する必要がない
- ベースのコードが記述された状態のファイルを生成することができる
- チーム開発において、個人によるコンポーネントの記述スタイルの違いを防ぐことができる
- 対話型のファイル生成により、配置するディレクトリを指定できる
- プロジェクトで設定されたディレクトリ構造に基づいてファイルを作成できる
- コンポーネントごとのStorybookファイルやテストファイルも一つのコマンドで一括作成できる
- ディレクトリ構造によってはファイル作成の効率が大幅に向上する
hygenの導入方法
インストール
npm install -D hygen
初期化
npm hygen init self
初期化コマンドを実行するとプロジェクトルートに_templates
ディレクトリが生成されます。_template
ディレクトリ内にはgenerator
とinit
というディレクトリがあり、初期状態ではサンプルコードが入っています。
自動生成するファイルはこの_templates
内に作成していきます。
_template/
|-- generator/
| |-- help/
| | |-- index.ejs.t
| |-- new/
| | |-- hello.ejs.t
| |-- with-prompt/
| | |-- hello.ejs.t
| | |-- prompt.ejs.t
|-- init/
| |-- repo/
| | |-- new-repo.ejs.t
テンプレートファイルの作成
今回は対話型のコードジェネレーターの機能を用いてコンポーネントファイル、Storybookファイル、コンポーネントテストファイルを作成したいため、_template
ディレクトリの中は以下のように簡略化します。(この状態でも対話型のコードジェネレーターは問題なく動きます!)
_template/
|-- component/
| |-- new/
| | |-- component.stories.tsx.ejs.t
| | |-- component.tsx.ejs.t
| | |-- index.js
今回はコンポーネント専用のディレクトリを作成し、その中にそれぞれコンポーネント名と同じStorybookファイルを配置する構造で作成してみたいと思います。
Storybookファイルやテストファイルが同じディレクトリに配置されていることで、コンポーネントを修正した際にすぐにStorybookやテストを修正できる利点があるため、今回はこの構造を採用してみました。
コンポーネントのテンプレートファイル
---
to: <%= path %><%= h.changeCase.pascal(component_name) %>/<%= h.changeCase.pascal(component_name) %>.tsx
---
import type { FC } from 'react';
const <%= h.changeCase.pascal(component_name) %>: FC = () => {
return (
<div>
<h1><%= h.changeCase.pascal(component_name) %></h1>
</div>
);
};
export default <%= h.changeCase.pascal(component_name) %>;
上記のようにコンポーネントのテンプレートファイルを作成しました。
ファイル上部のto
にはファイルの出力先を設定します。
以降の行ではReactコンポーネントのベースの形を記述しています。各component_name
の箇所にはプロンプトで入力したコンポーネント名がパスカルケースに変換されて代入されます。
Storybookのテンプレートファイル
---
to: <%= path %><%= h.changeCase.pascal(component_name) %>/<%= h.changeCase.pascal(component_name) %>.stories.tsx
---
import <%= h.changeCase.pascal(component_name) %> from './<%= h.changeCase.pascal(component_name) %>';
import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta<typeof <%= h.changeCase.pascal(component_name) %>> = {
component: <%= h.changeCase.pascal(component_name) %>,
tags: ['autotags'],
};
export default meta;
type Story = StoryObj<typeof <%= h.changeCase.pascal(component_name) %>>;
export const Default: Story = {
render: () => <<%= h.changeCase.pascal(component_name) %> />,
};
コンポーネントファイルと大きくは変わりませんが、component_name
が入ってくるところ以外で共通する部分をこのテンプレートファイルに記載します。
特にTypeScriptでインポートした型(ここではMeta
やStoryObj
など)を毎回使っている場合などは、ファイル作成のたびに記述したり、別ファイルをコピーしたりするのは手間がかかります。
テンプレートファイルとして雛形を用意し、変更がある部分(ここではPropsなど)だけ修正を加えることでチームとして同じ形でコードを管理することができる点も良いですね。
プロンプトのカスタマイズ
module.exports = {
prompt: ({ inquirer }) => {
const questions = [
{
message: 'What is directory name? (empty is root)',
name: 'directory_name',
type: 'input',
},
{
message: 'What is component name?',
name: 'component_name',
type: 'input',
validate: (answer) => answer !== '',
},
];
return inquirer.prompt(questions).then((answers) => {
const { directory_name } = answers;
const path = `src/components/${directory_name ? `${directory_name}/` : ``}`;
return { ...answers, path };
});
},
};
今回はコンポーネントファイル類を生成する出力先(directory_name
)とコンポーネント名(component_name
)を入力させる簡単な内容にしています。
questions
で定義しているプロパティは以下になります。
-
message
- コマンドライン上で聞かれる質問・メッセージ -
type
- 利用者自身が文字列を入力するinput
や選択式のselect
などがある -
name
- 入力や選択した値が入る変数名。これがテンプレートファイルに渡されて展開されます。
type
でselect
を定義した場合は、choices
という配列を用意してそこに実際の選択肢を定義することができます。コンポーネント設計にアトミックデザインを導入している場合などは以下のようにあらかじめ用意したディレクトリを選択させる方法が有効かもしれないですね。
{
message: 'In which directory do you want to create it?',
name: 'dir',
type: 'select',
choices: ['atoms', 'molecules', 'organisms', 'templates', 'pages'], // アトミックデザインのどのディレクトリに配置するかを選択できる
}
最後にpackge.json
にhygenの実行コマンドを追加します。
"scripts": {
"hygen": "hygen component new"
}
以降は新規にコンポーネントが必要なタイミングになったらnpm run hygen
(npmの場合)を実行することで、対話型のコマンド入力を通じてコンポーネントファイル・Storybookファイルを同時に生成することができます。
おわりに
今回は毎回同じ記述をしているコンポーネントファイルなどを、hygenのコードジェネレーターを使用することによって手間を省くことができました。
また、今回の例ではコンポーネントファイルとそのStorybookファイルを自動生成の対象にしていましたが、テストファイルを追加したり、CSS Modulesを導入している場合はCSSファイルを追加することでさらに開発効率を上げられそうです!!
エンジニア募集中
Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています!!
ぜひお気軽にカジュアル面談へお越しください!!