5) npm에 나만의 React Package 게시하기

2021. 2. 19. 10:13PROJECT/Dkbk's website

0. Intro

React 프로젝트를 진행하면서 예상외로 심사숙고 해야하는 부분이 있었는데, 바로 오픈소스 컴포넌트 선택이었다.
단순하게는 내가 원하는 기능을 제공하고 있는지부터, 어디까지 커스터마이징할 수 있는지 / 지원은 활발히 이루어지고 있는지 / 다운로드 수가 얼마나 되는지 /  사용법이 얼마나 직관적인지 (대부분 제공하고 있는 예제 페이지를 통해 확인할 수 있다) 등등 선택에 영향을 주는 요소가 많았다.

어떤 컴포넌트는 기본적으로 상당히 많은 기능을 제공하지만 커스터마이징이 거의 불가능하다는 단점을 함께 가지고 있었고, 어떤 컴포넌트는 정말 기본 뼈대만 제공하지만 props로 내가 원하는 살을 붙일 수 있는게 장점이기도 했다.

요렇게 오픈소스 npm package에 관심을 가지던 중에, 전에 프로젝트에서 공용으로 사용하기 위한 컴포넌트를 정리할 때 재밌게 했던 기억이 오버랩돼서 한 번 내 npm package를 배포해보기로 했다 :)

 

+) 다른 많은 가이드 글을 읽어보면 알겠지만 멀쩡한 package.json만 있어도 패키지 배포가 바로 된다.
하지만 실제로 기능을 가진 npm package를 개발하고, 화면에 붙여서 테스트하고, 코어 소스만  배포하는 과정에 대한 정리글은 많이 없어서 정리해본다!

 

1. Github repository 생성

library를 npm에 배포하고 나면 사용자들이 report해준 bug를 개선하거나 신규 기능을 추가하는 작업이 활발히 이루어지는데, 이 모든 작업은 해당 library의 github에서 issue와 pull request를 통해 수행되고 있다 :)

 

간혹가다 내가 사용하는 외부 library가 원하는대로 동작하지 않아서 구글링해보면 이미 해당 issue가 report되어 pr단계에 있는 것도 볼 수 있다! (뭔가 어렸을때 개미가 개미집에 들어가는 걸 엿본 기분이 든다ㅋㅋㅋ 개미도 집이 있구나.. library도 버그 pr을 하고 있구나..😭)

 

따라서, 나도 내 library 소스를 공개할 github repository를 생성해줬다!

github.com/ga0hyeon/dkbk-kanban-board

 

ga0hyeon/dkbk-kanban-board

react kanbanboard libarary with typescript support - ga0hyeon/dkbk-kanban-board

github.com

 

2. Repository를 clone받아서 npm init 하기

위에서 생성한 repository를 clone받아서 npm init을 해준다!

+) 참고로, npm init을 하기 전에 먼저 npm adduser를 통해 npm 계정이 등록되어있어야 한다 :)

npm adduser, npm init

npm init 을 할 때 몇 가지 정보를 물어보는데, 모두 나중에 수정 가능하니 처음에는 공백이나 기본정보로 입력해도 좋다. 나는 아래와 같이 넣어줬다.

