본문 바로가기

개발 일기

디자인시스템 만들기 : 컴포넌트 라이브러리 NPM 배포하기 (React, Vite)

1. 프로젝트 생성

Vite 프로젝트 생성:

boottent-design는 프로젝트 이름. 원하는 이름으로 변경 가능.

yarn create vite boottent-design --template react-ts

 

프로젝트 디렉토리로 이동:

cd boottent-design

 

의존성 설치:

yarn install

 

개발 서버 실행 및 확인:

브라우저에서 http://localhost:3000을 열어 기본 React 페이지 확인.

yarn dev

프로젝트내 불필요한 파일들 index.css , App.css , App.tsx, public 을 제거. (삭제하지 않아도 상관없다)

 

완료 후:

Vite를 사용한 React + TypeScript 프로젝트가 정상적으로 실행됨을 확인.

 

2. vite.config.ts 빌드 설정

 

vite-plugin-dts설치

yarn add -D vite-plugin-dts
  • 목적: vite-plugin-dts는 Vite 빌드 과정에서 TypeScript의 선언 파일(.d.ts)을 자동으로 생성해주는 플러그인이다.
  • 타입 정의 제공: 다른 개발자들이 패키지를 사용할 때 TypeScript의 타입 정의를 제공하여, 코드 완성, 타입 검사 등의 이점을 누릴 수 있다.
  • 배포 준비: 타입 정의는 TypeScript 프로젝트에서 패키지를 사용할 때 필수적아다. 타입 정의가 없으면 타입 안전성을 잃게 된다.

 

Vite 설정 파일(vite.config.ts) 수정:

import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';

export default defineConfig({
  // 모듈 해석 설정
  resolve: {
    alias: {
      // 절대 경로 별칭 정의.
      '@': path.resolve(__dirname, './src'),
    },
  },
  // 빌드 설정
  build: {
    // 라이브러리 모드 설정
    lib: {
      // 다중 엔트리 포인트를 객체로 정의.
      // 각 엔트리 포인트는 별도의 파일로 빌드.
      entry: {
        // 메인 엔트리 포인트: 전체 라이브러리의 진입점
        main: resolve(__dirname, "./src/index.ts"),
        // 기능 관련 컴포넌트의 엔트리 포인트
        feature: resolve(__dirname, "./src/feature/index.ts"),
        // UI 관련 컴포넌트의 엔트리 포인트
        ui: resolve(__dirname, "./src/ui/index.ts"),
        // 스타일 관련 파일의 엔트리 포인트
        styles: resolve(__dirname, "./src/styles/index.ts"),
        // 레이아웃 관련 컴포넌트의 엔트리 포인트
        layout: resolve(__dirname, "./src/layout/index.ts"),
        // 유틸리티 함수의 엔트리 포인트
        utils: resolve(__dirname, "./src/utils/index.ts"),
      },
      // 출력 형식을 ES 모듈과 CommonJS로 지정.
      formats: ['es', 'cjs'],
      // 출력 파일 이름을 커스터마이징.
      // 형식(format)과 엔트리 이름(entryName)을 포함하여 파일 이름을 생성.
      fileName: (format, entryName) =>
        `boottent-design.${entryName}.${format}.js`,
    },
    // 빌드 출력 디렉토리를 'dist'로 설정.
    outDir: 'dist',
    // Rollup 관련 추가 설정
    rollupOptions: {
      // 번들에 포함하지 않을 외부 종속성을 정의.
      // 여기서는 React 관련 패키지를 외부로 설정하여 번들에 포함되지 않도록.
      external: ['react', 'react-dom', 'react/jsx-runtime'],
      // 출력 옵션을 설정.
      output: {
        // 외부 종속성의 글로벌 변수 이름을 지정.
        // 주로 UMD 또는 IIFE 형식에서 사용.
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
          'react/jsx-runtime': 'jsxRuntime',
        },
      },
    },
  },
  // 플러그인 설정
  plugins: [
    // React 플러그인을 추가하여 React 관련 기능을 Vite에서 사용할 수 있게.
    react(),
    // 타입 선언 플러그인을 추가하여 TypeScript 타입 선언 파일을 생성.
    dts({
      // Rollup을 사용하여 타입 선언을 번들링
      rollupTypes: true,
      // 타입 선언 파일의 출력 디렉토리를 'dist/types'로 설정.
      outDir: 'dist/types',
      // 타입 선언 파일의 진입점을 자동으로 삽입.
      insertTypesEntry: true,
    }),
  ],
});

 

