개요
오랜만에 시리즈물 글을 작성하네요.
이번에는 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 은 다음과 같습니다.
{
"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 내부로 가져와서 작성했습니다.
그럼 이제 간단한 컴포넌트 하나를 만들겠습니다.
import React from "react";
const Dragond = () => {
return <>Hello Word</>;
};
export default Dragond;
지금은 컴포넌트 하나지만 이후 여러가지를 추가하게 될 때 이곳에서 정리하겠습니다.
export { default as Dragond } from "./components/Dragond/Dragond";
간단한 Story를 만들어 Storybook 에 띄우겠습니다.
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 에 컴포넌트가 잘 나타나는지 확인하세요.
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
{
"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"]
}
우리가 작성한 코드의 공백을 제거해서 파일 크기 줄이기위해 설치합니다.
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 내용을 설명해줍니다.
{
//...
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/types/index.d.ts",
"type": "module",
}
build 명령어를 통해 rollup 번들링과 declaration (.d.ts 타입 정의) 파일을 생성합니다.
"scripts": {
//...
"build": "rollup -c && tsc --emitDeclarationOnly",
},
마지막으로 NPM 에 로그인하고
npm login
배포합니다.
npm publish
재배포할때도 같은 명령어를 사용하는데요.
이때는 package.json
의 version 을 변경하여 배포합니다.
틀린 내용이 있다면 지적해 주시고,
더 좋은 방법이나 생각을 공유해주세요.