본문 바로가기

npm

npm 에 내가만든 패키지 배포하기 - (1) : React 컴포넌트와 Custom Hook 을 패키지로 만들기

지난 포스팅에 이어서 이번에는 어떻게 리액트 컴포넌트와 커스텀 훅 코드를 패키지 형태로 배포하는지 그 과정을 공유 해볼까합니다.
만약 패키지를 배포 하기위해 개발환경 세팅부터 해야한다면, 여기를 눌러주세요

 

npm 에 내가만든 패키지 배포하기 - (0) : 프로젝트 세팅 가이드

이전에 사이드 프로젝트를 진행하면서, 특정 UI 옆에 tooltip 말풍선 을 출력하는 형태의 React 컴포넌트를 개발해본적이 있습니다. 언젠가는 이걸 라이브러리화 해서 npm 에 배포해보자는 생각이 있

yoonocean.tistory.com

 

저의 경우, 배포할 소스의 entry 지점을 src 폴더로 지정했기 때문에, 패키지로 만들 원본 소스 코드를 src 폴더에서 관리합니다.
또한 타입스크립트로 개발을 하기때문에, 트랜스파일링을 거쳐 자바스크립트 파일과 타입 definition 파일이 최종적으로 npm 에 배포되어야 할것입니다.

먼저 아래와같이 폴더를 구성하고, 제가 이전에 작업했던 tooltip ui 를 노출시키는 코드를 가져왔습니다.
작업한 코드는 여기로 가시면 공개되어있습니다. 🙂

src 폴더 안의 소스코드들

이렇게 라이브러리에 포함되어야할 코드들을 모두 가져왔다면, 이제 이 코드를 내보낼 entry 파일을 하나 만듭니다.
저는 src 폴더 바로 아래에 index.ts 파일을 생성했습니다.

src 폴더와 index.ts 모듈 엔트리 지점
모듈 엔트리 index.ts

여기 index.ts 에 패키지로 배포할 커스텀 훅과 컴포넌트를 import / export 해줍니다. 이렇게 하면 보통 패키지를 다운로드 받았을시 package.json 에서 명시한 main 필드나 module 필드가 가리키는 지점이 될 파일로, 외부해서 참조해야할 몇가지 type alias 나 interface 들도 마찬가지로 export 해줬습니다.

이제 패키지 배포전용 코드를 빌드해볼 차례입니다. 그전에 먼저 위에서 말씀드린것처럼 js 파일과 type definition 파일로 컴파일링을 해주어야 합니다. 이 과정이 없다면 패키지에서 import 를 해올수가 없습니다. ts 프로젝트는 모두 tsconfig 에 따라 동작하는데, 보통 이 tsconfig 파일은 자신이 위치한 디렉터리 위치의 바깥으로 import/export 를 지원하지 못합니다.

먼저 타입스크립트 파일들을 컴파일 하기전 tsconfig.json 을 열어 트랜스파일링을 수행하지 않을 파일과 수행해야할 파일등을 확인합니다. 트랜스파일링에 포함되어야할 목록은 include 필드에, 포함하지 않을 목록은 exclude 필드에 배열로 작성합니다. 저같은 경우 아래와 같이 테스트 코드 폴더등을 설정하였습니다.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "useDefineForClassFields": true,
    "isolatedModules": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "downlevelIteration": true,
    "incremental": true,
    "useUnknownInCatchVariables": false,
    "jsx": "react-jsx",
    "types": ["vite/client", "jest", "@testing-library/jest-dom"],
    "declaration": true,
    "composite": true,
    "outDir": "lib"
  },
  "include": [
    "src",
    "**/*",
    "**/*.ts",
    "**/*.tsx",
    "src/app.tsx",
    "src/index.tsx"
  ],
  "exclude": [
    "node_modules",
    "lib",
    "src/__tests__",
    "**/*.spec.ts",
    "**/*.spec.tsx",
    "**/*.config.ts",
    "**/*.build.js",
    "jest.config.js",
    "jest.setup.ts",
    "src/temp.tsx"
  ],
  "references": [{ "path": "./tsconfig.node.json" }]
}

