11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

scaffdogでテンプレファイルを自動生成する

Last updated at Posted at 2022-12-16

はじめに

こんにちは!
Webフロントエンドエンジニアの那谷(なたに)です。

みなさんはコンポーネントを作るときって最初のファイルはどうしていますか。
1から書いていますか?
それとも別コンポーネントをコピペして中身を微修正していますか?
私は後者のパターンでした。

ただ、細かいところまで修正しきれておらず、PRで「ボタン関連コンポーネントなのに〇〇Checkbox(コピー元コンポーネント)になっている!」と指定を受けたりしました。

人間は気をつけていてもミスをするもの。。。。
こういった仕事はツールで自動化したいですね!

解決したいこと

  • 新規コンポーネントのファイル作成がめんどう
  • 開発者ごとに書き方にゆらぎがある

scaffdogとは

Node.js製のコマンド1つでテンプレを生成するツールです。
設定ファイルもmarkdownから設定ができます。

scaffdogの導入方法

  1. scaffdogをインストールします。

    npm i -D scaffdog
    
  2. package.jsonのscriptsに追加

    package.json
    {
      "scripts": {
        "scaffdog": "scaffdog"
      },
    }
    
  3. scaffdogのディレクトリ、初期テンプレートを生成

    npm run scaffdog init
    

    初期テンプレートのファイル名を聞かれます。
    本記事では、初期テンプレート名をMyFirstTemplateとしています。

  4. 最後に動作確認をしましょう

    npm run scaffdog generate
    

    [入力した文字].mdが指定したディレクトリに生成されていれば導入は完了です!

/.scaffdogディレクトリについて

scaffdog initで生成されたファイルについて説明します。

.scaffdog
  ├── MyFirstTemplate.md
  └── config.js

config.js

テンプレートとして使用する*.mdファイルのパスを定義しています。
他にもグローバル変数の定義、mustache記法を任意の記法に変更できます。

参考:scaffdog-config

MyFirstTemplate.md

MyFirstTemplate.md
---
name: "MyFirstTemplate"   ← テンプレート名
root: "."                 ← 生成するテンプレートのrootディレクトリ
output: "**/*"            ← rootを基準に候補として表示するディレクトリ
ignore: []                ← 候補から無視するディレクトリ
questions:                ← CLIの質問。回答結果を変数{{inputs.[key名]}}の形で利用する
  value: "Please enter any text."
---

# `{{ inputs.value }}.md`  ← 生成するファイル名(変数でなくてもOK)

```markdown                ← 生成するファイルの中身
Let's make a document!
See scaffdog documentation for details.
https://scaff.dog/docs/templates
```

参考:scaffdog-Attributes

scaffdogでオリジナルテンプレートを作ってみる

実際に使いそうなファイルを用いてテンプレートを作ってみましょう!

サンプルコード

サンプルとして以下のようなディレクトリを作ってみます。
よくあるコンポーネントのあるある三銃士ですね!

└── [コンポーネント名]
    ├── index.tsx             // コンポーネントファイル
    ├── index.stories.tsx     // Storybookファイル
    └── index.test.tsx        // テストファイル

テンプレート化する前の中身は以下のようになっています。
手動でコピペすると、どこか変更し忘れそうですね。

index.tsx
index.tsx
import { FC } from "react";

export const SomeText: FC = () => {
  return <div data-testid="text">Show some text...</div>;
};

index.stories.tsx
index.stories.tsx
import { ComponentStoryObj, ComponentMeta } from "@storybook/react";
import { SomeText } from ".";
type StoryObj = ComponentStoryObj<typeof SomeText>;
type ComponentProps = Required<typeof SomeText.defaultProps>;

export default {
  title: "SomeText",
  component: SomeText,
} as ComponentMeta<typeof SomeText>;

const args: ComponentProps = {};

export const Basic: StoryObj = {
  args: { ...args },
};

index.test.tsx
index.test.tsx
import { composeStories } from "@storybook/testing-react";
import "@testing-library/jest-dm";
import { render, screen } from "@testing-library/react";

import * as stories from "./index.stories";

const { Basic } = composeStories(stories);

describe("コンポーネント", () => {
  it("div内に適切な文字があること", () => {
    render(<Basic />);
    expect(screen.getByTestId("text")).toHaveTextContent("Show some text...");
  });
});

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

.scaffdog直下に任意の名前のmdファイルを作る。

デフォルトと異なり、下記を変更しています。

  • テンプレート名
  • 質問文、キー名の変更
  • コードを別ファイルから読み込む({{ 'templates/ReactTemplate/*.tsx' | read }}の部分)
.scaffdog/ReactTemplate.md
---
name: "ReactTemplate"
root: "."
output: "**/*"
ignore: []
questions:
  componentName: "What is component name?"
---

# `{{inputs.componentName}}/index.tsx`

```
{{ 'templates/ReactTemplate/index.tsx' | read }}
```

# `{{inputs.componentName}}/index.stories.tsx`

```
{{ 'templates/ReactTemplate/index.stories.tsx' | read }}
```

# `{{inputs.componentName}}/index.test.tsx`

```
{{ 'templates/ReactTemplate/index.test.tsx' | read }}
```

2. 変数の埋め込み

質問に応じて可変にする項目に気をつけながら、.scaffdog/templates/ReactTemplates直下にファイルを作成します。

.scaffdog/templates/ReactTemplate/index.tsx
import { FC } from "react";

export const {{ inputs.componentName }}: FC = () => {
  return <div data-testid="text">Show some text...</div>;
};
.scaffdog/templates/ReactTemplate/index.stories.tsx
import { ComponentStoryObj, ComponentMeta } from "@storybook/react";
import { {{ inputs.componentName }} } from ".";
type StoryObj = ComponentStoryObj<typeof {{ inputs.componentName }}>;
type ComponentProps = Required<typeof {{ inputs.componentName }}.defaultProps>;

export default {
  title: "{{ inputs.componentName }}",
  component: {{ inputs.componentName }},
} as ComponentMeta<typeof {{ inputs.componentName }}>;

const args: ComponentProps = {};

export const Basic: StoryObj = {
  args: { ...args },
};
.scaffdog/templates/ReactTemplate/index.test.tsx
import { composeStories } from "@storybook/testing-react";
import "@testing-library/jest-dm";
import { render, screen } from "@testing-library/react";

import * as stories from "./index.stories";

const { Basic } = composeStories(stories);

describe("コンポーネント", () => {
  it("div内に適切な文字があること", () => {
    render(<Basic />);
    expect(screen.getByTestId("text")).toHaveTextContent("Show some text...");
  });
});

3. 実行

できあがったら再度npm run scaffdog generateで生成してみましょう!
任意のディレクトリにファイルが3つ生成されていれば完成です!

まとめ

コマンド1つで複数ファイルが生成でき、かなり効率良く作業を進められるようになりました。
プロジェクトの細かいルールを取り入れながらカスタマイズができそうです。

かなり読みやすい公式サイトもあり、困ったことがあればここだけで解決できそうですね!
参考:scaffdog - Markdown driven scaffolding tool.

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?