본문 바로가기

프로젝트 개발일지/와우 디자인 시스템

[와우 디자인 시스템 개발기] 컴포넌트 빌드 전 entry point 자동 생성 스크립트 개발 및 개선

바로 전 글에서 배럴 파일을 사용하는 것보다 컴포넌트별로 multiple entry point를 지정해서 우리 디자인 시스템을 관리하고 있다고 했다.
이때, 자동화할 것이 아니라면 그냥 배럴 파일 쓰는 게 나을 수 있다고 얘기했었다.
 
아래 예시를 한 번 보자.
배럴 파일은 index.ts 파일에 아래와 같이 export하고 싶은 컴포넌트는 export 구문만 적어주면 된다.

export type { TextField1Props } from "./TextField1";
export { default as TextField1 } from "./TextField1";

export type { TextField2Props } from "./TextField2";
export { default as TextField2 } from "./TextField2";

 
그런데 multiple entry point를 지정해주는 경우는 rollup config 파일에 input도 조정해줘야 하고, package.json exports 속성도 수정해줘야 한다.
이 컴포넌트가 잘 되는지 확인해보려면 빌드해서 확인해봐야 하는데, 컴포넌트 개발하고 매번 빌드 전에 이 두 파일을 왔다 갔다 하며 작성하는 건 너무 비효율적이라고 생각했다.
이럴 거면 multiple entry point 안 했지...

// rollup.config.js
// ...

export default {
  input: {
    TextField1: "./src/components/TextField1",
    TextField2: "./src/components/TextField2",
    // ...
  },
  // ...
};

 

// package.json
{
  // ...
  "exports": {
    "./styles.css": "./dist/styles.css",
    "./TextField1": {
      "types": "./dist/components/TextField1/index.d.ts",
      "require": "./dist/TextField1.cjs",
      "import": "./dist/TextField1.js"
    },
    "./TextField2": {
      "types": "./dist/components/TextField2/index.d.ts",
      "require": "./dist/TextField2.cjs",
      "import": "./dist/TextField2.js"
    },
  },
  // ...
}

 
그래서 이 과정을 자동화하고자 했다.
이번에도 빌드 스크립트를 짜봤다.
전체 코드가 궁금하면 아래 링크를 들어가 보자.
https://github.com/GDSC-Hongik/wow-design-system/blob/main/packages/scripts/generateBuildConfig.ts
 

주요 로직

주요 로직은 다음과 같다.
1. 컴포넌트가 들어있는 폴더를 읽어 entry point를 지정해줘야 하는 컴포넌트명을 저장한다.
2. 각 컴포넌트를 돌면서 exports 객체를 생성한다.
3. 위에서 생성한 exports 객체를 package.json에 적용한다.
4. 또한, 각 컴포넌트를 돌면서 rollup input 객체도 생성한다.
5. 위에서 생성한 input 객체를 rollup config에 있는 input 객체 대신 적용한다.
 
사실 뭐 코드는 노드 파일 시스템에 대해서 조금 아는 사람이라면 쉽게 이해할 거 같아서 설명은 넘어가도 될 거 같다.
 
이렇게 작성한 스크립트는 wow-ui 패키지에서 사용해야 하기 때문에 wow-ui package.json에 스크립트를 등록해 주는 과정이 필요하다.
아래 generate:build-config가 바로 그 스크립트다.
이 스크립트를 통해 빌드 전에 entry point를 설정해주어야 하기 때문에 빌드 전에 실행할 수 있도록 build 스크립트도 수정해 주었다.

"scripts": {
    "build": "pnpm generate:build-config && rm -rf dist && rollup -c --bundleConfigAsCjs && tsc-alias && panda cssgen --outfile dist/styles.css",
    "lint": "eslint . --max-warnings 0",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    "generate:build-config": "tsx ../scripts/generateBuildConfig.ts && prettier --write ./rollup.config.js ./package.json",
    "test-storybook": "test-storybook --browsers firefox chromium webkit",
    "test": "jest"
  },

 
이렇게 하면 우리가 원하는 대로 빌드 전에 두 파일에 설정 정보가 잘 적용된다고 생각했었다.
안타깝게도 처음에 짠 코드는 확장성이 좀... 부족한 코드였다.
 
문제는 바로 컴포넌트 폴더를 불러오는 과정에서 발생했다.
처음에는 아래와 같이 components 폴더 내부에는 컴포넌트 파일, 스토리, 테스트 코드를 담은 각각의 폴더가 있었고, 해당 폴더에는 하나의 컴포넌트 파일만 있었다.

 
그래서, 위 스크립트에서 아래와 같이 components 디렉토리에서 폴더 이름만 불러오게 되면 Box, Button, Chip ... 이렇게 entry point를 문제없이 지정할 수 있었다.

const files = await fs.readdir(directoryPath);

 
그러나, 문제는 RadioGroup 컴포넌트가 생기고부터 발생했다...
RadioButton 컴포넌트는 RadioGroup 없이 사용되는 일이 거의 없다시피 하기 때문에 이렇게 RadioGroup 폴더 내부에 두는 게 더 자연스럽다.
그래서,,, 스크립트를 수정해 보기로 했다.


아래 PR에서 열심히 수정했다...
코드가 궁금하다면 아래 PR에 들어가서 구경해도 좋을 거 같다.
https://github.com/GDSC-Hongik/wow-design-system/pull/51
 
변경된 로직을 설명해 보자면, 컴포넌트 폴더에 들어있는 폴더명만 불러오는 것이 아니라 재귀적으로 폴더 내부 파일명까지 가져오도록 했다.
그 후 해당 파일들이 컴포넌트 파일, 즉 tsx 파일이어야 하고, 테스트 코드나 스토리 코드가 아니어야 하기 때문에 아래와 같이 필터링을 해주는 과정을 추가해 줬다.

const getFilteredComponentFiles = async (directoryPath: string) => {
  const files = await fs.readdir(directoryPath, { recursive: true });

  return files.filter(
    (file) =>
      file.endsWith(".tsx") &&
      !file.includes("test") &&
      !file.includes("stories")
  );
};

 
근데 생각보다 로직 개선하는 과정이 재밌었다.
추후, 변경된 환경 때문에 스크립트를 수정해야 하는 일이 생긴다면 또 재밌게 해 볼 거 같다.