モノリポの一部をGitHub PackagesのプライベートNPMパッケージで公開する

2021.03.23

まえがき

案件でサーバーとクライアントがJavaScript/TypeScriptで作られてる場合、サーバーとクライアントでAPIの型定義やバリデーションのユーティリティーなどを共通化したくなります。

共通化する部分だけを別リポジトリに切り出すと、開発中公開しないと使えないので面倒です。今回はサーバー側にYarn Workspaces モノリポ(モノリシックリポジトリ)の共通化部分をNPMで公開します。

また案件で使うときは共通化はしたいけど、パブリックに公開したくはないっという矛盾があります。プライベートNPMとして公開できるにようにします。

要件まとめ

  • サーバーもクライアントもプライベートリポジトリで管理されている
  • サーバー側が共通化ソースを含むモノリポ構成になっている
  • クライアント側はNPMパッケージとして読み込む
  • 非公開のNPMパッケージとして公開したい

今回はGitHub Packagesを使って解決していきます。

GitHub Packagesはソフトウェアパッケージのホスティングサービスであり、ソフトウェアパッケージをプライベートもしくはパブリックでホストでき、パッケージをプロジェクト中で依存関係として使えるようになります。

サンプルコード

サーバーとクライアントのサンプルを用意しました。こちらのサンプルをベースに説明していきます。 サーバー側は、Fastify、クライアント側はReactの例になっていますが、今回説明したいところとは関係ないのでお好きなものでお試しください。

公式ドキュメントはこちらを参考にしました。

サーバ側サンプルについて

$ tree -I node_modules -L 3            
.
├── my-commons
│   ├── package.json
│   ├── src
│   │   ├── hello.ts
│   │   └── index.ts
│   └── tsconfig.json
├── package.json
├── server
│   ├── package.json
│   ├── src
│   │   └── index.ts
│   ├── tsconfig.json
│   └── yarn-error.log
└── tsconfig.json

./package.json でYarn Workspacesの設定がされておりモノリポになっています。 my-commons を非公開NPMとしてGitHub Packagesで公開します。

./package.json

{
  "name": "sample-server",
  "version": "1.0.0",
  "private": true,
  "workspaces": {
    "packages": [
      "my-commons",
      "server"
    ]
  }
}

my-commonsもserverもTypeScriptになっています。my-commonsはビルド後のファイルをdistディレクトリに出力するようにtsconfig.jsonを設定します。

my-commons/tsconfig.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    ...
    "outDir": "./dist",
  },
  ...
}

my-commonsはnpmパッケージで読めるようにモジュールを単一のエンドポイントで読めるようにindex.tsにまとめていきます。package.jsonのmainとtypesにビルド後ファイルの指定をします。

my-commons/package.json

{
  ...
  "main": "dist/index.js", <- ここ
  "types": "dist/index.d.ts", <- ここ 
  ...
  "scripts": {
    "build": "tsc"
  },
}
 I

my-commonsをGitHub PackagesでプライベートNPMを公開する

手順は以下の通りです。モノリポなので少し複雑です。

  • my-commonsをアップロード先をGitHub Packagesに指定する
  • GitHub Packagesで上げるためのトークンを設定する
  • GitHub Packagesで公開する

参考

my-commonsをアップロード先をGitHub Packagesに指定する

GitHub Packagesで公開すること指定します。my-commons直下に.npmrcを作成します

registry=https://npm.pkg.github.com/[Owner]

[Owner]とは、たとえばhttps://github.com/kamedon 配下のリポジトリで作成する場合kamedonになります。組織の場合は組織名になります。以下の環境に合わせて編集ください。また@[Owner]と書かれているところは@が必要です。

registry=https://npm.pkg.github.com/kamedon

my-commonsのpackeage.jsonを設定します。

my-commons/package.json

{
  "name": "@[Owner]/my-commons", <- 名前規則が決まっているためこのフォーマットが必須
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "repository": {  
    "type": "git",  
    "url": "https://github.com/[Owner]/my-commons.git",  <- 公開するGitのURL
    "directory": "my-commons"  <- 公開したいディレクトリ
  },
  ...
}

