Front-End/JavaScript

plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋””์ž์ธ ์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์„ ํ…œํ”Œ๋ฆฟํ™” ํ•ด๋ณด์ž!

Splin 2024. 11. 17.

์ด ๊ธ€๋กœ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด

1. plop๋ฅผ ์ด์šฉํ•œ ํŒŒ์ผ ํ…œํ”Œ๋ฆฟํ™”
2. plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์‚ฌ์šฉ๋ฒ•
2. plop ํ…œํ”Œ๋ฆฟ
3. handlebars ํ…œํ”Œ๋ฆฟ

 

0. ๊ฐœ์š”

๋””์ž์ธ ์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด์„œ ์Šคํ† ๋ฆฌ๋ถ ๊ด€๋ จ ๊ตฌ๊ธ€๋ง ์ค‘์— ํŒŒ์ผ์„ ํ…œํ”Œ๋ฆฟํ™” ํ•˜์—ฌ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์•Œ๊ฒŒ๋˜์–ด ๋””์ž์ธ ์‹œ์Šคํ…œ ํŒŒ์ผ ํ…œํ”Œ๋ฆฟ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ณ  ํ…œํ”Œ๋ฆฟ์„ ๊ณต์œ ํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

 

1. plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ž€?

plop๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ฐœ์š”์—์„œ ๋งํ–ˆ๋“ฏ์ด ํŒŒ์ผ์„ ํ…œํ”Œ๋ฆฟํ™” ํ•˜์—ฌ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค.

plop์€ ํ”„๋กฌํ”„ํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ inquirer์™€ ํ…์ŠคํŠธ ํ˜•์‹์„ ์ƒ์„ฑํ•˜๋Š” ํ…œํ”Œ๋ฆฟ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ด ํ…œํ”Œ๋ฆฟ์„ ๋งŒ๋“œ๋Š” handlebars ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ†ตํ•ด ์ •๋ณด๋ฅผ ์ž…๋ ฅ๋ฐ›๊ณ  ๊ทธ ์ •๋ณด๋กœ ํ…œํ”Œ๋ฆฟ ์–ธ์–ด๋กœ ํ…œํ”Œ๋ฆฟ์„ ๋งŒ๋“ค์–ด ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

2. plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์‚ฌ์šฉ๋ฒ•

์ž์„ธํ•œ ๋‚ด์šฉ์€ plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์—์„œ ๋ณด๋Š” ๊ฒŒ ๋” ๋‚ซ๊ธฐ ๋•Œ๋ฌธ์— ์„ค์น˜ ๋ฐฉ๋ฒ•๊ณผ ์‚ฌ์šฉ๋ฒ•์„ ๊ฐ„๋žตํ•˜๊ฒŒ ํ•ต์‹ฌ๋งŒ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2-1. plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํŒŒ์ผ ๋งŒ๋“ค ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๋‹ˆ, production ํ™˜๊ฒฝ๊นŒ์ง€ ๋ฐ˜์˜๋˜์ง€ ์•Š์•„๋„ ๋˜๊ธฐ ๋•Œ๋ฌธ์— dev๋กœ ์„ค์น˜ํ•ด์ค๋‹ˆ๋‹ค.

// yarn
yarn add -dev plop

// npm
npm install --save-dev plop

2-2. plop script ๋“ฑ๋ก ๋ฐ type ์„ค์ •

plop์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ stript์™€ ESM(ECMAScript Modules)๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด type์„ ์ง€์ •ํ•ด์ค๋‹ˆ๋‹ค. (CJS(CommonJS)๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด type์„ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค.)

CJS(CommonJS), ESM(ECMAScript Modules)๋ž€?
ํŒŒ์ผ ๋ชจ๋“ˆํ™”๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์„ ์ผ์ปซ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋กœ package.json์— ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด CJS๊ฐ€ ๊ธฐ๋ณธ์ž…๋‹ˆ๋‹ค.
CJS๋Š” require / module.exports๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ESM์€ import/export ๋ฌธ์„ ์‚ฌ์šฉํ•ด์„œ ํŒŒ์ผ ๋ชจ๋“ˆํ™”๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
CJS/EMS์™€ package.json์˜ type, exports ๊ด€์‹ฌ์ด ์žˆ์œผ์‹œ๋‹ค๋ฉด ๊ด€๋ จ ๋‹ค์Œ ๊ธ€์„ ์ฝ์–ด๋ด๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
ํ† ์Šค - CommonJS์™€ ESM์— ๋ชจ๋‘ ๋Œ€์‘ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐœ๋ฐœํ•˜๊ธฐ: exports field
// package.json
{
  ...
  "type": "module",
  "scripts": {
    ...
    "plop": "plop"
  }
}

