Infra

[plopjs] plop을 통한 코드 생성 자동화 (React)

Daejlee 2025. 6. 16. 00:06

1. 코드 생성 자동화의 필요성

리액트 컴포넌트를 개발한다고 합시다. 컴포넌트의 이름을 정하고,  index.tsx, index.module.scss, stories.tsx를 만듭니다.

그리고 항상 똑같은 코드를 작성합니다.

컴포넌트의 이름만 바뀌고, 같은 코드가 반복됩니다.

// index.tsx
import classNames from 'classnames/bind';
import styles from './styles.module.scss';
const cx = classNames.bind(styles);
interface Props {}
export const Component = ({}: Props) => {
	return <></>
};

// index.stories.tsx
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { Component } from '.';
export default {
	title: '',
    component: Component,
} as Meta<typeof Component>;
export const 기본: StoryObj<typeof Component> = {
	name: 'Component',
    args: {},
};

// styles.module.scss
...

뭐 이런 거까지 자동화를 해? 별다자(별걸 다 자동화 하네)라고 생각하실 수 있지만, 생각보다 절약되는 시간이 많습니다.

또한, 많은 작업자들이 관여하는 모노레포의 경우 일관된 코드 스타일을 구축하는 데에 도움을 줄 수 있습니다.

무엇보다 규모가 거대한 모노레포의 특징을 고려했을 때, 내가 생성할 컴포넌트의 적절한 위치를 찾는 것 또한 피곤한 과정이 됩니다.

plopjs가 이런 작업을 위해 존재합니다.

2. plopjs를 활용한 코드 생성 자동화

 

Plop: Consistency Made Simple

A little tool that saves you time and helps your team build new files with consistency. Generate code when you want, how you want.

plopjs.com

Plop is what I like to call a "micro-generator framework."

plop은 일관된 형식으로 코드를 생성할 수 있게 하는 프레임워크입니다.

위의 리액트 컴포넌트 생성 과정을 plop으로 자동화 해볼까요?

자동화를 위해 필요한 정보는 2가지 입니다.

  1. 컴포넌트의 이름
  2. 컴포넌트가 생성될 폴더

이 두 가지 정보로 컴포넌트를 자동 생성해보겠습니다.

2.1 plop 셋업

$ pnpm install --save-dev plop

plop을 설치하고, 프로젝트의 루트 디렉토리에 propfile.mjs 파일을 생성합니다.

import { execSync } from 'child_process';

export default function (plop) {
  plop.setGenerator('component', {
    description: '컴포넌트를 생성합니다.',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: '컴포넌트 이름을 입력하세요:',
      },
      {
        type: 'input',
        nane: 'path',
        message: '컴포넌트 폴더가 생성될 경로를 입력하세요:',
      },
    ],
    actions: () => {
      const actions = [
        {
          type: 'addMany',
          destination: '{{path}}/{{name}}',
          base: 'plop-templates',
          templateFiles: 'plop-templates/*.hbs',
          abortOnFail: true,
        },
        function formatGeneratedFiles(answers) {
          try {
            const generatedDir = `${answers.path}/${answers.name}`;

            console.log(`코드 스타일을 정리합니다: ${generatedDir}`);
            execSync(`eslint --fix '${generatedDir}' | prettier --write '${generatedDir}'`);
            return '코드 스타일 정리가 완료되었습니다.';
          } catch (error) {
            return '코드 포맷팅을 건너뛰었습니다.';
          }
        },
      ];
      return actions;
    },
  });
}

이제 생성할 파일들의 포맷을 정의할 plop-templates 폴더를 루트에 만들고, 아래 파일들을 만듭니다.

// index.tsx.hbs
import classNames from 'classnames/bind';
import styles from './styles.module.scss';
const cx = classNames.bind(styles);
interface Props {}
export const {{pascalCase name}} = ({}: Props) => {
	return <>This is {{pascalCase name}}</>
};

// index.stories.tsx.hbs
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { {{pascalCase name}} } from '.';
export default {
	title: '',
    component: {{pascalCase name}},
} as Meta<typeof {{pascalCase name}}>;
export const 기본: StoryObj<typeof {{pascalCase name}}> = {
	name: '{{pascalCase name}}',
    args: {},
};

// styles.module.scss.hbs
...

마지막으로 package.json에 plop용 스크립트를 추가합니다.

"scripts": {
    "plop": "plop"
},

이제 CLI에 pnpm plop을 입력해서 컴포넌트를 생성하면 됩니다.

$ pnpm plop
> 컴포넌트 이름을 입력하세요: installment-chip
> 컴포넌트 폴더가 생성될 경로를 입력하세요: src/app/main/_component