Projects

[UI 컴포넌트 라이브러리] 2. 라이브러리 배포하고 사용하기 (NPM, Github Packages, Docker)

Mitchell 2023. 9. 2. 11:07
 

[UI 컴포넌트 라이브러리] 1. Typescript로 React 컴포넌트 만들고 Rollup으로 빌드하기

💡 스토리북의 디자인시스템 튜토리얼을 진행하고 오시는 것을 추천합니다. 튜토리얼을 진행하고 오셨다면 예제로 사용할 소스코드가 있을 것입니다. 해당 코드들 위에서 아주 간단한 Input 컴

mitchellglobal.tistory.com

이번에는 1편에 이어서 빌드한 소스를 만들어둔 소스를 필요한 곳에서 사용할 수 있도록 NPM registry에 배포하고 프로젝트에서 불러와 사용하는 방법을 공유하겠습니다.  전체적으로 다음과 같은 내용을 학습하게 되겠습니다.

  • 오픈소스로 라이브러리 배포하기
  • Private으로 라이브러리 배포하기
  • Github Action으로 배포 자동화하기
  • Docker 환경에서 Private 라이브러리 사용하기

라이브러리의 소유권, 제작목적 등에 따라 어디를 통해서 배포할 것이고 관리되는지 달라지는데요. 이 글에서도 두가지 방식을 나눠서 자세히 다뤄보겠습니다. 

 

오픈소스로 공개하고 싶어요!

- 배포: NPM에서 Public 배포를 진행합니다.

- 설치: 다른 오픈소스와 동일합니다. npm install 하세요.

 

회사내부에서만 사용하고 싶어요!

- 배포: Github Package에서 Private 배포를 진행합니다.

- 설치: 인증절차를 거쳐 npm install 하세요.

특히 Private 배포시 인증절차와 함께 Docker 환경에서 라이브러리를 사용할 수 있도록 Dockerfile, docker-compose 파일도 셋팅해보겠습니다.

 

💡 스토리북 디자인시스템 튜토리얼의 배포 부분을 참고해주세요.

** 튜토리얼에 나온 auto를 사용한 버전자동화 부분은 추후 다른 포스트를 통해 좀 더 자세히 알아보도록 할게요.


NPM에 오픈소스로 배포하기

NPM registry에 Public으로 배포하면 사용하고 싶은 누구든 npm install만 하면 권한없이 사용 할 수 있습니다. 여기에서는 Github Action으로 오픈소스 배포자동화하는 사례를 공유합니다.

 

1. NPM 가입하기

라이브러리를 배포하려면 배포할 계정과 권한인증을 위한 토큰이 필요합니다. 먼저 NPM에서 가입을 진행하세요.

 

npm

Bring the best of open source to you, your team, and your company Relied upon by more than 17 million developers worldwide, npm is committed to making JavaScript development elegant, productive, and safe. The free npm Registry has become the center of Java

www.npmjs.com

 

 

2. package.json 메타데이터 입력하기

NPM Registry에 등록하기 위해 프로젝트의 package.json 메타데이터를 입력하겠습니다. 프로젝트 폴더에 package.json이 없다면 < "npm init"으로 시작하거나 CLI 툴로 프로젝트를 시작하세요. 저희는 Storybook의 튜토리얼을 따라가고 있으니 package.json으로 바로 가보겠습니다.

 

package.json

{
    "name": "PACKAGE_NAME",
    "version": "0.0.1",
    "private": false,
    "description": "UI component library",
    "repository": "YOUR_GITHUB_REPOSITORY",
    "author": "YOUR_NAME"
    "main": "dist/cjs/index.js",
    "module": "dist/esm/index.js",
    "types": "dist/esm/@types/index.d.ts",
    
    ...
}

"name": 원하는 패키지 이름을 작성하면 됩니다. "npm info <패키지명>"으로 중복되진 않았는지 체크해주세요.

"private": 오픈소스로 배포하기 때문에 false.

"repository": 깃허브 레포지토리 주소를 명시하면 사용자가 레포지토리 주소를 찾기 쉬워진다.

 

"main": cjs(common.js)에서 사용가능한 패키지의 진입점을 나타낸다.

"module": esm(ECMAScript module)에서 사용가능한 패키지의 진입점을 나타낸다.