package name: (dkbk-kanban-board) 
version: (1.0.0) 0.0.1
description: react kanban board library with typescript support
entry point: (index.js) dist/index.js
test command: 
git repository: (https://github.com/ga0hyeon/dkbk-kanban-board.git) 
keywords: react kanbanboard typescript
author: gayoung hyeon
license: (ISC) MIT

 

3. library 열심히 개발하기 

3-1. 필요한 package download 하기 

npm i --save-dev webpack webpack-cli 
npm i --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
npm i --save-dev typescript ts-loader
npm i --save-dev node-sass sass-loader css-loader style-loader
npm i react @types/react react-dom @types/react-dom

3-2. webpack / babel / typescript 설정하기 🤯 

기능개발이야 하면 되지만, CRA에 익숙한 나에게는 webpack.config.js / .babelrc / tsconfig.json 이 세 설정파일의 관계를 이해하는 것부터 난관이었다..! 분명히 하나씩 이해할때는 고개를 끄덕끄덕 했던 것 같은데, 그래서 이 세 개가 왜 다 필요한데..? 라고 생각하면 띠용 하게 되는 ㅠㅠ 

일단 내 입장에서 webpack/babel/tsc가 필요한 이유는 아래와 같다.

1. 나는 tsx, ts, scss를 들고있는데, 브라우저는 js, html, css 만 알아들을 수 있다 🤨
2. 브라우저 별로 이해할 수 있는 es 버전이 다를 수도 있다 🤨  
3. 나는 파일단위로 js 모듈을 개발하고싶다 (javascript 변수의 scope를 개발자가 모두 파악하고 있을 수 없다)🤨 


그리고 실제로 내 소스에서 webpack/babel/tsc가 하는 일은 아래와 같다.

0-1. webpack에 .ts / .tsx 파일에 대해 ts-loader와 babel-loader를 사용하도록 일러줌
0-2. webpack에 .scss 파일에 대해 sass-loader, css-loader, style-loader를 사용하도록 일러줌

1-1. ts-loader가 tsconfig.json 설정을 참고하여 .ts / .tsx를  .js / .jsx로 변환
1-2. babel-loader가 지정된 preset 및 plugin 을 사용하여 .js / .jsx를 .js로 변환

2-1. sass-loader가 node-sass를 사용하여 .scss를 .css로 변환

2-2. css-loader가 @import/url()문법을 import/require()문법으로 변환

2-3. style-loader .css를 DOM에 주입

 

시행착오 끝에 설정을 마쳤다 ㅠㅠㅠㅠㅠ 감동 ㅠㅠ

//package.json
{
  "name": "dkbk-kanban-board",
  "version": "0.0.2",
  "description": "react kanban board library with typescript support",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "prepublishOnly": "rm -rf dist/ && npm run build",
    "update:major": "npm version major && npm publish",
    "update:minor": "npm version minor && npm publish",
    "update:patch": "npm version patch && npm publish"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ga0hyeon/dkbk-kanban-board.git"
  },
  "keywords": [
    "react",
    "kanbanboard",
    "typescript"
  ],
  "author": "gayoung hyeon",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/ga0hyeon/dkbk-kanban-board/issues"
  },
  "homepage": "https://github.com/ga0hyeon/dkbk-kanban-board#readme",
  "devDependencies": {
    "@babel/core": "^7.13.16",
    "@babel/preset-env": "^7.13.15",
    "@babel/preset-react": "^7.13.13",
    "@babel/preset-typescript": "^7.13.0",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.2.4",
    "node-sass": "^5.0.0",
    "sass-loader": "^11.0.1",
    "style-loader": "^2.0.0",
    "ts-loader": "^9.1.0",
    "typescript": "^4.2.4",
    "webpack": "^5.35.0",
    "webpack-cli": "^4.6.0"
  },
  "dependencies": {
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}



//tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "removeComments": true,
    "noImplicitAny": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "declaration": true,
    "outDir": "./dist/",
    "jsx": "react"
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules"]
}



//webpack.config.js
var path = require("path");

module.exports = {
  resolve: {
    extensions: [".ts", ".tsx", ".scss", ".css", ".js", ".jsx"],
  },
  entry: {
    main: ["./src/index.ts"],
  },
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "index.js",
  },
  module: {
    rules: [
      {
        test: /\.ts|\.tsx$/,
        include: path.resolve(__dirname, "./src"),
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [
                "@babel/preset-react",
                "@babel/preset-env",
                "@babel/preset-typescript",
              ],
            },
          },
          "ts-loader",
        ],
      },
      {
        test: /\.scss$/,
        include: path.resolve(__dirname, "./src"),
        use: [
          { loader: "style-loader" },
          {
            loader: "css-loader",
            options: {
              modules: true,
            },
          },
          { loader: "sass-loader" },
        ],
      },
    ],
  },
  watch: true,
};

 

+) 이 설정내용을 스무스 하게 이해하는데에 헷갈렸던 개념은 아래와 같다. (수정중)

1. babel의 plugin은 뭐고 preset은 뭔가?
plugin은 특정 js문법을 만나면 지정해둔 js문법으로 변환해준다. babel은 babel-loader설정이나 .babelrc파일에 명시해둔 plugin 목록을 보고 js to js 변환을하는데(공식 plugin 목록은 여기서 참고)
특정 상황을 위해 필요한 plugin들을 plugin 각각의 npm dependency 까지 고려하여 하나로 묶어둔 파일이 preset이다.
ES2015+ syntax를 컴파일하기 위한 @babel/preset-env
typescript 사용을 위한 @babel/preset-typescript
React 사용을 위한 @babel/preset-react
Flow 사용을 위한 @babel/preset-flow 이 공식 preset으로 제공된다 :) (21/04 현재)

신기한건 plugin이던 preset이건 내가 직접 만들 수도 있다는것 (babeljs.io/docs/en/plugins/)

2. webpack에서 지정하지 않은 파일들은 어떻게 되는건가?

 

+) 참고자료들

 

3-3. npm symbolic link를 이용해서 화면에 라이브러리 붙여서 개발하기