2-3. plopfile.js ์ž‘์„ฑ

ํด๋”์˜ root ๊ฒฝ๋กœ์— plopfile.js๋ฅผ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด setGenerator๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์„ ์ด์šฉํ•ด ํŒŒ์ผ๋“ค์„ ํ…œํ”Œ๋ฆฟํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

setGenerator์˜ prompots ๊ด€๋ จ ์˜ต์…˜์€ inquirer์—์„œ actions์˜ templateFile ๊ด€๋ จ ์˜ต์…˜์€ handlebars์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‹ค์Œ์—์„œ ์ด์–ด์ง‘๋‹ˆ๋‹ค.

export default function (plop) {
	// setGenerator๋กœ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ๊ณผ ํŒŒ์ผ ํ…œํ”Œ๋ฆฟํ™”๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
	plop.setGenerator('generator name', {
		description: 'generator description',
		prompts: [], // inquirer๋ฅผ ์ด์šฉํ•ด prompts๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
		actions: []  // actions ๊ด€๋ จ ์˜ต์…˜์€ plop์—์„œ actions๋‚ด๋ถ€์— templateFile ๊ด€๋ จํ•ด์„œ๋Š” handlebars์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
	});
};

2-4. plop ์‹คํ–‰

script๋ฅผ ์ง€์ •ํ•œ plop ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ๊ตฌ์„ฑํ•œ plop์„ ์‹คํ–‰ํ•˜์—ฌ ์„ค์ •ํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ํŒŒ์ผ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// yarn
yarn plop

// npm
npm plop

plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋””์ž์ธ ์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์„ ํ…œํ”Œ๋ฆฟํ™” ํ•ด๋ณด์ž! - 2. plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์‚ฌ์šฉ๋ฒ• - 2-4. plop ์‹คํ–‰

 

3. plop ํ…œํ”Œ๋ฆฟ ๊ณต์œ  ๋ฐ ๊ฟ€ํŒ

ํŒ€์—์„œ๋Š” Next.js14๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋งŒ๋“ค๊ณ  ์žˆ๋Š” ๋””์ž์ธ ์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ๋ณธ์ ์ธ ํŒŒ์ผ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ด๋ฃจ์–ด์ ธ ์žˆ์–ด ํ•„์š”ํ•œ ๋ถ€๋ถ„์€ ์ˆ˜์ •ํ•˜์—ฌ์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Component
ใ„ดindex.tsx
ใ„ดtype.d.ts
ใ„ดindex.modules.scss
ใ„ดindex.stories.tsx

3-1. plopํŒŒ์ผ

plopfile.js์—์„œ ๋ˆˆ์— ๋„๋Š” ๋ถ€๋ถ„๊ณผ ๊ด€๋ จ ๋งํฌ๋ฅผ ์ ์–ด๋†“๊ฒ ์Šต๋‹ˆ๋‹ค.

  • plop.setHelper: handlebars์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ ์—ฌ๊ธฐ์—์„œ๋Š” propmts ํด๋” ์„ ํƒ์„ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉ - https://handlebarsjs.com/guide/#custom-helpers
  • plop.setPrompt: ์‚ฌ์šฉ์ž๋“ค์ด plop์„ ์ด์šฉํ•ด ๋งŒ๋“  ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋“ฑ๋กํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ - https://github.com/plopjs/awesome-plop

์œ„์—์„œ handlebars ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” {{ }} ๋‚ด๋ถ€์— ์žˆ๋Š” ๊ฐ’๋“ค์„ ์น˜ํ™˜ํ•˜์—ฌ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. handlebars ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜ ์กฐ๊ฑด๋ฌธ ๋“ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ๊ณต์‹ ๋ฌธ์„œ๊ฐ€ ์ž˜ ๋˜์–ด ์žˆ์–ด ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค.

// plopfile.js

import inquirer from "inquirer";
import inquirerDirectory from "inquirer-directory";

