본문 바로가기

프로그래밍 - 활용/Front-end

NPM에 라이브러리 배포하기

🏄 배포된 surff 라이브러리: https://www.npmjs.com/package/surff

0. 배포하게 된 배경

프로젝트를 진행하면서 이전에 사용했던 컴포넌트나 훅을 반복적으로 불러와 사용하는 경우가 많았다. 이러한 과정을 통해, 이전 프로젝트의 레포지토리를 분석하고 필요한 요소를 추출하는 일이 잦아졌다. 이러한 노력을 반복하다 보니 공통적으로 사용되는 요소들을 모아두고 보다 빠르고 쉽게 활용하고 싶다는 생각이 들었다.
이런 아이디어를 바탕으로 깃 레포지토리를 활용하여 아카이빙을 시도하게 되었다. 스스로가 컴포넌트와 커스텀 훅을 재사용하고자 하는 목적도 있었지만, 함께 프로젝트를 하는 팀원들에게도 디자인 시스템을 공유하며 효율적인 개발 사이클을 구축하고자 하는 바람이 있었다.

공유할 방법에 대해 고민하다, import가 자유롭고 상세한 문서가 제공되는 NPM 라이브러리를 도입을 떠올리게 되었다.

오픈소스 생태계에 기여할 수 있는 기회라는 생각이 들어, 주저 없이 재사용이 높은 컴포넌트와 커스텀 훅을 모아둔 디자인 시스템을 NPM에 라이브러리로 배포하는 도전을 시작하게 되었다.

 

배포할 라이브러리는 Vite 환경에서 pnpm, styled-component을 활용해 개발했다.

rollup과 webpack을 고민했으나, legacy가 된 CRA보다 Vite로 개발하는 것이 DX(개발자 경험)에 도움이 될 것이라 판단을 했다. Vite도 내부에서 빌드할 때 esbuild와 rollup을 사용하기에 문제가 없으리라 생각했다. 

css 스타일링 도구는 지극히 필자의 선호도에 맞추어 styled-components로 결정하게 되었다. 👍

 

1. Vite+pnpm으로 프로젝트 생성하기

pnpm create vite

 

2. 프로젝트에 원하는 라이브러리 형태 제작하기

(1) 불필요한 파일 삭제하기

  • public/, src/assets/
  • src/App.tsx, src/App.css, src/index.css

 

(2) 라이브러리 폴더명 변경: src > packages

vite로 프로젝트를 생성하면, 프로젝트를 실행하는 코드가 src 폴더 내부에 생성되게 된다. 라이브러리로 배포하고, 이를 지속적으로 관리하기 위해선 src보다 직관적인 packages로 변경하는 것이 좋겠다는 판단을 했다. packages 내부에 리액트 디자인 시스템과 관련된 코드를 배치할 계획이다.

 

(3) 컴포넌트 생성하기

커스텀 훅과 컴포넌트를 배포할 예정이기에, packages 내부에 components와 hooks 폴더를 생성했다.

main.tsx의 경우, 로컬 환경에서 올바르게 동작하는지 확인하기 위하여 삭제하지 않고 유지했다.

packages/
├── components/
│   └── Skeleton/
│       ├── Skeleton.tsx
│       ├── Skeleton.styles.ts
│       └── Skeleton.d.ts
├── hooks/
└── index.tsx
└── main.tsx
└── vite-env.d.ts

 

(4) 배포에서 사용하도록 export 설정: index.tsx

사용자가 라이브러리를 사용하기 위해선, 개발한 라이브러리를 export 하는 부분이 필수적이다. 이 부분을 생략한다면 배포를 한 후, 라이브러리가 import를 하지 못하는 문제가 발생하니 유의해야 한다.

export { default as Skeleton } from "./components/Skeleton/Skeleton";
export { default as Modal } from "./components/Modal/Modal";
export { default as usePortal } from "./hooks/usePortal";

 

3. package.json 설정하기

(1) 빌드 환경 설정하기

{
  "type": "module",
  "main": "dist/index.umd.cjs",
  "module": "dist/index.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.umd.cjs"
    }
  },
  "files": [
    "dist"
  ],
}

 

(2) dependencies 환경 설정하기

dependencies와 peerDependencies, devDependencies의 설정은 라이브러리 배포에서 중요한 부분이다. 해당 요소를 어디에 배치하느냐에 따라, 패키지의 크기가 달라지고 사용자의 환경이 변하게 되기 때문이다.

 

라이브러리 배포에서의 dependencies의 기준은 다음과 같이 설계했다. 자세한 비교는 package.json 분석기: dependencies VS peerDependencies VS devDependencies를 참고하면 된다.

  • dependencies: 사용자가 라이브러리를 설치할 때, 자동으로 설치되는 요소
  • peerDependencies: 사용자의 dependencies 또는 devDependencies에 해당 요소가 설치되어 있어야 한다고 알리는 요소
  • devDependencies: 라이브러리 개발자를 위한 요소(빌드, 배포에 포함이 되지 않는 요소)

 

surff 라이브러리에선 다음과 같이 dependencies 요소를 배치했다.

  • dependencies: X(사용자 환경에서 추가적으로 설치해야 할 요소 없다고 판단)
  • peerDependencies: react, react-dom, styled-components
  • devDependencies
    • react, react-dom, styled-components: 라이브러리 개발 환경에서 확인 용도
    • vite, typescript, eslint, storybook: 라이브러리 개발 환경에서 개발 도구

