Typescript Library on github package

Photo by Yancy Min on Unsplash

Typescript Library on github package

이 포스트에서는 github packages, github actions 그리고 semantic-release를 이용해서 typescript library repository를 생성, 관리, 배포하는 방법을 다룬다.

In library

# .npmrc
@adoji92:registry=https://npm.pkg.github.com/

adoji92 부분에는 자신의 github 아이디, 또는 github organization을 넣어주면 된다.

# .releaserc.json
{
  "branches": [
    "+([0-9])?(.{+([0-9]),x}).x",
    "main",
    "master",
    "next",
    "next-major",
    {
      "name": "beta",
      "prerelease": true
    },
    {
      "name": "alpha",
      "prerelease": true
    }
  ]
}

semantic release를 설정하는 파일이다. 첫 세 개의 branch만 있어도 충분하다고 생각한다.

# .github/workflows/publish.yml
name: Publish

on:
  push:
    branches:
      - main
      - master

env:
  NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
          registry-url: 'https://npm.pkg.github.com'
          scope: '@adoji92'
      - run: |
          echo "//npm.pkg.github.com:_authToken=${{ secrets.GITHUB_TOKEN }}" > ~/.npmrc
          yarn install --frozen-lockfile
          yarn build
      - name: Release
        run: yarn semantic-release
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

main 또는 master 브랜치에 푸시가 되었을 때 돌아가는 github action을 정의한다. 중간에 scope: '@adoji92' 부분을 바꾸는 것에 유의하자. 파일명과 name은 publish와 다른 것을 써도 좋다. github action을 돌리는 컴퓨팅 리소스에서 접근 가능한 정보들을 .npmrc에 append한 뒤, typescript 코드를 빌드해서 npm에 릴리즈하도록 정의한다.

npm에 릴리즈하지 않고 github repo의 것을 그대로 ssh+git으로 가져다쓰는 경우 빌드된 js파일이 없는 pure ts 라이브러리를 사용하게되며, 이를 js repo에서 import하는 경우 에러가 발생한다.

# tsconfig.json
{
  "compilerOptions": {
    "module": "CommonJS",
    "moduleResolution": "Node",
    "target": "ES2015",
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "sourceMap": true,
    "declaration": true,
    "outDir": "./dist",
    "esModuleInterop": true
  },
  "exclude": [
    "node_modules",
    "**/node_modules/*"
  ],
  "include": [
    "./index.ts"
  ]
}
# package.json
{
  "name": "@adoji92/my-library",
  "version": "0.0.0-development",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "repository": "git@github.com:adoji92/my-library.git",
  "author": "Dongjin Ahn <adoji92@gmail.com>",
  "license": "MIT",
  "dependencies": {
    "winston": "^3.8.2",
    "winston-elasticsearch": "^0.17.1"
  },
  "scripts": {
    "build": "tsc",
    "semantic-release": "semantic-release"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/"
  },
  "devDependencies": {
    "@types/node": "^18.11.9",
    "semantic-release": "^19.0.5",
    "typescript": "^4.9.3"
  }
}

마지막으로 tsconfig.json과 package.json 파일이다. dist/ 폴더에 빌드된 파일을 정상적으로 참조할 수 있도록 package.json에서 main, types를 잘 정의해주는 것, 그리고 package.json에서 publishConfig를 넣어주는 것이 중요하다.

여기서는 version: "0.0.0-development" 로 고정되어있는데, 이는 semantic release에서 commitizen convention에 맞춰서 버전업을 해주는 것이 매우 편하다. (은근히 자주 겪는단 말이지.. push하고나서 봤더니 package.json에 버전 수정 안해서 두 번 푸시해야하는 것...)

  • fix: 커밋 -> 0.0.1 단위씩 증가

  • feat: 커밋 -> 0.1 단위씩 증가

  • BREACKING CHANGE: footer가 있는 커밋 -> 1 단위씩 증가

https://github.com/semantic-release/semantic-release 에서 How does it work? 를 참고하자.

이제 main 또는 master 브랜치에 푸시가 된 경우 Github action이 돌아가며 npm에 릴리즈한다.

Using library

이제 위 라이브러리를 가져다 사용하는 코드를 보자.

# package.json
{
  ...
  dependencies: {
    "@adoji92/my-library": "^1.0.0"
  }
}

위와 같이 package.json에 추가하고,

# .npmrc
@adoji92:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=GITHUB_TOKEN_HERE

Github에서 발급받은 토큰을 npmrc에 넣어주고 (public repo면 필요없을 수도?)

// app.js
const { foo } = require('@adoji92/my-library');
foo();

위와 같이 임포트하여 사용하면 된다.