コードジェネレーター「Plop」を使ってコンポーネント開発を加速させる

面白法人グループアドベントカレンダー2024 8日目の記事です。

こんにちは!技術部 フロントエンドエンジニアの大脇です。

今回は、コードジェネレーターを活用してコンポーネント開発を効率化する方法をご紹介します。
手動でのファイル作成や設定の煩わしさから解放されたい方に読んでいただけると幸いです。

課題

私の担当するReactプロジェクトでは、コンポーネントごとに以下のようなディレクトリ構造を採用しています。

components/
    └── Button/
        ├── Button.tsx
        ├── Button.module.scss
        ├── Button.stories.tsx
        └── index.ts

この構造はコンポーネントに関連する情報を1つのディレクトリにまとめることで、チーム開発や運用の際に非常に分かりやすい利点があります。
しかし、新しいコンポーネントを作成するたびに以下のような手順を踏む必要があります。

  1. ディレクトリを作成する
  2. Button.tsxを作成し、Reactコンポーネントの雛形コードを記述する
  3. エントリーポイント(index.ts)を作成する
  4. Button.module.scssを作成し、Button.tsxにてインポートする
  5. Button.stories.tsxを作成し、Storybook用の雛形コードを記述する

これだけの準備作業に時間がかかるうえ、手間が多く煩わしいと感じていました。

これらの課題を解決し、作業の効率化を図るために試行錯誤をはじめました。

VSCodeのスニペットを試してみた

最初に取り組んだのは、VSCodeのスニペット機能を活用することでした。

code.visualstudio.com

例えば、rcと打てばReactコンポーネントの雛形が、sbと打てばStorybookの雛形が生成されるように設定しました。

この方法により、ある程度作業の効率化は進んだものの、いくつかの問題が残りました。

ファイル作成の手間が残る

スニペットを使用するためには、まず必要なファイルを自分で手動で作成する必要がありました。
これにより、作業の初期段階で手間がかかる点が解消されませんでした。

オンボーディングコスト

ドキュメントにはスニペットの使用方法を記載しているものの、新しいメンバーへの認知コストが高く、説明するためのコストが発生しました。

作業漏れのリスク

スニペットを使っても、必ずしもすべての必要なファイルが自動で作成されるわけではありません。
特に、Storybookのファイルなどは作成されないことが多く、後から追加する手間が発生していました。
そのため、必要なファイルが揃っていない状態が発生するリスクがあり、この点が課題となっていました。

解決策: コードジェネレーターの採用

これらの課題を解決するために、コードジェネレーターの導入を検討しました。

コードジェネレーターとは、指定されたテンプレートや設定に基づいて、繰り返し作成するコードを自動生成するツールのことです。 これにより、手作業でのコーディング時間を大幅に削減できるだけでなく、コードの品質や一貫性を保つことも可能になります。

候補はいくつかありましたが、設定の容易さやプロジェクト規模の観点から「Plop」を採用しました。 plopjs.com

Plopの導入方法と生成例

実際にPlopを導入し、どのようにコードを生成するかについて説明します。   今回の解説は、以下の実行環境で行っています。

実行環境

  • node: "22.12.0"
  • plop: "^4.0.1"

2024年12月時点の最新のLTSバージョンを使用していますが、Plopは広いバージョン範囲で互換性があるため、近いバージョンでも動作する可能性があります。

Plopのインストール

まず、プロジェクトにPlopをインストールします。
以下の例では公式ドキュメントに沿ってnpmを使用していますが、yarnpnpmを使用している場合は、それぞれのコマンドに置き換えてください。

npm install --save-dev plop

Plopの初期設定

プロジェクトのルートにplopfile.mjsを作成します。このファイルがPlopの設定を記述するメインのスクリプトになります。