여기서 declaration 속성이 true 가 되어야 합니다. composite 속성이 true 가 되면 기본값으로 declaration 설정이 true 가 됩니다. declaration 이 true 일때, 트랜스파일링된 js 의 type definition 파일이 생성됩니다. composite 속성에 대한 자세한 내용은 저도 공식문서등을 확인하였지만,,,, 정확한 내용은 잘 파악이 안됐습니다. 좀더 빌드 속도가 빠르고 원본 프로젝트의 구조를 그대로 따라간다는것 정도,,,

저같은경우 tsc 가 실행될때 출력될 폴더를 lib 으로 지정했습니다. 이렇게 되면 프로젝트 루트 폴더 바로아래에 lib 폴더안에 트랜스파일링된 코드들이 들어가게 됩니다.

또한 컴파일 module 을 esnext 로 해서 배포하였더니, 넥스트 js 에서 읽어 오지 못하는 이슈가 있었는데요. 이유는 commonjs 가 아니면 import 를 해올수가 없는 문제 였습니다. next.js 내부 import 설정이 commonjs 로 되어있는거 같았습니다. 보통은 commonjs 모듈이 지원률이 높은편이고 안정적이니, 앞으로도 저는 commonjs 로 빌드를 할것같습니다.

다음은 package.json 을 열어, build 명령어에 대해 수행할 동작을 지정합니다. 이유는 정확히 모르겠으나 tsc 수행시 기존에 lib 폴더가 남아있으면 에러가 발생합니다. 아무래도 기존 코드를 지우고 다시 write 하지 못하는것 같습니다. 저같은 경우 아래와 같이 해결했습니다.

// package.json

...

  "scripts": {
    ...
    "build": "rm -rf lib && tsc",
    "prepublishOnly": "rm -rf lib && tsc",
    ...
  },
  
...

build 명령어를 입력시, 기존의 lib 폴더를 삭제시키는 명령어를 먼저 실행시키고, 이 동작이 끝나면 tsc 명령어를 실행시킵니다.
prepublishOnly 는 추후 실행할 npm publish 명령어 입력시, publish 되기전 사전에 수행할 명령을 적어 놓습니다. 저같은경우엔 한번더 build 를 진행하도록 작업하였는데, 이 과정에는 어떤것이든 들어갈수 있습니다. 예를 들면 lint 나 jest 등을 실행시켜 코드에 문제가 없는지 최종적으로 검토하는 CI/CD 과정이 들어갈수도 있을것 같습니다. 아직은 테스트 코드도 작성하지 않았고, lint 를 모두 잘 거친 코드들이 들어가 있으므로 저는 배포만 빠르게 진행하는것을 목적으로 위와 같이 해뒀습니다.

이제 아래 명령어를 입력하면, 트랜스파일링된 lib 폴더가 나타게됩니다. 패키지 매니저는 편하신것 아무거나 상관없습니다.

pnpm build

tsc 수행후 만들어진 lib 폴더

만약 여기서 쓸데없는 파일이 덕지 덕지 붙어서 트랜스파일링 되었다면, tsconfig.json 파일의 exclude 배열 목록에 추가해주어야 합니다.
index.js 에서 참조하지않거나, index.js 에서 import 해온 컴포넌트, 커스텀훅 등이 참조하지 않는 파일은 모두 포함시킬 필요가 없습니다. 불필요한 코드가 늘어나게 되면 이 패키지를 다운로드 받아서 쓰는 개발자가 불필요한 파일을 추가로 다운로드 받게 되는것입니다.

최종적으로 생성된 index.js 와 index.d.ts 파일은 아래와 같습니다.

이렇게 자바스크립트 모듈과 각 타입을 export 하는 파일이 생성됨을 확인할수 있습니다. export type 부분처럼 따로 타입 모듈을 내보지않게되면 패키지 사용자는 type 을 import 해올수 없는 문제가 있으므로 꼭 필요한 타입의 경우 내보내 주어야 합니다.

또한 리액트 프로젝트를 사용한다면 보통 es6 의 import / export 모듈 구문을 사용하니, 저는 이상태로 배포를 진행했습니다.
이제 진짜 npm publish 를 할수있는 단계가 되었습니다. package.json 에서 이 패키지를 사용하려면 어딜 참조해야하는지 명시해야합니다. 그러면 node_modules 폴더안에 있는 내 패키지를 개발자가 사용할때 바로바로 import 할수 있게 됩니다 !

// package.json

  ...
  
  "main": "lib/src/index.js",
  "module": "lib/src/index.js",
  "types": "lib/src/index.d.ts",
  
  ....

