리액트 컴포넌트 NPM 배포하기 (with TypeScript)

정현수·2022년 2월 26일
65

지식정리

목록 보기
5/5
post-thumbnail

📌 테오 구글 스프린트 4기

구글 스프린트 과정을 수요일부터 일요일까지, 하나의 간단한 프로젝트를 도출해내는 과정을 겪어봤습니다. 테오의 엄청난 진행력과 좋은 팀원들과 함께라서 잘 끝낼 수 있었던 것 같습니다.
주말이 사라지는 좋은 경험을 했고, 좋은 사람들을 알 수 있고, 결과적으로 우리가 처음에 계획했던 NPM 배포도 성공적으로 해보았으니 대만족하고 있습니다.

우리가 만든 라이브러리: react-season-component
우리가 만든 공식문서: react-season-component-web

우리가 어떤 것을 했는지는 테오가 정말 설명을 잘 해놨으니, 이 글에서는 스프린트 회고에 집중하지 않고 NPM 배포는 어떤 식으로 이루어지는지, 어떤 것을 해야하는지에 그리고 각각의 과정에서 우리는 어떤 실수를 했는지에 대해서 집중을 해서 글을 써보겠습니다.

정석적인 NPM 배포에 대한 설명 글 + 회고가 약간 섞일 것 같습니다.
NPM 배포를 하는 과정을 같이 경험하며 어떤 것을 주의해야 하는지, 어떤 것이 중요한지 같이 생각하면서 읽어주면 조금 더 재미있게 읽을 수 있을 것 같습니다.

개발 환경

React (create-react-app)
TypeScript
emotion

바로 시작해보겠습니다.

📌 npm

NPM은 무엇일까요?

npm은 Node Package Module (Node Package Manager)의 줄임말로 우리가 사용하는 라이브러리들이 이 곳에 배포가 되어있습니다.

우리는 이 곳에 배포되어 있는 npm들을 설치해서 사용을 하고 있죠.
리액트 또한 이 곳에 배포되어 있고, 하나의 명령어로 React 개발 환경설정을 다 해주는 create-react-app 또한 npm에 배포가 되어있습니다.

우리도 npm에 배포를 해서 다른 사람들이 npm install 라이브러리 명 명령어를 통해서 내 라이브러리를 설치해서 사용하게 할 수 있습니다.

📌 npm 계정 생성

NPM에 계정을 만들어봅시다.

우리의 라이브러리가 올라갈 계정을 생성해야 합니다.
NPM 공식 홈페이지에 접속해서 회원가입 버튼을 클릭해서 회원가입을 진행합니다.

📌 npm 로그인

우리가 만든 계정으로 로그인 해봅시다.

npm 계정을 생성하고, npm 웹 페이지에서 로그인을 하는 것이 아니라 로컬 터미널에 로그인을 해야합니다.

$ npm login

위 명령어를 터미널에 입력을 하게 되면 각종 정보들을 입력하게 됩니다.

Username: ID
Password: PW
Email: 회원가입 한 이메일
OTP: 이메일을 입력하고 난 다음 해당 메일에 OTP 번호를 입력하면 됩니다.

📌 프로젝트 설정

npm을 배포하기 위한 프로젝트를 생성해봅시다.

우리는 리액트 라이브러리를 배포하기 위해서는 리액트 프로젝트가 필요합니다.
리액트 프로젝트는 create-react-app을 통해서 만들어보겠습니다.

TypeScript

요즘 프로젝트들은 TypeScript로 이루어져 있습니다.
라이브러리에서 TypeScript를 지원해주지 않는다면 많은 프로젝트에서 우리의 라이브러리를 사용할 수 없습니다. 그래서 언어는 JavaScript가 아닌 TypeScript를 통해서 라이브러리를 만들어보죠.

$ npx create-react-app [프로젝트 명] --template typescript

tsconfig.json

다양한 tsconfig 컴파일러 옵션

Npm을 타입스크립트로 배포하기 위해서 몇 가지 타입스크립트 설정을 해야합니다.

1. "noEmit": false

noEmit 옵션은 출력을 내보내지 않는다는 설정입니다.
npm 배포를 하기 위해서는 우리가 만든 컴포넌트 혹은 파일이 출력으로 나와야 합니다.

2. "declaration": true

