[npm] 내가 만든 라이브러리를 배포해보자(npm publish)
내가 직접 만든 컴포넌트를 디자인시스템처럼 다른 프로젝트에서 다운받아 사용할 수 있도록 npm에 배포해보자.
1. vite + react + typescript로 프로젝트 만들기
npm create vite@latest
2. 불필요한 파일 삭제 (배포될 라이브러리 자체를 그려주는 화면은 필요하지 않기 때문.. 컴포넌트들만 개별로 export 해서 사용할 것임)
index.html
App.css
App.tsx
index.css
main.tsx
/assets 폴더
/public 폴더
1-1. (선택) npm > pnpm 마이그레이션
https://thefirstperson.tistory.com/262
2. package.json 파일에 배포 관련 정보 추가 및 devDependencies 추가 설치
// package.json
{
"name": "dayoung-publish-test",
"version": "0.0.1",
"files": ["dist"], // 패키지 배포 시 포함할 파일(=dist 폴더만 포함하겠다)
"description": "", // 패키지 설명 (npm 검색결과에 반영됨)
"main": "dist/index.es.js", // 패키지 진입점
"types": "dist/index.d.ts", // 패키지의 타입 정의파일 경로
"author": "Seoullabs", // 저자 정보
"license": "ISC", // 라이센스 정보(사용자가에게 어떤 권한과 제한사항이 있는지)
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"vite": "^5.2.0",
// lint, prettier 관련
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.34.1",
"prettier": "^3.2.5",
// config 설정 관련
"path": "^0.12.7",
"vite-plugin-css-injected-by-js": "^3.5.1",
"vite-plugin-dts": "^3.9.1",
"vite-tsconfig-paths": "^4.3.2"
}
}
참고 <오픈소스 라이선스 종류>
- MIT - 가장 흔히 사용됨 / 수정, 재배포 자유 / 비공식적이고 간단한 조건으로 공개되기를 원할 때 사용
- ISC - MIT와 같은 권한 / 그러나 좀 더 간결한 텍스트, 필요한 내용만을 담음
- GNU - 가장 강력함 / 해당 오픈소스를 가져다썼을 때 그 내용을 포함 시켜야하고, 소스코드를 공개해야 함
- Beerware - 뭐가 이리 복잡하지? 난 얼레벌레 돌아가는 걸 만들었을 뿐인데… 만나면 맥주나 한 캔 사줘
3. export해서 쓸 컴포넌트 및 폴더 생성
이제 버튼 컴포넌트를 간단하게 만들고, 같은 폴더 안에 index.ts 파일을 만들어 내보내주자.
// src/components/Button/Button.tsx
import { ButtonProps } from './Button.types';
const Button = ({ color, children }: ButtonProps) => {
return (
<button
type="button"
style={{ backgroundColor: color }}
onClick={() => console.log('click')}
>
{children}
</button>
);
};
export default Button;
// src/components/Button/Button.types.ts
export type ButtonProps = {
color: 'red' | 'blue';
children?: React.ReactNode | React.ReactNode[];
};
// src/components/Button/index.ts
export { default as Button } from './Button';
export * from './Button.types';
Button 컴포넌트가 들어있는 Button 폴더도 index.ts 파일을 만들어 내보내주고,
// src/components/index.ts
export * from './Button';
마지막으로 components 폴더 자체도 내보내준다.
// src/index.ts
export * from './components';
일단 여기까지 src 폴더의 파일구조를 살펴보면 다음과 같다.
📦src
┣ 📂components
┃ ┣ 📂Button
┃ ┃ ┣ 📜Button.tsx
┃ ┃ ┣ 📜Button.types.ts
┃ ┃ ┗ 📜index.ts
┃ ┗ 📜index.ts
┣ 📜index.ts
┗ 📜vite-env.d.ts
왜 여러 파일들을 index.ts에 모아서 re-export 해줄까?
- 특정 디렉토리 내의 여러 모듈을 하나의 파일에서 re-export 해주는 파일을 barrel 파일이라고 함
- 외부 파일에서 간결하게 import해서 사용할 수 있음
//barrel 적용 전
import { ModuleA } from 'utils/ModduleA';
import { ModuleB } from 'utils/ModduleB';
// barrel 적용 후
import { ModuleA, ModuleB, ModuleC } from 'utils';
우리는 이렇게 만든 Button Component를 외부 프로젝트에서 가져다 쓸 거다.
여기까지 하고 pnpm run build 때려보면 빌드 에러난다.
(왜냐면 아까 index.html 파일을 지워버렸기 때무네...)
4. vite.config.ts 파일에서 빌드 진입지점 및 다른 빌드옵션들을 바꿔준다.
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import dts from 'vite-plugin-dts';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import viteTsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
cssInjectedByJsPlugin({ topExecutionPriority: false }),
react(),
dts({
insertTypesEntry: true,
tsconfigPath: 'tsconfig.node.json',
}),
viteTsconfigPaths(),
],
build: {
lib: {
entry: path.resolve(__dirname, './src'),
name: '프로젝트이름',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: ['react', 'react-dom', 'styled-components'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'styled-components': 'styled',
},
banner: '"use client";',
interop: 'compat',
},
},
commonjsOptions: {
esmExternals: ['react'],
},
emptyOutDir: false,
},
});
일단 여기까지하면 빌드 에러는 해결되지만... 배포를 위해서는 좀 더 설정해줄 것들이 있다.
5. package.json 파일에서 build 스크립트 수정
// package.json
...
"build": "rm -rf dist && tsc -p ./tsconfig.node.json && vite build",
- 빌드 결과물은 위에서 설정해준대로 dist 폴더에 생성되는데, 덮어씌워지며 뭔가 꼬이는 현상을 막기 위해 일단 rm -rf 명령어로 한번 지워줌
- tsconfig.node.json 파일 안의 내용을 참고하여 빌드한다
6. 그러면 이제 아래 파일들을 수정해주러 가보자.
tsconfig.json
tsconfig.node.json
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"useDefineForClassFields": true,
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vite/client"],
"module": "ESNext",
"skipLibCheck": true,
"baseUrl": ".",
/* Bundler mode */
"moduleResolution": "node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"rootDir": "src",
"typeRoots": ["node_modules/@types", "@types", "node_modules"],
"noEmit": true, // 자동js 생성막기
"noEmitOnError": true, // ts파일 에러 시 js파일 생성X
"allowSyntheticDefaultImports": true // import cryptoJS
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"],
"declaration": true
}
// tsconfig.node.json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"allowSyntheticDefaultImports": true,
/* Bundler mode */
"moduleResolution": "node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"rootDir": "src",
"typeRoots": ["node_modules/@types", "@types", "node_modules"],
"noEmit": true, // 자동js 생성막기 (이거 안해주면 빌드했을 때 같은이름의 js파일 막 생기고 난리남...)
"noEmitOnError": true // ts파일 에러 시 js파일 생성X
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"], // 타입스크립트 컴파일러가 제외할 파일 또는 디렉터리
"outDir": "dist" // 빌드 결과물을 담을 디렉터리
}
7. 여기까지 설정을 마쳤으면, pnpm run build로 빌드 한 번 해준 뒤 npm publish를 입력해서 배포해줌
(그 전에 npm 사이트에 로그인 해두어야 함)
8. 외부 프로젝트에서 잘 불러와서 사용할 수 있는지 테스트
// 다른 프로젝트 (pnpm i dayoung-publish-test로 패키지 설치해주고)
import { Button } from 'dayoung-publish-test';
...
<Button color="red">테스트버튼</Button>
(참고용) 외부프로젝트에서 pnpm i로 설치해준 뒤, node_modules 폴더를 까보면 아래와 같은 구조로 다운받아져있다.
(참고용) prettierrc와 npmignore 파일도 추가적으로 설정해준다.
// .npmignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.yarn
storybook-static
package.tgz
.storybook
// .prettierrc
{
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"printWidth": 80
}
(참고)
빌드 결과물인 dist 폴더에 index.d.ts 파일이 없다면
vite.config.ts 파일에서
plugins: [
…
dts({
insertTypesEntry: true,
tsconfigPath: 'tsconfig.node.json',
}),
],
여기 dts 부분을 다시 한 번 체크한다.
(참고)
- npm 사이트에 로그인 해두어야 함 👉 해당 계정으로 배포됨
- git repository에 올라간 것 기준 X 👉 해당 시점의 build 결과물 기준으로 배포됨
- 빌드 결과물에 수정사항이 반영되지 않는다 👉 빌드 시 caching 적용되어서일 수 있으므로, npm cache clean 또는 pnpm cache prune 실행
- 수정본 배포 후 외부프로젝트에서 pnpm i를 다시 했는데 수정사항이 반영되지 않았다 👉 패키지 설치 시 caching 이슈일수 있으므로 node_modules 삭제 후 pnpm store prune
'한 걸음 > etc' 카테고리의 다른 글
npm과 pnpm (0) | 2024.03.06 |
---|---|
mac 개발환경 기본설정 (0) | 2024.03.04 |
아르코 인공지능퍼포먼스 특강 (0) | 2023.06.24 |
쿠키 vs 로컬스토리지 vs 세션스토리지 (0) | 2023.03.03 |
윈도우 cp949 문제로 pip install 안될 때 해결 (feat. kappa, zappa) (0) | 2022.10.05 |