- 위 두 입력값은 rollup.config.mjs에 사용되어 입력된 경로에 output이 생성되도록 설정되었었다.

import { createRequire } from 'node:module'
const requireFile = createRequire(import.meta.url)
const packageJson = requireFile('./package.json')

export default [
    {
        input: 'src/index.ts',
        output: [
            {
                file: packageJson.main,
                format: 'cjs',
                sourcemap: true,
            },
            {
                file: packageJson.module,
                format: 'esm',
                exports: 'named',
                sourcemap: true,
            },
        ],
        
  ...

 

"types": index.d.ts 파일의 경로를 입력합니다. 여기에 파일경로를 입력해주어야 사용하는 곳에서 타입들을 참조할 수 있습니다. 그런데 저희는 cjs와 esm에서 각각의 index.d.ts 파일을 가지고 있는데요. 같은 소스에 대한 타입들이기 때문에 한쪽의 경로만 입력해줘도 충분합니다. 저희는 tsconfig설정에 해둔대로 "dist/esm/@types/index.d.ts"로 설정하였습니다.

** 만약 두 cjs와 esm에서의 타입들이 다르다면 "exports"필드를 추가하여 경로를 각각 추가하세요.

 

3. Github Action으로 배포 자동화하기

push.yml

name: Release

# main브랜치로 push하면 워크플로를 실행합니다.
on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
    steps:
      - uses: actions/checkout@v2

      - name: Prepare repository
        run: git fetch --unshallow --tags
      - name: Use Node.js 16.x
        uses: actions/setup-node@v3
        with:
          node-version: 16
      - name: Install dependencies
        uses: bahmutov/npm-install@v1
      - name: Create Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: |
          yarn release

스토리북 튜토리얼을 진행하면 작성하게 되는 Github Action workflow 스크립트입니다. "-name: Create Release" step에서 실행하는 명령어가 소스코드를 NPM registry에 배포시킵니다.

주요하게 살펴볼 부분은 바로 "env"내부에 작성되어 있는 TOKEN들인데요. ${{ secrets.[토큰명] }}으로 접근하여 토큰 값을 얻어오고 배포시 사용하게 됩니다. 즉 secrets안에 토큰이 있어야 정상적으로 릴리즈 할 수 있다는 말이겠네요.

 

secrets라는 저장소는 어디인지 그리고 어떤 토큰을 저장해야하는지 살펴보겠습니다.

 

3-1) 토큰저장하기

프로젝트의 Github Repository에서 Settings 탭으로 이동하면 왼쪽 메뉴들 중 Security의 Secrets and variables - Actions가 있습니다. 메뉴를 눌러서 페이지로 이동하겠습니다.

Secrets and variables - Actions

이 페이지의 제목이 말해주듯 Actions에서 사용할 secrets와 variables를 설정할 수 있습니다. "New repository secret" 클릭하고 secrets이름과 토큰을 넣어주면 push.yml의 ${{ secrets.[토큰명] }}을 통해 값을 전달 할 수 있게 됩니다.

 

3-2) 토큰생성하기

push.yml에서 요구하는 토큰은 GITHUB_TOKEN, NPM_TOKEN입니다. 각각 어디에서 어떻게 생성하는지 알아보겠습니다.

 

GITHUB_TOKEN

사실 별도로 설정하지 않아도 자동으로 주어집니다. 하지만 토큰에 특정한 목적으로 권한을 주고 해당 토큰으로 사용하고 싶다면 아래와 같이 사용할 수 있습니다.

  1. settings - Developer settings - Personal access tokens(PAT)에서 생성
  2. Secrets and variables - Actions 페이지에 해당 PAT 저장하기 (GITHUB가 포함된 이름이 아니여야 합니다!)
  3. push.yml에서 GITHUB_TOKEN: ${{ secrets.[토큰명] }}으로 바꿔주기

 

NPM_TOKEN

다시 NPM 페이지로 돌아가서 Access Tokens 페이지로 이동합니다.

NPM - Access Tokens 페이지

Generate new token 버튼을 클릭하면 생성페이지로 이동합니다.

NPM Token 생성

Github Action을 통한 CI/CD workflow를 통해 배포하기 때문에 여기에서 "Automation"을 선택하여 토큰을 생성한다. 생성이 완료되면 토큰값을 딱 한번만 알려주는데 잘 복사해서 위에서 설명한 secrets에 저장하겠습니다.

 

