NPM 배포 연대기 - 1. 설정

NPM 배포 연대기 - 1. 설정

NPM 배포하기 - 설정편

8 min read
ReactNPMRollupJSComponent

개요

오랜만에 시리즈물 글을 작성하네요.

이번에는 React 컴포넌트를 NPM 에 배포하는 과정을 적게 될 것 같습니다. 설정 → 개발 → 배포 → 관리 → 유지보수 → 재배포 순서로 작성해볼까 하고 있습니다. 흐름만 그렇습니다만 React 컴포넌트 뿐만 아니라 훅이나 유틸 함수 등도 결국 유사하게 개발됩니다.



설정

이번 포스팅 배포에서는 다음 내용들이 포함됩니다.

  • React
  • Typescript
  • Storybook
  • Rollup
  • ... (개발하며 추가)

다운로드

빠르게 프로젝트를 꾸리고 넘어가겠습니다.

먼저 package 를 구성합니다.

npm init
yarn init

다음으로 React 와 React-dom 을 받고 typescript 역시 추가합니다.

yarn add react react-dom
yarn add -D typescript @types/react @types/react-dom

추가로 peerDependencies 를 설정하면서 의존하고 있는 패키지에 대해 정의합니다. peerDependencies는 런타임에는 필요하나 직접 관리하지 않는 것들이 위치합니다. package.json 에서 해당 내용을 직접추가해도 무관합니다.

yarn add -P react react-dom

여기까지 진행하게 되면 package.json 은 다음과 같습니다.

package.json
{
    "name": "dragond-react",
    "version": "0.1.0",
    "description": "",
    "main": "index.js",
    "license": "MIT",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "kyechan99",
    "dependencies": {
        "react": "^18.2.0",
        "react-dom": "^18.2.0"
    },
    "devDependencies": {
        "@types/react": "^18.2.48",
        "@types/react-dom": "^18.2.18",
        "typescript": "^5.3.3"
    }
    "peerDependencies": {
        "react": "^18.2.0",
        "react-dom": "^18.2.0"
    }
}

package.json 에 작성하는 내용이 NPM 페이지에 작성되기 때문에 정확하고 잘 검색될 수 있게 고민하는 것도 중요합니다. 특히 name 이 패키지의 이름이 되기 때문에 기존에 존재하는 이름일 경우 등록되지 않으니 주의하세요.


Storybook

Storybook 을 이용해 만들 컴포넌트들을 띄우고, 정리하겠습니다.

(수동 설정도 좋지만 자동 설정을 마다할 이유가 없습니다)

npx storybook@latest init

정상적으로 설치가 되었다면 Storybook에서 기본으로 만들어주는 stories 들을 모두 삭제합니다.

stories 폴더 자체는 src 내부로 가져와서 작성했습니다.


그럼 이제 간단한 컴포넌트 하나를 만들겠습니다.

src/components/Dragond/Dragond.tsx
import React from "react";

const Dragond = () => {
  return <>Hello Word</>;
};
export default Dragond;

지금은 컴포넌트 하나지만 이후 여러가지를 추가하게 될 때 이곳에서 정리하겠습니다.

src/index.ts
export { default as Dragond } from "./components/Dragond/Dragond";

간단한 Story를 만들어 Storybook 에 띄우겠습니다.

src/stories/Dragond.stories.ts
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";

import Dragond from "../src/components/Dragond/Dragond";

const meta = {
  title: "Dragond/Dragond",
  component: Dragond,
  parameters: {
    layout: "centered",
  },
  tags: ["autodocs"],
  argTypes: {},
} satisfies Meta<typeof Dragond>;
export default meta;

type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {},
};

Storybook 에 컴포넌트가 잘 나타나는지 확인하세요.

story


Rollup

번들링을 위해 Rollup과 관련 플러그인을 설치합니다.

라이브러리나 패키지를 만들때 Rollup 은 주로 선택되는 번들러입니다. Webpack이나 Parcel 등 다른 번들러 말고 Rollup 이 선택되어지는 근거는 Tree-shaking 을 효과적으로 지원한다인데, 이는 필요한 기능을 중점으로 불필요한 코드를 제거해 번들 크기를 최소화하는 기술입니다. 덤으로 ES6 모듈 시스템 기반 동작으로 코드를 모듈 단위로 분리해 생성하면서 라이브러리 개발에 유용하다는 것입니다.

yarn add -D rollup rollup-plugin-babel
yarn add -D @babel/preset-react @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-terser

추가로 typescript 를 사용하기 때문에 tsconfig 설정을 해주어야 합니다.

yarn add -D @rollup/plugin-typescript
yarn tsc --init
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noFallthroughCasesInSwitch": true,

    /* Type */
    "declaration": true,
    "declarationDir": "dist/types",

    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["**/*.stories.tsx"]
}

우리가 작성한 코드의 공백을 제거해서 파일 크기 줄이기위해 설치합니다.

rollup.config.js
import babel from 'rollup-plugin-babel'                     // babel 사용
import resolve from '@rollup/plugin-node-resolve'           // node_modules의 3rd-party 모듈을 사용하기 위해
import external from 'rollup-plugin-peer-deps-external'     // peerDependencies external 설정, 번들링 결과에서 제외
import typescript from '@rollup/plugin-typescript';         // typescript 사용
import { terser } from 'rollup-plugin-terser'               // 공백 제거
import postcss from 'rollup-plugin-postcss';                // css 적용을 위해 postcss 이용

export default [
    {
        input: './src/index.ts',
        output: [
            {
                file: 'dist/index.js',
                format: 'cjs'
            },
            {
                file: 'dist/index.es.js',
                format: 'es',
                exports: 'named'
            }
        ],
        plugins: [
            postcss({
              plugins: [],
              minimize: true,
            }),
            typescript({ tsconfig: './tsconfig.json' }),
            babel({
              exclude: 'node_modules/**',
              presets: ['@babel/preset-react']
            }),
            external(),
            resolve(),
            terser(),
        ]
    }
]

배포

배포후 사용자가 배포된 모듈을 다운받게 되면 package.json과 README 를 포함해 dist에 지정한 내용 들이 다운받게 됩니다. 때문에 package.json 을 통해 entry 내용을 설명해줍니다.

package.json
{
    //...
    "main": "dist/index.js",
    "module": "dist/index.es.js",
    "types": "dist/types/index.d.ts",
    "type": "module",
}

build 명령어를 통해 rollup 번들링과 declaration (.d.ts 타입 정의) 파일을 생성합니다.

package.json
"scripts": {
    //...
    "build": "rollup -c && tsc --emitDeclarationOnly",
},

마지막으로 NPM 에 로그인하고

npm login

배포합니다.

npm publish

재배포할때도 같은 명령어를 사용하는데요. 이때는 package.json 의 version 을 변경하여 배포합니다.

틀린 내용이 있다면 지적해 주시고,
더 좋은 방법이나 생각을 공유해주세요.

banner
  • 개요
  • 설정
  • 다운로드
  • Storybook
  • Rollup
  • 배포
Git Hooks로 테스트와 Lint 관리하기 (Husky)NPM 배포 연대기 - 2. 문서