1) 임포트 섹션:

  • @vitejs/plugin-react: Vite에서 React를 사용하기 위한 플러그인이다.
  • path: 파일 및 디렉토리 경로를 조작하기 위한 Node.js의 내장 모듈이다.
  • defineConfig: Vite 설정을 정의할 때 타입 지원을 제공하는 함수다.
  • vite-plugin-dts: TypeScript 선언 파일을 생성하기 위한 Vite 플러그인이다.

2) 모듈 해석 설정 (resolve):

  • alias를 사용하여 @ 기호를 프로젝트의 src 디렉토리에 매핑힌다. 이를 통해 상대 경로 대신 절대경로를 사용할 수 있다.
  • 예: import Button from '@/components/Button';

3) 빌드 설정 (build):

  • 라이브러리 모드 (lib):
    • entry: 단일 진입점으로 설계하려면 "entry:  path.resolve(__dirname, 'src/index.ts')" 처럼 간단히 적으면 되지만, 필자는 컴포넌트의 성격에 따라 여러 진입점을 가지도록 분류하고자 아래 코드에서 적었듯이, 다중 엔트리 포인트를 객체 형태로 정의했다. 각 키는 엔트리 포인트의 이름을 나타내며, 값은 해당 엔트리 파일의 경로이다. 예를 들어, UI 컴포넌트만 필요할 경우 "import { Button } from 'boottent-design/ui'"와 같이 임포트할 수 있다.
    • formats: 출력할 모듈 형식을 지정한다. 여기서는 ES 모듈과 CommonJS 형식으로 번들링되어, 다양한 환경에서 호환성을 유지한다.
    • fileName: 출력 파일의 이름을 커스터마이징하는 함수입니다. 형식과 엔트리 이름을 사용하여 파일명을 생성한다.
      예 ) boottent-design.ui.es.js, boottent-design.utils.cjs.js 등.

4) 출력 디렉토리 (outDir):

  • 빌드 결과물을 dist 디렉토리에 출력한다는 의미이다.

5) Rollup 옵션 (rollupOptions):

  • external: 번들에 포함하지 않을 외부 종속성을 지정한다. 여기서는 React 관련 패키지를 외부로 설정하여 라이브러리 번들 크기를 줄이고, 소비자가 별도로 React를 설치하도록 유도한다.
  • globals: 외부 종속성의 글로벌 변수 이름을 지정한다. 이는 UMD나 IIFE 형식의 번들에서 사용된다.

6) 플러그인 설정 (plugins):

  • react(): Vite에서 React를 원활하게 사용할 수 있도록 지원하는 플러그인.
  • dts(): TypeScript 선언 파일을 자동으로 생성하는 플러그인.
    • rollupTypes: Rollup을 사용하여 타입 선언을 번들링하도록 설정한다.
    • outDir: 타입 선언 파일의 출력 디렉토리를 dist/types로 설정한다.
    • insertTypesEntry: 타입 선언 파일의 진입점을 자동으로 삽입하여, 라이브러리 사용자가 타입 정보를 쉽게 참조할 수 있도록 한다.

 

 

3. package.json 수정

 

빌드 설정이 완료되었으면, package.json 파일을 적절히 수정하여 배포를 준비.