이제 모든 설정은 끝났습니다.

Storybook에서 작성된 workflow에 의하면(push.yml) main 브랜치로 push될 때 자동으로 배포를 시작하게 됩니다. 이제까지의 변경사항을 git push하면 Github Actions를 통해 배포가 진행됩니다.

Actions

 

워크플로가 완료되면 NPM에서 배포된 라이브러리를 확인 할 수 있습니다.

 

오픈소스로 배포한 라이브러리의 설치방법은 다른 라이브러리와 다르지 않습니다.

간단히 아래 명령어를 실행하고 작업해보세요!

npm i YOUR_PACKAGE_NAME

Github package에 Private 배포하기

회사내부에서만 사용해야하거나 권한이 있는 사용자만 사용할 수 있게 하려면 Private 배포를 해야합니다. NPM에서도 Private 배포할 수 있는 방법을 제공하지만 유료 솔루션이기 때문에 저희의 선택이 될 수 없습니다. Github Packages에서는 무료로 Private 배포를 제공하니 이 방법을 통해 배포해보겠습니다.

 

전제적인 흐름은 Public 배포와 다르지 않지만, registry가 다르다는점, Private과 관련된 설정들이 필요하다는 점을 유의해서 살펴보도록 하겠습니다. 설정을 완료한 후 Private Respository를 생성하고 이전의 코드들을 해당 Repository에 Push하면 이전과 마찬가지로 Github Action과 함께 Github Package로 완료될 것입니다.

 

1. package.json 수정하기

package.json

{
    "name": "@YOUR_NAME/PACKAGE_NAME",
    // "private": false, => 삭제
    
    ...
    
    "publishConfig": {
        "type": "git",
        "registry": "https://npm.pkg.github.com"
    }
    
    ...
}

"name": 회사 또는 계정의 namespace로 종속되도록 패키지이름의 형식을 맞추도록 권장합니다. @username/package_name

"private": Private Repository로 Push 할 예정이기 때문에 설정을 삭제합니다.

"publishConfig": 여기에서 npm registry가 아니라 github의 registry로 배포하겠다는 것을 반드시 명시해야합니다.

 

2. 깃허브액션 워크플로 수정하기

기본적인 예제는 Github의 공식문서에서 참조할 수 있습니다.

 

Publishing Node.js packages - GitHub Docs

You can publish Node.js packages to a registry as part of your continuous integration (CI) workflow.

docs.github.com

push.yml

# Name of our action
name: Release

# The event that will trigger the action
on:
    push:
        branches: [main]

# what the action will do
jobs:
    release:
        runs-on: ubuntu-latest
        permissions:
            contents: read
            packages: write
        if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
        steps:
            - uses: actions/checkout@v2
              with:
                  token: ${{ secrets.PACKAGE_RELEASE }}

            - name: Prepare repository
              run: |
                  git config user.email "your_mail@gmail.com"
                  git config user.name "your_name"
                  git fetch --unshallow --tags
            - name: Use Node.js 16.x
              uses: actions/setup-node@v3
              with:
                  node-version: 16
                  registry-url: https://npm.pkg.github.com/
                  scope: '@your_name'
            - name: Install dependencies
              uses: bahmutov/npm-install@v1
            - name: Create Release
              env:
                  GH_TOKEN: ${{ secrets.PACKAGE_RELEASE }}
                  NODE_AUTH_TOKEN: ${{ secrets.PACKAGE_RELEASE }}
              run: |
                  yarn release
  • 우선 패키지 배포용 PAT를 따로 만들어서 PACKAGE_RELEASE 토큰을 secrets에 저장하였습니다.
  • push.yml에 토큰이 필요한 부분에 스크립트를 수정해주었습니다.
  • "uses: actions/setup-node@v3" 부분에서 with에 Github Package로 배포하기 위한 설정을 추가합니다.

 

3. Push or Merge Pull Request

package.json과 push.yml을 모두 수정하였다면 main 브랜치로 push 하거나 Pull Request를 병합하세요. 그럼 아래와 같이 배포가 진행됩니다.


Private 라이브러리를 Docker에서 설치하고 실행하기

Private 하기 때문에 권한인증 과정이 반드시 포함되어 있을 겁니다. Package Read 권한이 있는 Token을 생성하고 그 토큰을 통해서 라이브러리를 설치하고 사용하도록 하겠습니다. 먼저 라이브러리를 사용할 프로젝트를 하나 준비해두고 시작하겠습니다.

 

