[npm] 내가 만든 라이브러리를 배포해보자(npm publish)

2024. 5. 27. 19:14
반응형

내가 직접 만든 컴포넌트를 디자인시스템처럼 다른 프로젝트에서 다운받아 사용할 수 있도록 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

 

npm > pnpm으로 마이그레이션 하기

1. pnpm 설치npm i -g pnpm  2. 기존의 node_modules 폴더 삭제npx npkill* npkill은 node_modules 폴더를 쉽게 찾고 제거할 수 있는 경량 NPM 패키지   3. pnpm 만 사용 가능하도록 package.json 수정"scripts": { "preinstall":

thefirstperson.tistory.com

 

 

 

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>

 

설정해둔 type도 잘 나와주고요
버튼 렌더링도 잘 되네요^ㅅ^

 

 

 

 

(참고용) 외부프로젝트에서 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

 

 

반응형

BELATED ARTICLES

more