スキャフォールディングツール Scaffdog 使ってみた

## はじめに
本記事はプロもくチャット Adevent Calendar2023の4日目です

https://qiita.com/advent-calendar/2023/puromoku

## スキャフォールディングツールとは
スキャフォールディングツールとは、プログラミングの開発やテストの際に汎用的なコードを生成する際に利用するツールです.

## Node.jsにおけるスキャフォールディングツール
* [hygen](https://www.hygen.io/)
* [plop](https://plopjs.com/)
* [scafffdog](https://scaff.dog/)

npm trands

https://npmtrends.com/hygen-vs-plop-vs-scaffdog

今回は、scaffdogについてまとめていきます🧑‍💻


## scafffdog について
Markdownに記載したテンプレートに従ってファイルを生成するツールです。

ロゴのスカーフつけている犬🐕
かわゆす...😍

## scaffdog install 方法
```console
$ npm i -D scaffdog
```

```console
$ npx scaffdog init

? Please enter a document name. atom

Setup of scaffdog 🐶 is complete!

  ✔ .scaffdog/config.js
  ✔ .scaffdog/atom.md

Now you can do scaffold by running $ scaffdog generate.

Please refer to the following documents and customize it.
https://scaff.dog/docs/templates
```

```diff_json:package.json
  "scripts": {
    ...
+   "scaffold": "scaffdog generate"
  },
```

**これで、設定 完了 👍**

**試しに実行**

```console
$ npm run scafold

? Please select a document. Atom
? Please select the output destination directory. src
? Please enter any text. test

🐶 Generated 1 file!

     ✔ src/test.md

✨  Done in 6.74s.
```

## scafold 基本記述
ここでは、基本的な記述の内容を記載します。

### Attributes

最初に各種設定を入力
```md:.scaffdog/atom.md
---
name: "atom"
root: "src/_components/atom"
output: "." # 
ignore: [] 
questions:
  name: "Please Enter a Component Name..."
  hasReadme:
    confirm: "Is it hab readme? (Default: true)"
    initial: true
---
... 以下に作成したいファイルを記載する
```

key|型|説明|
--|--|--
name|string|実行時に出てくるテンプレート名
root|string|生成するテンプレートのrootディレクトリ
output|string, string[]|出力先 or 出力先の候補
ignore|string, string[]|出力先から除外するディレクトリ
questions|object|実行時に表示される質問内容

https://scaff.dog/docs/templates/attributes

---
### Questions

Questionには、以下の4つの回答方法があります
* Free text
* Boolean
* Choice
* Array

----

#### Free Text
文字列の値を受け入れるためのプロンプトを呼び出します。
利用例）ファイル名やディレクトリ名など

```md:free text
---
questions:
    name: # 回答内容代入先
        message: "Please Enter a Component Name..." # 質問内容
        initial: "button" # デフォルト回答 (デフォルト値を設定したい場合のみ）

    # デフォルト回答が不要の場合、下記の記載でも可
    name: "Please Enter a Component Name..." # 回答内容代入先 : 質問内容
---
```

https://scaff.dog/docs/templates/attributes#free-text

#### Boolean
Boolean値を受け入れるためのプロンプトを呼び出します。
利用例）ファイルの作成の分岐など

```md:boolean
---
questions:
    hasReadme: # 回答内容代入先
        confirm: "Is it hab readme? (Default: true)" # 質問内容
        initial: true # デフォルト回答 (デフォルト値を設定したい場合のみ）
---
```

https://scaff.dog/docs/templates/attributes#boolean

#### Choice
リストから単一の値を選択するためのプロンプトを呼び出します

```md:choice
---
questions:
    value2: # 回答内容代入先
        message: "Please select a value." # 質問内容
            choices: # 選択項目
                - "A"
                - "B"
                - "C"
            initial: "C" # デフォルト回答 (デフォルト値を設定したい場合のみ）
---
```

https://scaff.dog/docs/templates/attributes#choice

#### Array
リストから複数の値を選択するためのプロンプトを呼び出します

```md:array
---
questions:
    value1: # 回答内容代入先
        message: "Please select a value." # 質問内容
            multiple: true # 複数選択可能フラグ
            choices: # 選択項目
                - "A"
                - "B"
                - "C"
            initial: ["C"] # デフォルト回答 (デフォルト値を設定したい場合のみ）
---
```

https://scaff.dog/docs/templates/attributes#array

#### 条件分岐
また下記のように、回答内容によって質問項目を表示させるか分岐させることも可能です

```md
---
questions:
    # Free Text
    name: # 回答内容代入先
        message: "Please Enter a Component Name..." # 質問内容
    test: # 回答内容代入先
        if: contains(inputs.name, 'Form') # 表示内容条件 (この場合 name の回答が"Form"の場合表示される)
        confirm: 'Do you need a test?' # 質問内容

    # Boolean
    hasReadme: # 回答内容代入先
        confirm: "Is it hab readme? (Default: true)" # 質問内容
        initial: true # デフォルト回答
    test: # 回答内容代入先
        if: inputs.hasReadme # 表示内容条件 (この場合 hasReadme の回答が"True"の場合表示される)
        message: "Please Enter a Test Name..." # 質問内容
---
```

https://scaff.dog/docs/templates/attributes#conditional-prompt


## Template Engine, Built-in Helpers, Injection
ここらは書き始めたらキリがないので公式サイトをご覧ください
条件分岐や、ループ処理、ヘルパー関数など様々なものがあります。
気になったらぜひ見に行ってみてください😆

https://scaff.dog/docs/templates/template-engine

https://scaff.dog/docs/templates/helpers

https://scaff.dog/docs/templates/injection

## 構築例

Vite / React / Chakra UI

#### ファイルツリー
```console
.
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc
├── .scaffdog
│   ├── Atom.md
│   ├── Organism.md
│   ├── config.js
│   └── templates
│       ├── atom
│       │   ├── component.stories.tsx
│       │   ├── component.tsx
│       │   ├── index.ts
│       │   └── type.ts
│       └── organisms
│           ├── component.stories.tsx
│           ├── component.test.ts
│           ├── component.tsx
│           ├── hooks.ts
│           ├── index.ts
│           └── type.ts
├── .storybook
│   ├── main.ts
│   └── preview.tsx
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── _components
│   │   ├── atoms
│   │   │   └── TextField
│   │   │       ├── TextField.stories.tsx
│   │   │       ├── TextField.tsx
│   │   │       ├── index.ts
│   │   │       └── type.ts
│   │   └── organisms
│   │       └── Auth
│   │           └── Login
│   │               ├── Login.stories.tsx
│   │               ├── Login.test.ts
│   │               ├── Login.tsx
│   │               ├── hooks.ts
│   │               ├── index.ts
│   │               └── type.ts
│   ├── index.css
│   ├── main.tsx
│   ├── pages
│   ├── constants
│   ├── libs
│   ├── assets
│   ├── theme
│   ├── types
│   ├── utils
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
```

#### scaffdog テンプレート
~~~md:.scaffdog/Organism.md
{{ /* Attributes */ }}
---
name: "Organisms"
root: "src/_components/organisms"
output: "."
ignore: []
questions:
    directory: "Please Enter a Directory Name..."
    name: "Please Enter a Component Name..."
    hasReadme:
        confirm: "Is it hab readme?(Default: true)"
        initial: true
    hasHooks:
      confirm: "Is it hab hooks?(Default: true)"
      initial: true
    hasTest:
      confirm: "Is it hab test?(Default: false)"
      initial: false
---

{{ /* 変数設定 ヘルパー関数 */ }}
# Variables

- name: `{{ inputs.name | pascal }}` {{ /* pascal を用いてパスカルケースに変換 */ }}
- directory: `{{ inputs.directory }}`

{{ /* 以下生成するファイルのパス設定、生成内容の各種テンプレート読み込み */ }}

{{ /* index */ }}
# `{{ directory }}/{{ name }}/index.ts`
```typescript
{{ 'templates/organisms/index.ts' | read }}
```

{{ /* tsx */ }}
# `{{ directory }}/{{ name }}/{{ name }}.tsx`
```typescript
{{ 'templates/organisms/component.tsx' | read }}
```

{{ /* storybook */ }}
# `{{ directory }}/{{ name }}/{{ name }}.stories.tsx`
```typescript
{{ 'templates/organisms/component.stories.tsx' | read }}
```

{{ /* type */ }}
# `{{ directory }}/{{ name }}/type.ts`
```typescript
{{ 'templates/organisms/type.ts' | read }}
```

{{ /* hooks */ }}
# `{{ !inputs.hasHooks && '!' }}{{ directory }}/{{ name }}/hooks.ts`
```typescript
{{ 'templates/organisms/hooks.ts' | read }}
```

{{ /* テスト */ }}
# `{{ !inputs.hasTest && '!' }}{{ directory }}/{{ name }}/{{ name }}.test.ts`
```typescript
{{ 'templates/organisms/component.test.ts' | read }}
```
~~~

```ts:.scaffdog/templates/organisms/index.ts
export * from './{{ name }}';
```

```ts:.scaffdog/templates/organisms/index.ts
export * from './{{ name }}';
```

```ts:.scaffdog/templates/organisms/component.tsx
import { type {{ name }}Props } from './type';
import { memo } from 'react';

const {{ name }}Component = ({}: {{ name }}Props) => {
    return (
        <>
            {{ name }} Component
        </>
    )
}

const {{ name }} = memo<{{ name }}Props>(({}) => {
    return (
        <{{name}}Component />
    );
});

export default {{ name }}
```

```ts:.scaffdog/templates/organisms/component.stories.tsx
import {type Meta, type StoryObj} from "@storybook/react";
import {{ name }} from './{{ name }}';

const meta: Meta<typeof {{ name }}> = {
    component: {{ name}},
    title: 'Atomic Design/Organisms/{{ directory | pascal }}/{{ name }}',
    tags: ['autodocs'],
    argTypes: {}
}

export default meta;

type Story = StoryObj<typeof meta>;

export const Base: Story = {
    name: 'base',
    args: {}
}
```

```ts:.scaffdog/templates/organisms/type.ts
export type {{ name }}Props = {}
```

```ts:.scaffdog/templates/organisms/hooks.ts
{{ /* 未設定 */ }}
```

```ts:.scaffdog/templates/organisms/component.test.ts
{{ /* 未設定 */ }}
```

#### 実行動画

<iframe width="560" height="315" src="https://www.youtube.com/embed/aqxKccAQ6Co?si=6SvyFeoIpj_BswMQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## まとめ

今回記載していませんが、指定の行に追加などカスタマイズすることもでき、コマンドひとつでテンプレートをもとに複数ファイルが作成することができるので、時間の短縮ができ効率もアップするのでおすすめです:relaxed:


:::note info
閲覧ありがとうございますした。
記事書くの初なので・・・間違ってる部分とかあればコメントでご指示いただけますと幸いです。
:::

---
参考サイト

https://scaff.dog/

https://blog.wadackel.me/2022/scaffdog-v2/

https://zenn.dev/taizo_pro/articles/07c3b6dfa89395

https://qiita.com/y-natani/items/b8034b2d7c7fbafe63bf