.d.ts 파일을 생성할 것인지에 대한 옵션입니다.
라이브러리가 타입스크립트를 지원하기 위해서는 해당 옵션을 true로 주어야 합니다.

3. "outDir": "./dist"

tsc 명령어로 컴파일된 파일들이 어디에 위치할 것인지에 대한 옵션입니다.
dist 폴더에 두기 위해서 위와 같이 작성합니다.

4. "include" 속성

tsc 명령어로 컴파일 할 파일이 어디에 위치하는지를 명시해주는 옵션입니다.
우리는 라이브러리를 개발할 위치를 src 폴더에서 lib 폴더를 생성해서 그 안에서 개발을 할 겁니다. 그래서 아래와 같이 작성을 합니다.

"include": ["./src/lib/**/*.tsx", "./src/lib/**/*.ts"]

이 외의 옵션들에 대해서는 선택적입니다.
위 옵션들은 라이브러리를 배포하기 위한 최소한의 설정들을 설명했습니다.

완성된 모습은 다음과 같습니다. (create-react-app typescript version 기준)

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false,
    "jsx": "react-jsx",
    "declaration": true,
    "outDir": "./dist"
  },
  "include": ["./src/lib/**/*.tsx", "./src/lib/**/*.ts"]
}

package.json

package.json docs
package.json의 여러가지 옵션들

프로젝트가 생성되었다면 package.json을 변경해야 합니다.
package.json에는 우리가 배포할 라이브러리들의 정보들을 적어주고,
우리가 배포할 파일들의 엔드포인트들을 적어줍니다.
우리가 꼭 변경해야 하는 부분은 다음과 같습니다.

1. "name"

"name": "react-hyeonsu-component"

제일 중요한 라이브러리의 이름을 적어주어야 합니다.
저는 간단하게 react-hyeonsu-component라고 작명 해보았습니다.

라이브러리 이름은 꼭 npm에 먼저 쳐보세요.

"어? 너무 완벽한 이름인데?" 라고 생각된다면 이미 존재할 확률이 높습니다.
이미 존재하는 이름이라면 이름으로 사용하지 못합니다.
이름은 우리가 만들 라이브러리를 가장 잘 표현하고 찾기 쉬운 이름을 생각해보세요.

2. "version"

"version": "0.0.1"

우리 라이브러리의 버전을 명시하는 곳입니다.
보통 major | minor | patch 순으로 적습니다.

주의할 점: 새롭게 npm에 배포할 때는 버전을 무조건 올려서 배포해야합니다.
안 그러면 버전이 겹쳐있다고 에러가 뜨게 될거에요.

3. "private"

"private": false

우리는 중앙 저장소인 npm에 배포하기 위해 private 옵션을 false로 변경해주세요.

4. "main"

"main": "dist/index.js"

우리 라이브러리의 진입점 파일을 명시해주는 곳입니다.
우리는 dist 폴더로 생성해서 배포할 것이기 때문에
dist 폴더에 있는 index.js 파일로 명시해주세요.
(typescript 파일은 tsc 명령어를 통해서 js로 변환됩니다.)

5. "types"

"types": "dist/index.d.ts"

우리는 타입스크립트를 지원하기 위해서 타입 추론을 도와주는 진입점 파일을 명시해줍니다.

주의할 점: package.json이 "files" 프로퍼티를 포함하고 있으면 "types" 프로퍼티는 무시됩니다. 이 경우 메인 선언 파일을 "files" 프로퍼티에 전달해야 합니다.

6. "browser"

"browser": "./browser/specific/main.js"

우리는 브라우저에서 구동되는 라이브러리를 제공해야 하기 때문에 browser 필드도 설정해야 합니다.

클라이언트(client-side) 사용을 위하는 경우 자바스크립트 번들러 또는 구성 요소 도구에 대한 힌트로 제공하기 위해 main 옵션(field) 대신에 사용해야 합니다.
참고

제가 main 필드도 없애보고, browser 필드도 없애보고, 둘 다 없애보고, 둘 다 넣어보고 했는데 둘 다 있어야 잘 작동합니다.

7. "scripts"

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "prepare": "rm -rf dist && mkdir dist && tsc"
  },

prepare 스크립트를 추가해놓아야 합니다.

  1. rm -rf dist 명령어로 dist폴더를 삭제하고
  2. mkdir dist 명령어로 다시 dist 폴더를 만들고
  3. 다시 만들어진 폴더에 tsc 명령어로 컴파일을 진행해서 나온 파일들을 dist에 넣는 과정입니다.