필요한 패키지에 대한 명시는 아래에 적어놨습니다. 아직 peerDependencies 의 명확한 목적은 이해가 되지 않았지만, 아마도 필요한 종속성 패키지가 무엇이 있는지, 최소 버전은 몇인지 등을 적어두는 곳 같습니다. 저는 아래와 같이 표기했습니다

// package.json

...

  "peerDependencies": {
    "next": "^12.0.0",
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "styled-components": "^5.3.9"
  }
  
...

 

마지막으로, 이상태로 바로 npm publish 를 하게되면 프로젝트에 포함된 온갖 설정 파일들도 같이 업로드 되버리는 문제가 있습니다. 그래서 패키지 배포후에 확인해보니 마치 시장바닥을 차려놓은 듯한 라이브러리가 되어있었습니다,,,,, 모두 패키지 사용하는데는 필요가 없으므로 무시하도록 .npmignore 파일에 명시해줍니다. 패키지를 배포할땐 번들 크기 최적화를 하는것이 아주 중요합니다. 저는 아래와 같이 정리하였습니다.

// .npmignore

# folders

node_modules
src
.husky

# configs

.eslintrc.json
.gitignore
.swcrc
tsconfig.json
tsconfig.node.json
vite.config.ts
jest.config.js
jest.setup.ts
cypress.config.ts

# local application

index.tsx
app.tsx

마찬가지로 .gitignore 에도 굳이 커밋하지 않아도 될 파일들을 정리해줍니다. 보통 빌드 혹은 컴파일된 파일은 커밋하지 않으므로, lib 폴더등도 무시하도록 햇습니다.

// .gitignore

# folders

unused
node_modules
lib

# local application

temp.tsx
index.tsx
app.tsx

 

여기까지 이상없이 진행했다면, 이제 npm 에 login 을 해야합니다.
npm 계정이 없다면 가급적 npm 홈페이지에 가서 계정을 등록하고, 다시 진행합니다.

npm login

// 계정정보 입력

로그인이 완료 되었다면, npm publish 를 입력하면 npm 에 나의 패키지가 배포되게 됩니다 !

npm publish

완료 문구가 뜨면, 알림 메일도 같이 받게 됩니다. 개발하면서 감회가 새로운 순간 이었습니다 ㅎㅎ
npm 에 가서 내 패키지명으로 검색을 하게되면, 바로 등록이 되어있는것을 확인할수 있습니다.

저같은 경우, package.json 에 깃헙 주소등을 입력하니, 오른쪽에 저렇게 레포지토리 주소도 나타났습니다.
html 파일로 따지면 meta 태그와 유사한 역할을 하는 느낌이 들었습니다.
이미지 처럼 사용법도 README.md 에 기재하여 배포하였습니다. 패키지에 README.md 파일을 포함시켜 배포하게되면 저렇게 npm 패키지 페이지에서 README.md 파일을 보여줍니다.

이제 마지막으로 패키지를 다운받아 테스트를 해볼 차례입니다. 저의 경우 next.js 환경과 react 환경 모두 문제없이 지원되는 컴포넌트와 커스텀 훅을 제공할 예정이었으니, 둘다 확인 해봤습니다

제가 진행중인 사이드 프로젝트에서 이 툴팁 UI 를 처음 만들기 시작해서, 저희 프로젝트에 붙여서 확인 해보았습니다 ㅎㅎ
이렇게 패키지가 되어 배포가 되서 더욱 감회가 새롭게 느껴졌습니다.
번외로 앞으로도 이 React-BubblyTip 은 계속 업데이트를 해나갈 예정이니, 생각나면 한번 사용해봐주시면 감사하겠습니다 🙏🏻
issue 나 pull request 도 환영입니다 !
나중에 이 React-BubblyTip 의 개발 과정이나 경험등을 기록해보도록 하겠습니다.

 

react-bubblytip

You can easily display a tooltip bubble UI on components in your React project.. Latest version: 0.0.6, last published: 8 minutes ago. Start using react-bubblytip in your project by running `npm i react-bubblytip`. There are no other projects in the npm re

www.npmjs.com

다음 포스트에서는 package.json 에 추가로 입력하면 좋을 정보들과, github action 을 사용하여 npm registry 로 ci/cd 자동화를 하는 과정을 작성해보겠습니다.