📑

定型作業を自動化!Hygenで効率的なReactコンポーネント作成

に公開

はじめに

React 開発において、新規コンポーネントの作成は繰り返し発生する定型的な作業です。しかし、この作業は単純でありながら手間がかかり、効率化したいポイントでもあります。

本記事では、コード生成ツールである Hygen を活用して、コンポーネント作成の手間を減らし効率化する方法をご紹介します。

また、プロジェクトの初期設定だけを行いがちで、設定方法を忘れてしまうことも多いため、備忘録としても活用できるようまとめました。


Hygen とは

Hygen を使用すると、手作業を回避し、コードベースでカスタム操作を生成、追加、挿入、実行できるため、Redux、React Native、Express などを使用したワークフローを強化できます。

Hygen はコード生成ツールで、あらかじめ用意したテンプレートからファイルやコードを自動生成し、繰り返し行う定型的な作業を大幅に効率化できます。

公式サイト・GitHub:


導入手順

🚀 Hygen のインストール

まずはプロジェクトに Hygen を 開発依存として インストールします。

# npmを使う場合
npm install --save-dev hygen

# yarnを使う場合
yarn add --dev hygen

✅ 設定とテンプレートの作成

Hygen を最大限に活用するには、まず _templates ディレクトリを適切に設定し、コンポーネントの雛形となるテンプレートを作成します。

🔹 1. Hygen の初期設定とテンプレートディレクトリの準備

Hygen を初めて使う場合、以下のコマンドを実行して基本的なテンプレートの雛形ディレクトリを自動生成します。

# npmを使う場合
npx hygen init self

# yarnを使う場合
yarn dlx hygen init self

これにより、プロジェクト直下に _templates ディレクトリが作成され、サンプルテンプレートが含まれます。


🔹 2. プロジェクトのフォルダ構成を仮定する

今回の例では、以下のプロジェクトフォルダ構成を仮定して進めます。特に、コンポーネントは Atomic Design の概念を取り入れ、src/components 配下に atoms, molecules, organisms, templates, pages の各ディレクトリを設けるとします。

.
├── public/
├── src/
│   ├── assets/
│   ├── components/
│   │   ├── atoms/     // 最小単位のUI部品 (例: Button, Input)
│   │   ├── molecules/ // atomsを組み合わせてできる部品 (例: SearchBar, Card)
│   │   ├── organisms/ // moleculesを組み合わせてできる部品 (例: Header, ProductList)
│   │   ├── templates/ // ページのレイアウトを定義 (例: FullWidthTemplate)
│   │   └── pages/     // ページ全体 (例: HomePage)
│   ├── hooks/
│   ├── lib/
│   ├── styles/
│   └── App.tsx
├── _templates/    // Hygenのテンプレートディレクトリ (自動生成される)
├── package.json
└── tsconfig.json

🔹 3. コンポーネントジェネレーターの定義とテンプレートファイルの配置

Hygen は hygen [generator] [action] --[オプション] の構文でテンプレートを使います。
今回は、component というジェネレーターで新しいコンポーネントを作成したいので、_templates/component/new/ のようなディレクトリ構造にテンプレートファイルを配置します。

新しいコンポーネントジェネレーターの雛形を作成するには、以下のコマンドを使用できます。

# npmを使う場合
npx hygen generator new component

# yarnを使う場合
yarn dlx hygen generator new component

このコマンドは、_templates/component/new にジェネレーターの最小構成を作成します。

アトミックデザインを考慮した実用的なコンポーネントテンプレートのディレクトリ構成例:

作成されたディレクトリに、以下のような実用的なテンプレートファイルを追加します。これにより、Hygen で対話的に名前などを入力し、Atomic Design のカテゴリに応じた複数のファイルを一括生成できるようになります。

_templates/
└── component/
    └── new/
        ├── index.ejs.t                    // コンポーネント本体 (tsx/ts/jsx/jsはpromptで選択)
        ├── <%= name %>.module.css.ejs.t   // スタイル用CSSモジュール (ファイル名も動的に)
        ├── types.ts.ejs.t                 // 型定義
        ├── stories/
        │   ├── <%= name %>.docs.mdx.ejs.t // ドキュメント
        │   └── <%= name %>.stories.tsx.ejs.t // Storybook用ストーリー
        └── prompt.cjs                    // 入力プロンプト定義

🔹 4. テンプレートの書き方とファイル生成の仕組み

Hygen のテンプレートは EJS で書かれており、<%= name %> のように入力値を埋め込むことができます。

また、Hygen のテンプレートファイルの先頭に YAML Front Matter (--- で囲まれた部分) を記述することで、そのテンプレートから生成されるファイルの出力パスや、既存ファイルの上書きルールなどを細かく制御できます。

今回はアトミックデザインのカテゴリに応じて、生成されるファイルのパスを動的に変更できるようにします。

以下に、各テンプレートファイルの内容を記述します。

_templates/component/new/index.ejs.t (コンポーネント本体)

