보통 storybook으로 컴포넌트 테스트를 하게 되면
**.stories 파일 생성3번의 경우 react-docgen-typescript 이라는 라이브러리로 해결이 어느정도 가능하지만
1,2번의 경우 모든 stories 파일에서 공통적으로 반복해서 해줘야 한다
import type { Meta, StoryObj } from "@storybook/react"
import { Test } from "../components/Test/Test.tsx"
const meta = {
title: "Test",
component: Test,
tags: ["autodocs"],
} satisfies Meta<typeof Test>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {},
}
예전에는 이런 예시 파일같은걸 하나 만들어놓고 복붙한 다음에 컴포넌트 이름 부분만 전체 블럭으로 잡고 수정해버리면 되긴 하지만 개인적으로 그게 더 귀찮다...
그래서 이런 템플릿을 자동으로 생성해주는 기능이 없을까 하다가 plop 이라는 라이브러리를 알게 되었다
Plop is a little tool that saves you time and helps your team build new files with consistency
plop은 nodeJs 기반의 도구로, 코드(파일) 생성 작업을 자동화 할 수 있는 라이브러리이다
plop은 사용자가 지정한 템플릿을 기반으로 코드를 작성해주는데, 이를 통해 일관된 코드 스타일을 유지할 수 있게 해준다
템플릿 코드를 보면 이해하기 쉬운데
//stories.tsx.hbs
import type { Meta, StoryObj } from "@storybook/react"
import { {{pascalCase name}} } from "../components/{{pascalCase name}}/{{pascalCase name}}.tsx"
const meta = {
title: "{{pascalCase name}}",
component: {{pascalCase name}},
tags: ["autodocs"],
} satisfies Meta<typeof {{pascalCase name}}>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {},
}
템플릿 파일은 .hbs 확장자를 통해 만들고, {{pascalCase name}} 부분에는 입력받은 컴포넌트 이름이 들어가게 될 것이다
import { promises as fs } from "fs"
import path from "path"
import { fileURLToPath } from "url"
async function getAllFiles(dirPath) {
let files = []
const items = await fs.readdir(dirPath, { withFileTypes: true })
for (const item of items) {
const fullPath = path.join(dirPath, item.name)
if (item.isDirectory()) {
const nestedFiles = await getAllFiles(fullPath)
files = files.concat(nestedFiles)
} else {
files.push(fullPath)
}
}
return files
}
export default async function writeStories(plop) {
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dir = path.join(__dirname, "../ui/src/components")
const files = await getAllFiles(dir)
const fileNames = files.map((file) => path.relative(dir, file))
plop.setGenerator("Story", {
description: "Create a story file",
prompts: [
{
type: "input",
name: "name",
message: "stories/** 파일을 생성할 컴포넌트 이름을 입력해주세요",
validate: (input) => {
const componentPath = `${input}.tsx`
const isValid = fileNames.some((fileName) =>
fileName.endsWith(componentPath),
)
if (!isValid) {
console.log("\n만들어진 컴포넌트 이름을 입력해주세요.")
return false
}
return true
},
},
],
actions: [
{
type: "add",
path: "../ui/src/stories/{{pascalCase name}}.stories.tsx",
templateFile: "Stories.tsx.hbs",
},
],
})
}
plop을 통해 해당 코드를 실행하게 되면
1. 터미널을 통해 컴포넌트 이름을 입력받는다
getAllFiles 를 통해 components/ 경로에 있는 모든 파일들을 다 찾은 후, 그 중에서 입력받은 컴포넌트가 존재하는지를 찾고 있다path 경로에, 만들어 놓은 template 파일을 이용하여 파일을 생성한다pascalCase name 은 입력받은 컴포넌트 이름이 들어간다완성본
//Test.stories.tsx
import type { Meta, StoryObj } from "@storybook/react"
import { Test } from "../components/Test/Test.tsx"
const meta = {
title: "Test",
component: Test,
tags: ["autodocs"],
} satisfies Meta<typeof Test>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {},
}
이제 귀찮게 복붙하지 않아도 명령어 하나로 stories 템플릿을 생성할 수 있다