1. 읽기 권한이 있는 토큰 얻기

라이브러리를 배포한 계정으로부터 토큰을 요청해야합니다. 배포한 계정에서는 package를 read하는 것 외에는 해당 토큰으로 다른 접근을 할 수 없도록 "read:packages"만 체크하여 토큰을 생성합니다. 그러고 나서 생성된 토큰 값을 사용자에게 공유합니다.

읽기 권한만 있는 토큰 생성하기

 

2. npm 설정 추가하기

Docker로 실행하기 전에 먼저 로컬에서 설치하는 방법을 알려드리겠습니다.

.npmrc를 루트디렉토리에 생성하고 다음과 같이 만드세요.

@YOUR_NAME:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_TOKEN
  • 라이브러리의 scope(보통 계정이름) YOUR_NAME에 입력합니다.
  • 1번에서 공유받은 읽기권한이 있는 토큰을 YOUR_TOKEN에 입력합니다.

"npm install @YOUR_NAME/PACKAGE_NAME"을 터미널에서 실행하면 정상적으로 라이브러리 설치가 완료됩니다.

 

그렇다면 Docker에서는 어떻게 할까요? Docker에서도 .npmrc 설정을 통해서 라이브러리를 설치하지만 token의 입력 장소가 다릅니다. 그 부분에 유의해서 보도록 하겠습니다.

.npmrc

@YOUR_NAME:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${PACKAGE_TOKEN}
  • _authToken에 필요한 토큰을 더이상 하드코딩하여 넣지 않습니다.
  • 도커빌드 시에 PACKAGE_TOKEN라는 인자를 받아서 처리하도록 변수로 넣어둡니다.

3. Docker 설정하고 빌드하기 (Dockerfile, docker-compose.yml)

dockerfile.develop

FROM node:16-alpine
RUN apk update
RUN apk add --no-cache tzdata
RUN apk add g++ make python3

WORKDIR /app

ARG PACKAGE_TOKEN
COPY .npmrc .npmrc
COPY package*.json ./

RUN npm install
COPY ./ ./

CMD [ "npm", "run", "dev" ]
  • ARG PACKAGE_TOKEN: PACKAGE_TOKEN 빌드시 값을 받을 인자 설정합니다.
  • COPY .npmrc .npmrc: Private패키키 설치를 위한 설정이 담겨있는 .npmrc 를 복사합니다.

이제 아래 명령어로 도커를 빌드하면 정상적으로 Private 패키지를 설치하고 이미지가 빌드될 것입니다.

docker build --build-arg PACKAGE_TOKEN=받아온_토큰_값 -f Dockerfile.develop -t 이미지이름:태그 .
docker run 이미지이름:태그​

 

또다른 방법으로는 docker-compose.yml을 이용하여 이미지를 빌드하고 컨테이너를 실행하는 법이 있는데요. 토큰을 어디에 저장해두어야 하는지 살펴보겠습니다.

 

docker-compose.yml

version: "3"
services:
  your_service_name:
    build:
      dockerfile: Dockerfile.develop
      context: ./your_service_name
      args:
        PACKAGE_TOKEN: your_token_value
    volumes:
      - /app/node_modules
      - ./smart_board:/app
    stdin_open: true
    environment:
      - CHOKIDAR_USEPOLLING=true

터미널 명령어와 크게 다르지 않은 형태로 입력하는 것을 확인 할 수 있습니다. build의 args로 토큰값을 입력하고 파일을 저장합니다.

 

docker-compose up --build

 위 명령어로 실행해도 역시 Private 패키지가 정상적으로 설치되어 사용할 수 있는 상태가 됩니다.


디자인시스템을 구축하기 위한 여정중 큰 산을 넘었다고 생각합니다. 이제는 진짜 컴포넌트 코드를 작성할 일만 남았습니다. UI 컴포넌트 라이브러리는 여러 프로젝트에서 동시에 접근하여 사용하기 때문에 버그 발생에 유의하여 코드를 작성해야 합니다.

 

따라서 다음 포스팅에서는 Storybookr과 TDD로 UI 컴포넌트를 만드는 방법에 대해서 공유드리도록 하겠습니다.