npm 배포를 하기전에 실행하는 명령어 입니다.

완성된 모습은 아래와 같습니다.
위에 적힌것들 말고는 다 똑같습니다.
정말 별 것 없지 않나요?

{
  "name": "react-hyeonsu-component",
  "version": "0.0.1",
  "private": false,
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "browser": "./browser/specific/main.js",
  "dependencies": {
    "@emotion/react": "^11.8.1",
    "@emotion/styled": "^11.8.1",
    "@types/node": "^16.11.26",
    "@types/react": "^17.0.39",
    "@types/react-dom": "^17.0.11",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "5.0.0",
    "typescript": "^4.5.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "prepare": "rm -rf dist && mkdir dist && tsc"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

추가적으로

package.json에 넣으면 좋을 내용을 적어보았습니다.

1. description

프로젝트 설명으로, 문자열로 기술합니다.
npm search로 검색된 리스트에 표시되기 때문에 사람들이 패키지를 찾아내고 이해하는 데 도움이 됩니다.

// 예시입니다
"description": "Very Very Awesome Component"

2. keywords

프로젝트를 검색할 때 참조되는 키워드입니다.
description과 마찬가지로 npm search로 검색된 리스트에 표시됩니다.

"keywords" : ["react", "component, "background", "animation"]

3. author

프로젝트 작성자 정보로, 한 사람만을 지정합니다. JSON 형식으로 name, email, url 옵션을 포함합니다.

4. repository

프로젝트의 소스 코드를 저장한 저장소의 정보입니다.
소스 코드에 참여하고자 하는 사람들에게 도움이 될 수 있습니다. 프로젝트의 홈페이지 url을 명시해서는 안 됩니다.

// 예시입니다.
"repository" : {
	"type": "git",
    "url" : "git://gitbub.com/documentcloud/test.git"
 },

5. contributors

프로젝트에 참여한 공헌자 정보로, 여러 사람을 배열로 지정할 수 있습니다.

// 예시입니다.
"contributors" : ["person", "person2"],

.npmignore

npm에 배포하지 않을 파일을 명시해줍니다.

우리는 dist 폴더를 제외하고 나머지 src 폴더, public 폴더, tsconfig과 같은 것들은 우리 라이브러리에 포함시켜 라이브러리 크기를 키울 필요가 없습니다.

라이브러리에서 사용되지 않는 폴더나 파일은 이 곳에서 명시합시다.

node_modules/
src/
public/
tsconfig.json

📌 개발 진행

설정은 끝났으니 라이브러리로 내보낼 컴포넌트를 개발해봅시다.

우리는 CSS-in-JS 라이브러리인 emotion을 설치해줍니다.

$ npm install @emotion/react @emotion/styled

라이브러리 개발 폴더는 lib으로 아까 tsconfig.json에서 설정해놓았기 때문에
lib 폴더를 생성하고, 거기에 index.tsx 파일에 우리가 최종적으로 보내고자 하는 컴포넌트를 내보내면 됩니다.

우리는 간단하게 제 이름으로 된 Hyeonsu 컴포넌트를 만들어보겠습니다.

src/lib/Hyeonsu/index.tsx

간단히 컴포넌트를 만들었고, 내보내고 있습니다.

src/lib/Hyeonsu/styled.tsx

저는 스타일 코드 파일을 따로 분리하는 것을 좋아하기 때문에 다음과 같이 분리했습니다.

src/lib/index.tsx

그리고 여러 컴포넌트를 내보낼 가능성을 생각해, index.tsx 파일을 두고 최종적으로 컴포넌트를 내보내고 있습니다.

폴더 구조는 간단히 이렇게 생겼습니다.

📦src
 ┣ 📂lib
   ┣ 📂Hyeonsu
   ┃ ┣ 📜index.tsx
   ┃ ┗ 📜styled.tsx
   ┗ 📜index.tsx

이렇게 하면 우리는 npm에 라이브러리를 내보낼 준비가 되었습니다.

📌 빌드 및 npm 배포

우리가 만든 라이브러리를 빌드하고, 배포를 진행해봅시다.

npm publish 명령어를 입력해서 배포를 하면 됩니다.

그럼 dist 폴더를 생성하고, 빌드해서 알아서 배포를 진행할거에요

그럼 정말 끝났습니다!

📌 사용해보기

잘 만들어졌나 확인해볼까요?

저는 새롭게 프로젝트를 파서 npm install react-hyeonsu-component 명령어를 통해서 설치를 해보았습니다.

잘 설치가 되네요!

컴포넌트를 export default가 아닌 export로 내보냈기 때문에 위와 같이 불러오고
한번 사용해보았습니다.

우리가 원하는대로 잘 만들어진 것을 볼 수 있습니다.

📌 오픈소스에서 중요한 것

우리는 벌써 우리만의 라이브러리를 만들었습니다.
이 라이브러리를 우리만 사용한다면 상관없지만,
다른 누군가가 내 라이브러리를 사용해보길 원한다면 사용하게끔 만드는 것 또한 우리의 몫입니다.

The State of JS 2021 | Other Tools 섹션

이번 State of JS 2021 에서 Library Evaluation Rankings, 라이브러리를 평가하는데 중요한 요소가 무엇인지에 대한 설문이 있었습니다.

1위는 공식 문서가 차지했구요, 2위는 개발자 경험, 그리고 3위로는 유저 경험이 있습니다.
그 아래로는 사용 유저 수, 커뮤니티, 만든 사람 혹은 팀 등등이 차지하고 있네요.

공식문서

우리가 라이브러리를 만들면서 제일 중요시해야하는 요소중에 하나입니다.
공식문서가 잘 되어있다면, 다른 요소들은 따라서 오는 것이라고 생각합니다.

컴포넌트 라이브러리를 만들었다면 컴포넌트의 속성을 변경시키며 사용해볼 수 있는 공식문서를 만들어보세요. 그리고 사용방법과 종류에 대해서도 설명을 잘 해놓는다면 유저가 쉽게 접근 할 수 있을거에요.

README.md

README 파일은 라이브러리의 얼굴입니다. 깃허브에서 사용자들이 라이브러리를 찾고, 맨 처음 보게되는 것이 README 파일이기 때문에 각별히 신경을 써야합니다.

우리 라이브러리의 정체성을 잘 표현하고, 사용법과 설명을 적어보세요.

유명 라이브러리들의 README를 참고해서 작성을 해보세요.

라이센스

라이센스는 ‘해야할 것’과 ‘하지 말아야할 것’에 대한 약속 입니다.
오픈소스는 모두에게 공개되어 있기 때문에 이런 약속은 더더욱 중요합니다.

Github 문서에 의하면, 라이센서가 없으면 기본 저작권법이 적용되지만, 라이센스를 선택하게 됨으로써 코드의 모든 권리는 내가 가지고 다른 사람이 내 코드를 복제, 배포, 재생산을 할 수 없으니 오픈소스 프로젝트는 오픈소스 라이센스를 선택하라고 권장하고 있습니다.

참고: Github license의 종류와 나에게 맞는 라이선스 선택하기

Contribute

오픈소스는 혼자 만들 때 보다, 같이 만들 때 그 가치가 더욱 빛납니다.

라이브러리를 만들 때 어떻게 하면 사람들이 기여하기 쉽게 구조를 짤지,
설명은 어떻게 할지, 어떻게 커뮤니티를 활성화 시킬지에 대한 고민을 해보는 것도 중요할 것 같습니다.

혼자해서 키워나가는 것 보다는, 나는 그런 구조를 만들어놓고 사람들이 자발적으로 도와주도록 만들어 놓으면 내가 들이는 시간은 줄이면서 더욱 라이브러리를 단단하게 만들 수 있습니다.

📌 Boiler Plate

Boiler Plate Repository

위의 과정들을 그대로 담은 코드들을 보일러 플레이트로 만들어놨습니다.
사용해보고 싶다면 fork 후에, clone 하시고 난 뒤,
package.json의 라이브러리 이름과 버전들을 바꾸어 가면서 npm publish 명령어로
한번 배포를 해보세요!

아, npm login 잊지 마시구요!

📌 우리가 겪었던 문제

라이브러리를 테스트할 땐 dist 폴더를 확인하면 됩니다.

만든 라이브러리를 테스트 하기 위해선 우선 생성된 dist 폴더를 테스트 해보면 됩니다.

굳이 npm에 배포하고 난 다음에 다른 프로젝트에서 라이브러리를 설치해서 테스트해보지 않아도 됩니다. dist파일이 그대로 npm에 배포되기 때문이죠. 하지만 예외는 있습니다.

수 많은 배포 이력들...

TypeScript 지원하기

많은 프로젝트에서 타입스크립트를 사용하고 있습니다. 라이브러리를 사용할 때도 type을 지원하는지, 하지 않는지는 유저의 만족도와 직결됩니다. 처음에 타입스크립트를 도입하는데 애를 먹었는데, 타입스크립트를 도입하지 않을 순 없었습니다.
왜냐하면 대부분의 팀원이 타입스크립트를 사용하고, 많은 사람들이 타입스크립트를 공부할 예정이거든요.

전처리가 필요한 SCSS, SASS와 같은 파일들은 Webpack 설정이 필요합니다.

디자인이 많이 요구되는 라이브러리라면 CRA로 프로젝트를 생성하는 것은 비추합니다.
CRA는 sass-loader가 내장되어 있기 때문에 로컬에서 테스트를 할 때는 모릅니다.
하지만 배포를 하고 나서 테스트를 해보면 sass 파일을 찾지 못한다고 에러를 뱉습니다.
sass, scss loader 같은 설정들을 webpack에 해주어야 하지만, CRA는 webpack 설정하기가 너무 힘듭니다.

📌 마무리

위의 과정들이 모든 프로젝트에 적용되지는 않습니다.

위의 과정들은 create-react-app + TypeScript + emotion를 통해 간단한 컴포넌트를 배포해보고 사용해보는 과정입니다.

위의 과정에서 CSS-in-JS가 아닌 CSS, SASS, SCSS를 사용한다면 여러가지 전처리를 해주어야 하고, create-react-app이 아닌 vite를 통해 프로젝트를 생성해주었다면 그에 맞는 설정을 해주어야 합니다. 또한 라이브러리 안에서 사용하는 다른 라이브러리들과의 의존성도 생각해야 합니다. 그리고 컴포넌트 라이브러리가 아닌 다른 편의성을 위한 라이브러리라면 여러 설정도 바꿔주어야 합니다. (node환경인지 browser인지에 따라 설정 값도 바뀌겠죠?)

누구나 할 수 있어요!

이 글의 취지는 npm 배포라는 "큰 벽" 처럼 느껴지는 과정들이 해보면 별 것 아니다 라는 것을 느끼고 여러분들께 소개해주고 싶었습니다.

자신만의 라이브러리를 가지고 싶나요? 그냥 이 글을 보고 당장 프로젝트를 생성하세요.
하고나면 정말 아무것도 아니란 걸 알게될 거에요
내보내고자 하는 폴더를 명시하고, 개발을 진행 후 명령어를 입력해 빌드 및 배포를 진행하면 됩니다.

여러분들이 자주 사용하는 함수나 컴포넌트와 같은 것들을 라이브러리로 배포해놓고 사용하는 건 어떨까요? 자신이 프로젝트를 진행할 때 가져와서 사용해도 되고, 문서만 작성 잘해놓는다면 ⭐star⭐가 올라가는 경험을 할 수도 있습니다. (가슴이 웅장해지지 않나요?)

긴 글 읽어주셔서 감사합니다.
다들 즐거운 코딩하세요~

참고

profile
개인블로그를 만들었습니다. https://junghyeonsu.com/

5개의 댓글

comment-user-thumbnail
2022년 2월 26일

와!! 엄청 자세하게 적어주셔서 너무 좋네요 :) 하고나면 생각보다 별거 아니네 하다가도 아무것도 모르는 상태에서 해보려면 엄두가 안나는 것도 맞죠 ㅋ 이 기회에 npm에도 한글로된 많은 라이브러리들이 올라왔으면 좋겠습니다 :)

답글 달기
comment-user-thumbnail
2022년 2월 26일

npm 배포하시는 분들이 보면 큰 도움이 되겠네요 ㅎㅎ 고생하셨습니다!

답글 달기
comment-user-thumbnail
2022년 2월 26일

boilerplate는 정말 유용하네요,, 감사합니다! 고생하셨습니다!ㅎㅎ

답글 달기
comment-user-thumbnail
2022년 5월 13일

잘 읽었습니다

답글 달기
comment-user-thumbnail
2023년 7월 6일

질문있습니다!
emotion에서 babel 설정도 하셨을텐데 그 경우에 package 빌드는 어떻게 하셨는지 궁금합니다!

답글 달기