export default function (plop) {
  // hbs ํ…œํ”Œ๋ฆฟ(handlebars)์—์„œ ์‚ฌ์šฉํ•  ํฌํ•จ ์—ฌ๋ถ€ ํ•จ์ˆ˜ ์ •์˜
  plop.setHelper("includes", function (arr, values) {
    const valueList = values.split(",");

    return valueList.some((value) => arr.includes(value));
  });

  // ํด๋” ์„ ํƒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ์šฉ
  plop.setPrompt("directory", inquirerDirectory);
  // prompt ์ƒ์„ฑ
  plop.setGenerator("design-system-ui", {
    description: "Create design system ui",
    prompts: [
      // ํด๋” ์„ ํƒ(setPrompt์—์„œ ์ ์šฉํ•œ ํด๋” ์„ ํƒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ)
      {
        type: "directory",
        name: "path",
        message: `1. ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•  ํด๋”๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š” (โฌ†๏ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋น ๋ฅด๊ฒŒ ์„ ํƒ(choose this directory) ํ•  ์ˆ˜ ์žˆ์–ด์š”)`,
        basePath: "ui",
      },
      // ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„ ์ž…๋ ฅ
      {
        type: "input",
        name: "name",
        message: "2. ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”",
      },
      // ์Šคํ† ๋ฆฌ๋ถ ์˜ต์…˜ ์„ ํƒ
      {
        type: "checkbox",
        message: "3. ์Šคํ† ๋ฆฌ๋ถ Meta์— ์‚ฌ์šฉํ•  ์˜ต์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”",
        name: "options",
        loop: false,
        choices: [
          new inquirer.Separator("====== ๊ธฐ๋ณธ ์˜ต์…˜ ======"),
          {
            value: "args",
            name: "args ",
            disabled: "์ปดํฌ๋„ŒํŠธ props ๊ฐ’ ์„ค์ •",
          },
          new inquirer.Separator("====== ์„ ํƒ ์˜ต์…˜ ======"),
          {
            value: "storyHeight",
            name: "story.height (Story๊ฐ€ ๋ณด์—ฌ์งˆ ์˜์—ญ ๋†’์ด ์กฐ์ ˆ)",
          },
          {
            value: "sourceCode",
            name: "source.code (Story์— ๋ณด์—ฌ์งˆ source code ๊ด€๋ จ ์„ค์ •)",
          },
          {
            value: "argTypes",
            name: "argTypes (์ปดํฌ๋„ŒํŠธ props์˜ type๊ด€๋ จ ๋‚ด์šฉ ์„ค์ •)",
          },
          {
            value: "renderOrDecorators",
            name: "render or decorators (Story ๋ Œ๋”๋ง ๋งˆํฌ์—…,์Šคํƒ€์ผ๋ง,๋™์ž‘ ์ œ์–ด)",
          },
        ],
      },
    ],
    // templateFile์—์„œ prompt๋กœ ์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ ๋ฐ›์•„ ํŒŒ์ผ์„ ์ƒ์„ฑ
    actions: [
      // index.tsx ์ƒ์„ฑ
      {
        type: "add",
        path: "ui/{{path}}/{{pascalCase name}}/index.tsx",
        templateFile: "plop-templates/design-system-ui/Component.tsx.hbs",
      },
      // type.d.ts ์ƒ์„ฑ
      {
        type: "add",
        path: "ui/{{path}}/{{pascalCase name}}/type.d.ts",
        templateFile: "plop-templates/design-system-ui/Type.d.ts.hbs",
      },
      // index.module.scss ์ƒ์„ฑ
      {
        type: "add",
        path: "ui/{{path}}/{{pascalCase name}}/index.module.scss",
      },
      // index.stories.tsx ์ƒ์„ฑ
      {
        type: "add",
        path: "ui/{{path}}/{{pascalCase name}}/index.stories.tsx",
        templateFile: "plop-templates/design-system-ui/Story.tsx.hbs",
      },
    ],
  });
}

3-2. handlebars ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ

plopํŒŒ์ผ์—์„œ ์ •์˜ํ•œ includes helper๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๊ณ  if๋ฌธ์„ ํ†ตํ•˜์—ฌ ํ…œํ”Œ๋ฆฟ์„ ์กฐ๊ฑด๋ถ€๋กœ ์ ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