nameは@オーナー/ライブラリー名にする必要があります。モノリポの一部だけを公開するのでrepositoryを追加する必要があります。わかりやすくするためにmy-commonsを公開するだけのプライベートリポジトリを作りました。モノリポじゃない場合、repositoryの設定と公開するために新しいリポジトリ作成は必要ないです。

あとは npm publish コマンドで公開しますが、認証で弾かれていまいます。

GitHub Workspacesで上げるためのトークンを設定する

公開するためにはGitHubの認証トークンが必要です。GithubからPersonal access tokensを作成をします。Personal access tokensの権限で write:packagesを付与する必要があります。

GithubからPersonal access tokensを入手後 ホームディレクトリの .npmrc に追記します。なければ新規作成します。[TOKEN]を入手したPersonal access tokenに書き換えてください。

~/.npmrc

//npm.pkg.github.com/:_authToken=[TOKEN]

Personal access tokensの取得はこちらを参考にしてください。

GitHub Packagesで公開する

my-commonsでnpm publishしたら公開ができます。

$ npm publish

成功したらGitHubのmy-commonsリポジトリでこんな感じになります。

サーバーサイドでmy-commonsを利用する

サーバーサイドでmy-commonsの読み込みは公開したNPMを使わずにローカルを読むようにします。開発中NPMを公開しないと動作確認ができないとめんどくさいためです。

server/package.json

{
  "name": "server",
  "dependencies": {
    ....
    "@[Owner]/my-commons": "*"
  },
}

Yarn Workspacesの設定をしているので簡単です。 ただし、事前にmy-commonsでyarn buildでビルドしておく必要があります。

ソースコードでは他のパッケージと同様に利用できます。

server/src/index.ts

import fastify from 'fastify'
import {hello} from "@[Owner]/my-commons";

const server = fastify({
    logger: true
})

server.get('/', async (request, reply) => {
    reply.type('application/json').code(200)
    const hello2 = hello()
    return {hello2}
})

server.listen(3000, (err, address) => {
    if (err) throw err
    server.log.info(`server listening on ${address}`)
})

クライアントでmy-commonsを利用する

クライアント側のサンプル

⟩ tree -I node_modules -L 3 
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── index.css
│   ├── index.tsx
│   ├── logo.svg
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   └── setupTests.ts
├── tsconfig.json
└── yarn.lock

単純にcreate-app-reactでreactのプロジェクトを新規作成した状態になっています

クライアント側からmy-commonsを読み込む

手順は以下の通りです。

  • package.jsonにmy-commonsをdependenciesに追加
  • npmの取得の先の設定
  • GitHub Packagesの認証トークンの設定

package.jsonにmy-commonsをdependenciesに追加

クライアント側は、NPMパッケージとして読めるようにします。package.json の依存関係に追加します。

package.json

{
  "name": "sample-app",
  "dependencies": {
    ...
    "@[Owner]/my-commons": "0.0.0" <- ここを追加
  },
}

npmの取得の先の設定

デフォルトのnpmにはないプライベートNPMパッケージなので検索先を教えてあげます。

プロジェクトのルートに .npmrc を追加します。

registry=https://npm.pkg.github.com/[Owner]
@[Owner]:registry=https://npm.pkg.github.com

GitHub Packagesの認証トークンの設定

yarn install 等で取得しようとすると認証エラーになるので、 GitHub Packages認証を通すときと同じようにホームディレクトリに .npmrc を設定します。公開のときと違う点はPersonal access tokensの権限で read:packagesのみでよいです。

インストールできる条件が、作成したトークンのユーザが、そのレポジトリをみれるかどうかです。

これでyarn installができ、ライブラリをインストールできるようになりました。

動作確認してみます。

import React from 'react';
import './App.css';
import {hello} from '@[Owner]/my-commons'

function App() {
  return (
    <div className="App">
        <p>
          {hello()}
        </p>
    </div>
  );
}

export default App;

まとめ

モノリポの一部分をGitHub Packagesで非公開NPMパッケージを公開できました。