이것도 라이브러리 개발 플로우가 머릿속에 쏙 들어오지 않았던 원인 중 하나인데, 라이브러리를 만들어보자!고 한 뒤부터 궁금한게 있었다.

 

1. 내가 사람들에게 제공하려고 하는건 '컴포넌트'이다 (끄덕,,,)

2. 상상코딩 천재가 아닌이상 컴포넌트 사용성을 개선하려면 실제로 화면에 붙여서 동작을 확인하는게 필수이다

3. 움..? 컴포넌트를 붙여볼 화면 소스(CRA로 만든 react 플젝)와 컴포넌트 소스(위에서 지지고 볶고 만들어낸 소스)는 별개의 레포지토리에 있다. 그렇다고 화면 소스에서 컴포넌트 개발을 하고 그걸 복붙해서 컴포넌트 소스를 배포하는건 개발자 마인드로 용납할수가 없다.... 

4. 움.. 컴포넌트 소스를 npm 배포한 뒤에 화면 소스에 npm install해서 붙여봐야하는건가.....? 라이브러리 개발자들은 천재인가...? 😫

당연히 아니었다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 

npm link 명령어를 사용하면 마치 바로가기 처럼 떨어진 위치의 프로젝트 소스를 바로 참조할 수 있다 :)

//link
////1.컴포넌트 소스에서 global symlink 생성
sudo npm link
////2.화면 소스에서 symlink 참조
npm link dkbk-kanban-board

//unlink
////1.화면 소스에서 symlink 참조 제거
npm unlink --no-save dkbk-kanban-board 
npm uninstall --no-save dkbk-kanban-board
////2.컴포넌트 소스에서 global symlink 제거
npm uninstall

 

이러면 또 궁금해지는데, react 개발할 때 나를 너무나 편하게 해주는 webpack-dev-server가 symlink된 소스의 변화도 감지할 것인가..?!

1. 컴포넌트 소스에서 webpack 번들링을 하면 dist/index.js가 업데이트 됨

2. 컴포넌트 소스의 package.json에 "main" : "dist/index.js"로 지정되어있기 때문에, 화면 소스의 webpack-dev-server가 이 변화를 감지할 수 있음


아무런 설정 없이도 위와 같이 동작한다. webpack 똑똑해.... 무시해서 미안....

하지만 내가 정말 하고 싶은것은 1번 단계보다 앞서서 컴포넌트 소스의 변경사항을 알아서 캐치하여 webpack이 번들링되는 것이었다.

구글링해보니, webpack.config에서 "watch" : "true"로 지정해주면 뚝딱이었다 ㅎㅎㅎㅎㅎ

 


4.  prepublishOnly script 추가 및 npm publish 

나는 react, typescript, scss를 썼기 때문에 publish전에 js, css, html로 변환하는 작업이 선행되어야 한다.

매번 손으로 npm 빌드를 하고 publish를 한다고 생각하면 완전 끔찍 😫 하지만, 당연히 이 상황을 위한 script 명령어가 이미 있다!

 

npm이 특정 상황에서 자동으로 수행되도록 지정해둔 스크립트들이 있는데, 그 중 하나가 prepublishOnly이며 이 스크립트는 npm publish가 수행될 때 그보다 먼저 실행된다.

 

따라서, 내가 원하는 동작을 위해서는 prepublishOnly 에 build 명령어를 넣어주면 된다 :)


docs.npmjs.com/cli/v7/using-npm/scripts

 

scripts | npm Docs

How npm handles the "scripts" field

docs.npmjs.com

 

* npm publish를 했더니.. 처음에는 이런 에러가 났다😢 찾아보니 npm 가입 후 이메일 인증을 안해서 그런거였다...!

a package version that is forbidden by your security policy

근데 예전에 온 인증 이메일의 링크로 들어가도 404에러가 나서... 헤메다가 ㅋㅋ ㅋㅋㅋㅋ ㅋ npm 홈페이지에서 상단에서 발견함 ㅜㅜ;;;; 보이시나요......? 저는 왜 계속 못봤을까요

Do you need us to send it again...? YES!!!!!  /  인증 하고 publish하면 아주 잘 됨 ^^ 편-안

*  tsconfig설정 후 바로 먹히지 않을 때 이런 에러가 날 수 있다. ide를 다시 시작해주자

Cannot use JSX unless the '--jsx' flag is provided.

 

'PROJECT > Dkbk's website' 카테고리의 다른 글

스프린트 관리 도구 구상 & 개발하기  (1) 2021.04.17
Github.io에 react 빌드결과 publish 하기  (0) 2021.04.16
4) [DataGrid Library 만들기]  (0) 2021.02.18
3) Mainpage 만들기  (1) 2021.01.16
2) 레이아웃 구상  (0) 2021.01.15