このファイルは、選択されたファイルタイプ (tsx, ts, jsx, js) に応じて内容が変わります。

_templates/component/new/index.ejs.t
---
to: "src/components/<%= category %>/<%= subDirectory ? subDirectory + '/' : '' %><%= name %>/index.<%= type %>"
---

import clsx from "clsx";
import { CoreComponent } from "@/components/ui_elements/CoreComponent";
<% if (withStyles) { %>import styles from "./styles.module.css";
<% } %>
<% if (withTypes) { %>import { <%= name %>Props } from './types';
<% } %>

<% if (type === 'tsx' || type === 'jsx') { %>
export const <%= name %> = (<%= withTypes ? '{ className } : ' + name + 'Props' : '' %>) => {
  return (
    <CoreComponent<% if (withStyles) { %> className={clsx(styles.root, className)}<% } %>>
      <%= name %>コンポーネント
    </CoreComponent>
  );
};
<% } else { %>
export const <%= name %> = (<%= withTypes ? 'props' : '' %>) => {
  return null; // または他のロジック
};
<% } %>

export default <%= name %>;
  • to:: 出力パスを指定します。<%= category %><%= subDirectory ? subDirectory + '/' : '' %><%= name %><%= type %> を利用して、Atomic Design のカテゴリ、サブディレクトリ、コンポーネント名、ファイルタイプに応じた柔軟なパスを生成します。
  • <%% if (...) { %%>...<%% } %%> は EJS の条件分岐で、withStyleswithTypes などのプロンプトの選択に応じてコードを生成します。
_templates/component/new/<%= name %>.module.css.ejs.t (スタイルファイル)

withStylestrue の場合のみ生成されます。

<%= name %>.module.css.ejs.t
---
to: "src/components/<%= category %>/<%= subDirectory ? subDirectory + '/' : '' %><%= name %>/styles.module.css"
unless_exists: true
---
.root {
  /* <%= name %> コンポーネントのスタイル */
}
  • unless_exists: true: 既に同じ名前のファイルが存在する場合、上書きせずにスキップします。
_templates/component/new/types.ts.ejs.t (型定義ファイル)

withTypestrue の場合のみ生成されます。

_templates/component/new/types.ts.ejs.t
---
to: "src/components/<%= category %>/<%= subDirectory ? subDirectory + '/' : '' %><%= name %>/types.ts"
unless_exists: true
---
export type <%= name %>Props = {
  className?: string; // 追加のCSSクラス
  // 他のPropsをここに追加
};
_templates/component/new/stories/<%= name %>.docs.mdx.ejs.t (Storybook ドキュメント)

withStorybooktrue の場合のみ生成されます。

_templates/component/new/stories/<%= name %>.docs.mdx.ejs.t
---
to: "src/components/<%= category %>/<%= subDirectory ? subDirectory + '/' : '' %><%= name %>/stories/<%= name %>.docs.mdx"
unless_exists: true
---
import { Canvas, Meta, Source, Markdown } from "@storybook/addon-docs/blocks";
import * as <%= name %>Stories from './<%= name %>.stories';

<Meta of={<%= name %>Stories} />

# <%= name %>

これは **<%= name %>** コンポーネントのドキュメントです。


## Index