export default function (plop) {
  plop.setGenerator('component', {
    description: 'Reactコンポーネントを作成します',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'コンポーネント名を入力してください(例 Button)',
      },
    ],
    actions: [
      {
        type: 'add',
        path: 'components/{{name}}/{{name}}.tsx',
        templateFile: 'plop-templates/components.hbs',
      },
      {
        type: 'add',
        path: 'components/{{name}}/index.ts',
        templateFile: 'plop-templates/index.hbs',
      },
      {
        type: 'add',
        path: 'components/{{name}}/.stories.tsx',
        templateFile: 'plop-templates/stories.hbs',
      },
      {
        type: 'add',
        path: 'components/{{name}}/.module.scss',
        templateFile: 'plop-templates/styles.hbs',
      },
    ],
  });
}

テンプレートファイルの作成

Plopはテンプレートファイルを使ってコードを生成します。
以下の例では、plop-templatesディレクトリにテンプレートファイルを用意します。

Reactコンポーネントの雛形 (plop-templates/component.hbs)

まず、Reactコンポーネントの雛形を作成します。
このテンプレートでは、スタイルのインポートを簡略化するために、仮のスタイルもあらかじめ設定しています。

import styles from './{{pascalCase name}}.module.scss';

export const {{pascalCase name}} = () => {
  return (
    <div className={styles.wrapper}>
      {{pascalCase name}}
    </div>
  );
};

エントリーポイントの雛形 (plop-templates/index.hbs)

次に、コンポーネントのエントリーポイントを作成します。
このテンプレートは、Reactコンポーネントをインデックスファイル経由でエクスポートするためのものです。

export * from './{{pascalCase name}}';

Storybookの雛形 (plop-templates/stories.hbs)

Storybook用のテンプレートも作成します。
この雛形では、コンポーネントに対する基本的なストーリーを定義し、Metaオブジェクトを使用してコンポーネントの情報を設定します。

import type { Meta, StoryObj } from '@storybook/react';
import { {{pascalCase name}} } from './{{pascalCase name}}';

type Component = typeof {{pascalCase name}};
type Story = StoryObj<Component>;

const meta = {
  component: {{pascalCase name}},
  tags: ['autodocs'],
} satisfies Meta<Component>;

export default meta;

export const Primary: Story = {}

スタイルの雛形 (plop-templates/styles.hbs)

最後にSCSS Moduleの基本的なスタイルを作成します。
このテンプレートでは、簡単なスタイルを示すために仮のスタイルを記述しています。

.wrapper {
  width: 100%;
}

Plopの動作例

次のコマンドを実行してみましょう。

npx plop component

すると、プロンプトが表示されます。

コンポーネント名を入力してください(例 Button):  Button

Buttonと入力すると、自動的に次のディレクトリとファイルが生成されます。

components/
    └── Button/
        ├── Button.tsx
        ├── Button.module.scss
        ├── Button.stories.tsx
        └── index.ts

各ファイルにはテンプレートに基づいた初期コードが記載されています。

Plop導入で得られた効果

生産性が向上した

コンポーネント作成に必要なディレクトリやファイルが瞬時に生成されるようになり、準備作業にかかっていた時間を大幅に削減できました。煩雑だった手動操作が不要になり、開発の集中力を本来のロジック実装に向けられるようになりました。

作業漏れがなくなった

テンプレートに必要なファイルやコードがあらかじめ定義されているため、手動操作による作業漏れやミスが解消されました。
これまで忘れがちだったStorybook用ファイルの生成を確実に行える点は大きな利点です。
また、現時点ではテストコードの雛形は含めていませんが、将来的に導入する場合、同じ仕組みで効率的に生成できることが期待できます。

プロジェクト全体での認知負荷が下がった

テンプレート化によって、コンポーネント構造が一貫性を持つようになりました。
これにより、コードレビューの効率が向上し、チーム全体で同じ基準の開発が可能になりました。
また、新しいメンバーが参加した場合でも、規約や構造を簡単に理解できる環境が整いました。

最後に

今回ご紹介したPlopをはじめ、コードジェネレーターを活用することで、Reactコンポーネント開発の効率を大幅に向上させることができます。
特に、開発における「煩わしいけど必要な作業」を自動化することで、より本質的な開発に集中できるようになるはずです。

ぜひ一度、皆さんのプロジェクトでも導入を検討してみてください!

カヤックではプロダクト改善に興味のあるエンジニアを募集しています。