{
  "name": "디자인시스템 라이브러리 이름",
  "private": false,
  "version": "0.0.1",
  "description": "디자인시스템 라이브러리",
  "repository": {
    "type": "git",
    "url": "<https://github.com/boottent-team/boottent-design.git>"
  },
  "type": "module",
  "files": [
    "dist"
  ],
  "main": "dist/boottent-design.main.cjs",
  "module": "dist/boottent-design.main.es.js",
  "types": "dist/types/main.d.ts",
  "exports": {
    ".": {
      "import": "./dist/boottent-design.main.es.js",
      "require": "./dist/boottent-design.main.cjs",
      "types": "./dist/types/main.d.ts"
    },
    "./feature": {
      "import": "./dist/boottent-design.feature.es.js",
      "require": "./dist/boottent-design.feature.cjs",
      "types": "./dist/types/feature.d.ts"
    },
    "./ui": {
      "import": "./dist/boottent-design.ui.es.js",
      "require": "./dist/boottent-design.ui.cjs",
      "types": "./dist/types/ui.d.ts"
    },
    "./styles": {
      "import": "./dist/boottent-design.styles.es.js",
      "require": "./dist/boottent-design.styles.cjs",
      "types": "./dist/types/styles.d.ts"
    },
    "./layout": {
      "import": "./dist/boottent-design.layout.es.js",
      "require": "./dist/boottent-design.layout.cjs",
      "types": "./dist/types/layout.d.ts"
    },
    "./utils": {
      "import": "./dist/boottent-design.utils.es.js",
      "require": "./dist/boottent-design.utils.cjs",
      "types": "./dist/types/utils.d.ts"
    }
  },
  "scripts": {
			...
  },
  "devDependencies": {
    ...
  },
  ...
}

 

빌드 스크립트:

"build: vite build"로 단순화하여 Vite 빌드 프로세스만 실행하도록 변경했다. vite-plugin-dts가 타입 정의 파일을 자동으로 생성하기 때문에 tsc -b는 필요하지 않다.

 

추가 스크립트:

  • lint:fix: ESLint 규칙을 자동으로 수정하는 스크립트 추가.
  • format: Prettier를 사용하여 코드 포맷팅을 자동으로 수행하는 스크립트 추가.
  • storybook, build-storybook, deploy-storybook: Storybook을 개발, 빌드 및 배포할 수 있는 스크립트 추가.

 

추가 필드:

  • repository: GitHub 리포지토리 URL을 추가하여 패키지 정보 명확히.
  • keywords: 패키지 검색 시 도움이 되는 키워드 추가.
  • author: 패키지 작성자 정보 추가.
  • license: 패키지 라이선스 정보 추가.

 

4. tsconfig.json 수정

수정된 tsconfig.json 코드