handlebars์—์„œ helper ์‚ฌ์šฉ๋ฒ•
๊ธฐ๋ณธ ์ ์œผ๋กœ {{ ํ—ฌํผ์ด๋ฆ„ ๋ฉ”์„œ๋“œ์ธ์ž }} ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ, ์กฐ๊ฑด๋ฌธ ์•ˆ์— ๋„ฃ์„ ๋•Œ๋Š” ๊ด„ํ˜ธ๋กœ ๊ฐ์‹ธ์„œ( {{#if (includes options "storyHeight,sourceCode")}} ) ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ex) {{ pascalCase name }} : pascalCase๋Š” handlebars์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์„œ๋“œ ์ธ๋ฐ, prompt์—์„œ ๋ฐ›์€ name ๊ฐ’์„ pascalCase ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด pascalCase๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

Story.tsx.hbs

import {{pascalCase name}} from "@design-system/ui/{{path}}/{{pascalCase name}}";
import type { Meta, StoryObj } from "@storybook/react";

/**
 * ์—ฌ๊ธฐ์— ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ ์–ด์ฃผ์„ธ์š”. ๋ฏธ๊ธฐ์žฌ ์‹œ {{pascalCase name}} ์ปดํฌ๋„ŒํŠธ์˜ JSDoc์ด ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
 * (Storybook์—์„œ parameters.docs.description.component ๋ณด๋‹ค JSDoc์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.)
 * @see {https://storybook.js.org/docs/api/doc-blocks/doc-block-description#writing-descriptions}
 */
const meta: Meta<typeof {{pascalCase name}}> = {
  component: {{pascalCase name}},
  {{#if (includes options "storyHeight,sourceCode")}}
  parameters: {
    docs: {
      {{#if (includes options "storyHeight")}}
      story: {
        // (Optional) story๊ฐ€ ๋ณด์—ฌ์งˆ ์˜์—ญ ๋†’์ด๋ฅผ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        height: "200px",
      },
      {{/if}}
      {{#if (includes options "sourceCode")}}
      source: {
        /**
         * (Optional)
         * storybook์— ๋…ธ์ถœ๋˜๋Š” source code๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (dedent๋ฅผ ์‚ฌ์šฉํ•ด ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ์ข‹์Šต๋‹ˆ๋‹ค.)
         *
         * code์™€ ์•„๋ž˜์˜ transform ๋‘˜ ๋‹ค ์ž‘์„ฑํ•˜๋ฉด transform์€ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.
         */
        code: dedent`
          const [state, setstate] = useState();

          return (
          <Component>
            <SubComponent/>
          </Component>
          );`,
        /**
         * (Optional)
         * storybook์— ๋…ธ์ถœ๋˜๋Š” source code๋ฅผ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
         *
         * ํ•ฉ์„ฑ ์ปดํฌ๋„ŒํŠธ์ธ SubComponent๋ฅผ ํ‘œํ˜„ํ•˜๋ ค๊ณ  Meta.component์— Component.SubComponent๋กœ ์ž‘์„ฑํ•ด์ฃผ์–ด๋„
         * source code์—๋Š” SubComponent๋กœ ๋…ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ฉ์„ฑ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
         */
        transform: (code: string) =>
          code.replaceAll("SubComponent", "Component.SubComponent"),
      },
      {{/if}}
    },
  },
  {{/if}}
  {{#if (includes options "argTypes")}}
  argTypes: {
    props1: {
      // (Optional) ๋ฏธ๊ธฐ์žฌ ์‹œ ํ•ด๋‹น prop์˜ JSDoc์ด ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
      description: "์—ฌ๊ธฐ์— props1์˜ ์„ค๋ช…์„ ์ ์–ด์ฃผ์„ธ์š”.",
      table: {
        // description ์•„๋ž˜ ์œ„์น˜ํ•˜๋ฉฐ, type์„ ๋‚˜ํƒ€๋‚ด์ค๋‹ˆ๋‹ค
        type: {
          summary: "default ๊ฐ’์„ ํ‘œ์‹œํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
          detail: "detail์€ summary๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.",
        },
        /**
         * (Optional)
         * args์— props1์ด ์žˆ๊ณ  defaultValue๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด args์— ์ •์˜๋œ ๊ฐ’์ด default ๊ฐ’์œผ๋กœ ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
         */
        defaultValue: {
          summary: "default ๊ฐ’์„ ํ‘œ์‹œํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
          detail: "detail์€ summary๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.",
        },
        /**
         * (Optional)
         * props1์— category๋ฅผ ์ •์˜ํ•˜๋ฉด props1์ด ๊ธฐ์กด ์œ„์น˜๊ฐ€ ์•„๋‹Œ ํ† ๊ธ€๋กœ ๋œ category์— ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
         * subcategory๋Š” ์ •์˜ํ•œ category ์•„๋ž˜์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
         * ํ•ฉ์„ฑ ์ปดํฌ๋„ŒํŠธ๋‚˜, ์ค‘์ฒฉ๋œ props๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
         */
        category: "category",
        subcategory: "subcategory",
        // (Optional) Args Table์—์„œ props1์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
        disable: true,
        // (Optional) Args Table์—์„œ props1์ด ์ฝ๊ธฐ ์ „์šฉ์ž„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
        readonly: true,
      },
      /**
       * (Optional)
       * Args Table์—์„œ ์‚ฌ์šฉ์ž ์กฐ์ž‘์— ๊ด€ํ•œ ์„ค์ •์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
       * ํŠน์ • type(select, radio ๋“ฑ..)์˜ ๊ฒฝ์šฐ์— options ๊ฐ’์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
       * @see {https://storybook.js.org/docs/api/arg-types#control}
       */
      control: "select",
      options: ["option1", "option2"],
    },
  },
  {{/if}}
  // (Optional) Meta์—์„œ args์— ์ž…๋ ฅํ•œ ๊ฐ’์€ Args Table์— default ๊ฐ’์œผ๋กœ ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  args: {
    props1: "์—ฌ๊ธฐ์— props1 ํƒ€์ž…์— ๋งž๋Š” ๊ฐ’์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”",
  },
  {{#if (includes options "renderOrDecorators")}}
  /**
   * (Optional) render or decorators
   * Storybook์— ๋ Œ๋”๋ง ๋  ์ปดํฌ๋„ŒํŠธ์— ์ถ”๊ฐ€๋กœ ๋งˆํฌ์—…/์Šคํƒ€์ผ๋ง์„ ํ•˜๊ฑฐ๋‚˜ ๋™์ž‘ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
   *
   * storybook ๋‚ด ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ args๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ useArgs Addon๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.
   * @see {https://storybook.js.org/docs/writing-stories/args#setting-args-from-within-a-story}
   */
  // ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  decorators: [
    (Story, context) => {
      return <Story {...context} args={context.args} />;
    },
  ],
  // ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋‚˜ ํ•ฉ์„ฑ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  render: (args) => {
    return (
      <>
        <Component>
          <SubComponent />
        </Component>
        <Component>
          <SubComponent />
        </Component>
      </>
    );
  },
  {{/if}}
};

export default meta;

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

/**
 * ์—ฌ๊ธฐ์— ํ•ด๋‹น Story์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ ์–ด์ฃผ์„ธ์š”
 * Storybook์—์„œ parameters.docs.description.story ๋ณด๋‹ค JSDoc์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
 * @see {https://storybook.js.org/docs/api/doc-blocks/doc-block-description#writing-descriptions}
 */
export const Default: Story = {
  args: { props1: "option1" },
};

Component.tsx.hbs

import classNames from "classnames/bind";
import type { {{pascalCase name}}Props } from "@design-system/ui/{{path}}/{{pascalCase name}}/type";
import style from "@design-system/ui/{{path}}/{{pascalCase name}}/index.module.scss";

const cx = classNames.bind(style);

function {{pascalCase name}}(props: {{pascalCase name}}Props) {
  return <div />;
}

export default {{pascalCase name}};

Type.d.ts.hbs

export interface {{pascalCase name}}Props {}

 

๋งˆ์น˜๋ฉฐ

๋™๋ฃŒ๋“ค๊ณผ ๊ฐœ๋ฐœํ•  ๋•Œ, ์–ด๋А ์ •๋„ ๊ทœ์น™์„ ๋งŒ๋“ค๋ฉฐ ๋ฐ˜๋ณต๋˜๋Š” ์—…๋ฌด๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ํŠน์ • ๋ถ€๋ถ„๋“ค์„ ํ…œํ”Œ๋ฆฟํ™” ํ•˜๋Š” ๊ฒƒ์ด ์ƒ์‚ฐ์„ฑ์„ ๋†’์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด plop ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ์ƒ์‚ฐ์„ฑ์„ ๋งŽ์ด ๋†’์—ฌ์ค„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋” ์ข‹์€ plop ํ…œํ”Œ๋ฆฟ์„ ๊ตฌ์„ฑํ•˜์‹œ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ์œผ์‹œ๋‹ค๋ฉด ๋‹ค์–‘ํ•œ ํ”ผ๋“œ๋ฐฑ์œผ๋กœ ๊ณต์œ ํ•ด์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค ๐Ÿ™

๋Œ“๊ธ€