- [概要](#概要)
- [基本的な使用方法](#基本的な使用方法)
- [Props 一覧](#props一覧)
- [実用例](#実用例)
- [ベストプラクティス](#ベストプラクティス)


## 概要

このコンポーネントは、[ここにコンポーネントの簡単な説明を書きます]。


## 基本的な使用方法

<Source of={<%= name %>Stories.Default} />
<Canvas of={<%= name %>Stories.Default} />

## Props一覧
<Markdown>{`
| Prop名 | 型 | デフォルト値 | 説明 |
| :----- | :----- | :----------- | :--- |
| className | string | undefined | コンポーネントに適用する追加のCSSクラス。 |
`}</Markdown>


## 実用例


## ベストプラクティス
```

## Props

| Prop名 | 型 | デフォルト値 | 説明 |
| :----- | :----- | :----------- | :--- |
| `className` | `string` | `undefined` | コンポーネントに適用する追加のCSSクラス。 |
_templates/component/new/stories/<%= name %>.stories.tsx.ejs.t (Storybook ストーリー)

withStorybooktrue の場合のみ生成されます。

_templates/component/new/stories/<%= name %>.stories.tsx.ejs.t
---
to: "src/components/<%= category %>/<%= subDirectory ? subDirectory + '/' : '' %><%= name %>/stories/<%= name %>.stories.tsx"
unless_exists: true
---
import type { Meta, StoryObj } from "@storybook/react-vite";
import { <%= name %> } from '../index';

// Storybookのメタ情報
const meta: Meta<typeof <%= name %>> = {
  title: '<%= category %>/<%= name %>', // Storybookでの表示パス
  component: <%= name %>,
  parameters: {
    layout: 'centered',
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

// デフォルトのストーリー
export const Default: Story = {
  args: {
    // ここにデフォルトのpropsを設定
  },
};


🔹 5. スクリプトに追加

毎回 hygen を打たなくても良いように、package.json にスクリプトを登録しておくと便利です。

// package.json
"scripts": {
  "gen:component": "hygen component new"
}

🔹 6. 対話型で値を入力できるようにする(prompt.cjs

✅ 対話形式の利点

  • 毎回コマンドライン引数(--nameなど)を指定しなくてもよくなります。
  • 複数の項目を聞くこともできます(例:ディレクトリ名、ファイル分割有無、Atomic Design カテゴリなど)。
  • チームでの導入時に使いやすく、ミスも防げます。

Hygen では、テンプレート実行時に対話形式で値を入力できるようにするために、prompt.cjs をテンプレートフォルダに配置します。

今回は、コンポーネント名に加えてAtomic Design のカテゴリ(atoms, molecules など)やファイルの種類(tsx, ts など)、その他のオプションも選択できるようにします。

以下のような prompt.cjs_templates/component/new/ に追加します:

// _templates/component/new/prompt.cjs
module.exports = [
  {
    type: "input",
    name: "name",
    message: "コンポーネント名を入力してください(例: Button)",
  },
  {
    type: "select",
    name: "category",
    message: "Atomic Design のカテゴリを選択してください:",
    choices: ["atoms", "molecules", "organisms", "templates", "pages"],
  },
  {
    type: "input",
    name: "subDirectory",
    message:
      "カテゴリ内に追加のディレクトリが必要な場合、名前を入力してください(例: Common / 空欄可)",
    default: "",
  },
  {
    type: "select",
    name: "type",
    message: "コンポーネントのファイルタイプを選択してください:",
    choices: ["tsx", "jsx"],
    default: "tsx",
  },
  {
    type: "confirm",
    name: "withStyles",
    message: "スタイルファイル(.module.css)も生成しますか?",
    default: true,
  },
  {
    type: "confirm",
    name: "withTypes",
    message: "型定義ファイル(types.ts)も生成しますか?",
    default: true,
  },
  {
    type: "confirm",
    name: "withStorybook",
    message: "Storybookファイル(.stories.tsx / .docs.mdx)も生成しますか?",
    default: true,
  },
];

📌 ポイント

  • 初期セットアップnpx hygen init self または yarn dlx hygen init self を使えば簡単に雛形が作れます。
  • _templates 以下に**「ジェネレーター名/アクション名」の階層でテンプレートを置きます**。
  • .ejs.t テンプレート内で <%= variableName %> を使うと、引数やプロンプトで指定した値を埋め込めます
  • prompt.cjs を活用することで、アトミックデザインのカテゴリ選択など、対話形式で詳細なコンポーネント生成が可能になります
  • スクリプト化でチームメンバーも迷わず使えます。

この形にしておくと、「誰でも同じ構造でコンポーネントを追加できる」ので、開発効率とプロジェクトの統一感がぐっと高まります! 🚀

実行

設定が完了したら、コマンドを実行してテンプレートに沿ったコンポーネントを作成します。

# npmを使う場合
npm run gen:component

# yarnを使う場合
yarn gen:component

プロンプトの指示に従って情報を入力すると、指定したカテゴリと名前で新規コンポーネントが自動生成されます。

実行例: Button コンポーネント (Atom) を生成する場合

$ npm run gen:component // またはyarn gen:component
? コンポーネント名を入力してください(例: Button) › Button
? Atomic Design のカテゴリを選択してください: › atoms
? カテゴリ内に追加のディレクトリが必要な場合、名前を入力してください(例: Common / 空欄可) ›
? コンポーネントのファイルタイプを選択してください: › tsx
? スタイルファイル(.module.css)も生成しますか? (Y/n) › Yes
? 型定義ファイル(types.ts)も生成しますか? (Y/n) › Yes
? Storybookファイル(.stories.tsx / .docs.mdx)も生成しますか? (Y/n) › Yes

✔ make directory src/components/atoms/Button
✔   create src/components/atoms/Button/index.tsx
✔   create src/components/atoms/Button/Button.module.css
✔   create src/components/atoms/Button/types.ts
✔   create src/components/atoms/Button/stories/Button.docs.mdx
✔   create src/components/atoms/Button/stories/Button.stories.tsx

上記のように実行すると、src/components/atoms/Button/ 配下に以下のようなファイル群が生成されます。

src/
└── components/
    └── atoms/
        └── Button/
            ├── index.tsx             // コンポーネント本体
            ├── Button.module.css      // スタイル
            ├── types.ts               // 型定義
            └── stories/
                ├── Button.docs.mdx    // ドキュメント
                └── Button.stories.tsx // Storybook用ストーリー

まとめ

Hygen を使うことで新規コンポーネント作成の手間が減るだけでなく、Atomic Design のような特定のフォルダ構成やコーディング規約に沿ったファイル生成が可能になります。これにより、作業の効率化とコードの統一感アップが期待できます。

Discussion