dependencies에 있는 요소를 peerDependencies, devDependencies로 옮기자 패키지의 사이즈가 130KB에서 42.7KB로 감소되는 효과를 얻었다. 😀👍

{
  "dependencies": {},
  "peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0",
    "styled-components": ">=5.0.0"
  },
  "devDependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "styled-components": "^6.1.14",
    "@chromatic-com/storybook": "3.2.3",
    "@eslint/js": "^9.17.0",
    "@storybook/addon-essentials": "8.4.7",
    "@storybook/addon-interactions": "8.4.7",
    "@storybook/addon-onboarding": "8.4.7",
    "@storybook/blocks": "8.4.7",
    "@storybook/react": "8.4.7",
    "@storybook/react-vite": "8.4.7",
    "@storybook/test": "8.4.7",
    "@types/node": "^22.10.5",
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.17.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.16",
    "eslint-plugin-storybook": "^0.11.2",
    "globals": "^15.14.0",
    "path": "^0.12.7",
    "storybook": "8.4.7",
    "typescript": "~5.6.2",
    "typescript-eslint": "^8.18.2",
    "vite": "^6.0.5",
    "vite-plugin-dts": "^4.5.0"
  },
  "eslintConfig": {
    "extends": [
      "plugin:storybook/recommended"
    ]
  }
}

 

(3) 배포 환경 설정하기

NPM 페이지에 기재될 내용을 담당하는 부분이다.

  • private: false
  • version: 0.0.0

private NPM은 유료 전용이기에, private은 필수적으로 false로 설정을 해야 한다. 💸

version의 경우, 배포할 때마다 버전을 높여야 한다. 처음 라이브러리를 배포하는 경우, 0.0.0으로 설정하면 된다.

{
  "name": "surff",
  "description": "It's a React UI library. Let's surff!",
  "license": "MIT",
  "private": false,
  "version": "0.0.6",
  "keywords": [
    "react",
    "ui",
    "library",
    "components",
    "skeleton"
  ],
  "author": {
    "name": "Kim minjeong",
    "email": "dotoriido@gmail.com",
    "url": "https://github.com/minjeongss"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/minjeongss/surff"
  },
  "bugs": {
    "email": "dotoriido@gmail.com"
  },
}

 

4. vite.config.ts 설정하기

(1) build: lib

entry는 빌드를 적용할 위치이기에, packages/index.tsx로 설정해야 한다.

 

(2) build: rollupOptions, build: commonjsOptions

빌드 환경에서 react, react-dom, styled-components를 외부 라이브러리라고 표기해야 한다. dependencies에 배치하지 않고, peerDependencies에만 배치했기에 사용자 환경의 라이브러리를 사용해야 하기 때문이다.

  • rollupOptions: external, globals로 외부 라이브러리 표기
  • commonjsOptions: esmExternals로 외부 라이브러리 표기

 

(3) plugins

타입스크립트 관련 설정 파일은 tsconfig.json을 참고하게 된다. vite가 버전을 업데이트하며, tsconfig.json가 다양하게 변화하게 되었다.

  • 과거: tsconfig.json
  • 과거: tsconfig.json, tsconfig.node.json
  • 현재: tsconfig.json, tsconfig.app.json, tsconfig.node.json

그렇기에 plugins에 tsconfigPath를 tsconfig.app.json이라고 명시하는 작업이 필요하다.

 

(4) 예시

import { defineConfig } from "vite";
import path from "path";
import dts from "vite-plugin-dts";

// https://vite.dev/config/
export default defineConfig({
  build: {
    lib: {
      entry: path.resolve(__dirname, "packages/index.tsx"),
      name: "surff",
      fileName: "index",
    },
    rollupOptions: {
      external: ["react", "react-dom", "styled-components"],
      output: {
        globals: {
          react: "React",
          "react-dom": "ReactDOM",
          "styled-components": "styled",
        },
      },
    },
    commonjsOptions: {
      esmExternals: ["react", "react-dom", "styled-components"],
    },
  },
  plugins: [dts({ tsconfigPath: "./tsconfig.app.json" })],
});

 

5. 빌드하기

(1) 명령어 입력하기

pnpm build

 

(2) dist/ 내부 구조 확인하기

dist/
├── components/
│   └── Skeleton/
│       ├── Skeleton.d.ts
│       └── Skeleton.styles.d.ts
├── index.d.ts
├── index.js
└── index.umd.cjs

 

6. NPM 연동하기

(1) 로그인 명령어 입력하기

npm login

 

(2) 로그인된 정보 확인하기

npm whoami //minjeongss

 

7. NPM 배포하기

(1) NPM에 존재하는 라이브러리인지 확인하기

npm info (원하는 라이브러리 이름)

 

(2) NPM에 배포하기

npm publish

 

8. NPM에서 배포된 라이브러리 확인하기

성공적으로 라이브러리가 배포된 것을 확인할 수 있다. 😆👍

 

surff

It's a React UI library. Let's surff!. Latest version: 0.0.6, last published: 2 days ago. Start using surff in your project by running `npm i surff`. There are no other projects in the npm registry using surff.

www.npmjs.com

 

  • NPM 홈페이지에서 라이브러리 검색하기