{
  "compilerOptions": {
    /* ==================== 기본 컴파일러 옵션 ==================== */

    // JavaScript의 ECMAScript 버전 설정
    "target": "ES2020",

    // 클래스 필드 선언 시 `define` 사용 여부
    "useDefineForClassFields": true,

    // 컴파일 시 포함할 라이브러리의 타입 선언 파일
    "lib": ["ES2020", "DOM", "DOM.Iterable"],

    // 모듈 시스템 설정 (ESNext는 최신 ES 모듈을 사용)
    "module": "ESNext",

    /* ==================== 모듈 해석 및 경로 설정 ==================== */

    // 모듈 해석의 기준 디렉토리 설정 (상대 경로의 기준점)
    "baseUrl": ".",

    // 모듈 경로 별칭 설정
    "paths": {
      "@/*": ["src/*"]
    },

    /* ==================== 모듈 해석 전략 ==================== */

    // 모듈 해석 전략 설정 (bundler 모드 사용 시 적합)
    "moduleResolution": "bundler",

    // TypeScript가 `.ts` 확장자를 가진 파일을 임포트할 수 있게 허용
    "allowImportingTsExtensions": true,

    // JSON 모듈 임포트 허용
    "resolveJsonModule": true,

    // 각 파일을 독립된 모듈로 취급하여 컴파일
    "isolatedModules": true,

    /* ==================== 출력 관련 옵션 ==================== */

    // 컴파일 결과물을 생성하지 않음 (빌드 도구가 생성)
    "noEmit": true,

    /* ==================== JSX 설정 ==================== */

    // JSX 변환 방식 설정 (React 17+의 새로운 JSX 변환)
    "jsx": "react-jsx",

    /* ==================== 타입 검사 강화 ==================== */

    // 모든 엄격한 타입-체크 옵션 활성화
    "strict": true,

    // 사용되지 않는 지역 변수 오류 발생
    "noUnusedLocals": true,

    // 사용되지 않는 매개변수 오류 발생
    "noUnusedParameters": true,

    // switch 문에서 case 간의 낙하(fallthrough) 금지
    "noFallthroughCasesInSwitch": true
  },
  /* ==================== 프로젝트 참조 ==================== */

  // 프로젝트 참조 설정 (타입스크립트 프로젝트 간 의존성 관리)
  "references": [{ "path": "./tsconfig.node.json" }],

  /* ==================== 포함 및 제외 설정 ==================== */

  // 타입스크립트가 컴파일할 파일 및 디렉토리 설정
  "include": [
    "src" // 소스 디렉토리 전체 포함
  ],

  // 타입스크립트가 컴파일하지 않을 파일 및 디렉토리 설정
  "exclude": [
    "node_modules" // node_modules 디렉토리 제외
  ],

  /* ==================== 글로벌 타입 정의 ==================== */

  // 글로벌 타입 정의 파일 지정
  "types": ["types"] // 주의: 'types' 디렉토리 또는 파일이 존재하는지 확인 필요
}

 

 

tsconfig.node.json

{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}

 

5. 타입 정의 확인

yarn build

위의 설정을 바탕으로 Vite가 라이브러리 모드로 빌드한다. dist 폴더에 ESM, CJS 형식의 빌드 파일과 타입 정의 파일(.d.ts)이 생성된다. 빌드 후 dist 폴더에 .d.ts 파일이 생성되었는지 확인하면된다.

 

6. NPM 배포 

NPM 계정 생성:

yarn login

계정이 있다면, 위와같이 터미널에서 로그인하거나, 없으면 npmjs.com에 접속하여 계정을 생성한 후 로그인한다.

 

package.json 설정 검토

  • name: 고유한 패키지 이름인지 확인. 이미 npm에 등록된 이름은 사용할 수 없다.
  • version: 적절한 버전 번호인지 확인. 동일한 버전 번호로는 다시 배포할 수 없다.
  • exports: ESM과 CommonJS를 모두 지원하도록 정확히 설정되었는지 확인.
  • types: 타입 정의 파일 경로가 올바른지 확인.
  • files: 배포할 파일들이 dist 폴더에 포함되어 있는지 확인합니다.

패키지 빌드

yarn build
  • vite build 명령어가 실행되어 라이브러리 모드로 빌드가 진행된다.
  • dist 폴더에 boottent-design.es.js, boottent-design.cjs.js, index.d.ts 파일이 생성된다.

NPM 패키지 배포

yarn publish --access public
  • -access public 옵션을 사용하여 공개 패키지로 배포한다.
  • 프라이빗 패키지의 경우 이 옵션을 생략하거나 다른 설정을 사용한다.

NPM 패키지 정상 배포 확인

yarn add boottent-design
// 다른 프로젝트의 App.tsx 예시

import React from 'react';
import { CustomDialog } from 'boottent-design';

function App() {
  return (
    <div className="App">
      <CustomDialog />
    </div>
  );
}

export default App;

 

다른 프로젝트에서 boottent-design 패키지를 설치하여 사용해본다.