先日、同時実行数を制限しながら並行実行する関数を書きました。
便利関数を作ったら他のプロジェクトから参照したいですよね。 そこでパッケージレジストリに登録してみました。
正直コピペで実装で十分なのでは?という分量ですが、パッケージ公開の練習です。
ソースコードを準備する
まずは公開するソースコードを準備していきましょう。 ソースコードはGitHubで公開しました。
Denoの開発環境を整える
TypeScriptの開発環境を整えたいのですが、Node+TypeScriptの組み合わせはプロジェクトの立ち上げは意外と面倒です。 そこで今回は Deno を使ってみることにしました。 DenoはTypeScriptの実行ランタイムとして開発されており、特別な設定なしでTypeScriptを実行できます。 Brewでインストールしました。
brew install deno
僕は最近エディターには VS Code を使っているので、Deno用の拡張機能をインストールしました。
インストールしただけでは有効化されません。
ワークスペースの設定ファイル .vscode/settings.json を編集して、明示的に有効化します。
{
  "deno.enable": true,
  "deno.lint": true,
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "denoland.vscode-deno",
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
}
開発環境が整ったらパッケージ本体のソースコードを書いていきます。
GitHub Actionsでテストを実行する
パッケージとして公開するのであれば、テストを書いて、CIを回しておきたいですよね。 Deno公式がセットアップするためのアクションを公開しているので、これを利用します。
あとは deno test コマンドを実行するだけです。
on:
  push:
  pull_request:
jobs:
  deno:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x
      - name: test
        run: |
          deno test          
カバレッジを計測する
--coverage オプションをつけてテストを実行すると coverage ディレクトリにカバレッジが出力されます。
カバレッジはJSONファイルで出力されるのですが、これは Deno 独自のものらしく、そのままでは他のサービスと連携できません。
以下のコマンドで、一般的な lcov 形式に変換できます。
deno coverage --lcov > coverage.lcov
カバレッジは Codecov にアップロードすることにしました。
テスト関連のワークフローをまとめると、次のようになります。
on:
  push:
  pull_request:
jobs:
  deno:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x
      - name: test
        run: |
          deno test --coverage
          deno coverage --lcov > coverage.lcov          
      - name: upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: coverage.lcov
          token: ${{ secrets.CODECOV_TOKEN }}
JSRにパッケージを公開する
今回は Deno のパッケージとして作成したので、 JSR に公開するのがお手軽です。 ここにアップロードしてみましょう。
Denoのパッケージを作成する
JSRはGitHubアカウントでサインアップ可能です。 サインアップしたら jsr.io/new からパッケージを作成しましょう。
Denoのパッケージをアップロードする
パッケージのメタ情報を deno.json に記載します。
{
  "name": "@shogo82148/limit-concurrency",
  "version": "0.1.6",
  "exports": {
    ".": "./limit-concurrency.ts"
  }
}
あとは deno publish を実行するだけです。
deno publish
ここでブラウザーが起動するので、案内にしたがって公開許可を行います。
一度ログインしていればパスワードの入力は必要ありません。
今まで .hogerc にパスワードを書く方式しか知らなかったので、「時代は進化しているんだなぁ」と感動しました。
npmにパッケージを公開する
新興のレジストリも登場してきましたが、JavaScriptのレジストリと言ったら今も npm でしょう。 そういうわけで、 npm にも登録してみます。
Deno のパッケージを npm パッケージに変換する
npmはあくまでもJavaScriptのレジストリなので、TypeScriptのモジュールをアップロードするにはビルドが必要です。 denoland/dnt を使ってビルドしてみました。
- Deno + dntでCJS・ESMに対応したnpmパッケージを作ろう
- dnt — the easiest way to publish a hybrid npm module for ESM and CommonJS
- denoland/dnt
dnt用の設定ファイルを用意します。
// ex. scripts/build_npm.ts
import { build, emptyDir } from "@deno/dnt";
await emptyDir("./npm");
await build({
  entryPoints: ["./limit-concurrency.ts"],
  outDir: "./npm",
  shims: {
    deno: true,
  },
  package: {
    // package.json properties
    name: "@shogo82148/limit-concurrency",
    version: Deno.args[0],
    description: "Limit the concurrency of tasks.",
    license: "MIT",
    repository: {
      type: "git",
      url: "git+https://github.com/shogo82148/limit-concurrency.git",
    },
    bugs: {
      url: "https://github.com/shogo82148/limit-concurrency/issues",
    },
  },
  postBuild() {
    // steps to run after building and before running the tests
    Deno.copyFileSync("LICENSE", "npm/LICENSE");
    Deno.copyFileSync("README.md", "npm/README.md");
  },
});
このスクリプトを実行すると npm/ ディレクトリに npm パッケージが出力されます。
deno run -A scripts/build_npm.ts 0.1.0
npmのパッケージをアップロードする
あとは npm publish を実行するだけです。
cd npm
npm publish --access public
ブラウザーが起動するので、案内にしたがって公開許可を行います。
GitHub Actionsでリリースを自動化する
せっかくなのでリリースも自動化しましょう。 タグを打ったら、自動的にJSRとnpmに公開するよう設定します。
GitHub Actionsでリリースを自動化する利点
もちろん、リリースの手間が減るというメリットはありますが、それだけではありません。
パッケージのページに
Built and signed on GitHub Actions
と書かれた バッジを表示できます 。
なんかカッコイイ!欲しい!
npmのアクセストークンを取得
npmの場合、公開のためにアクセストークンが必要です。 npmの設定から Access Token を発行し、GitHub の Secretsに保存しておきましょう。
GitHubからJSRへのアクセス許可を設定
JSRの場合、アクセストークンは不要です。 代わりに GitHub Actions からのアクセス許可が必要です。 パッケージの設定から許可を出しておきます。
GitHub Actions のOIDCを有効化する
パッケージのリリースにはOIDCが有効化されている必要があります。 リリースワークフローに以下の行を追加します。
permissions:
  contents: read
  id-token: write
publishスクリプトを書く
あとは GitHub Actions ワークフローの中で deno publish && npm publish するだけです。
#!/bin/bash
ROOT=$(git rev-parse --show-toplevel)
set -euxo pipefail
cd "$ROOT"
# publish to jsr
deno publish
# npm publish
deno run -A scripts/build_npm.ts "$(jq -r .version deno.json)"
cd npm
npm publish --access public --provenance ## --provenance オプションがついているのがポイント
まとめ
npmとjsrに簡単なパッケージを公開してみました。 さらに、リリースフローの一部を GitHub Actions で自動化しました。
皆さんもぜひ「Built and signed on GitHub Actions」のバッジをゲットしてください。
コンピューターの森でラビットが舞う
パッケージを変換、夢中で追う
自動リリースの風に乗り
npmとJSRへ、夢を運ぶ
🌟コードの魔法、今広がる🛤✨by CodeRabbit
余談
Denoについて調べていたら、Denoの標準ライブラリに pooledMap という関数を見つけました。 AsyncIterableを受け取り、並列度を制限しながら並行実行するというもの。 これはまさに求めていたもの!
まあ、今回はパッケージ公開のお勉強だったのでヨシ!
参考
- Deno
- denoland/vscode_deno
- denoland/setup-deno
- 【GitHub Actions】DenoのカバレッジをCodecovで出す
- npmに公開していたパッケージをjsrにもpublishしてみた
- マルチランタイム時代のモダン JavaScript レジストリ JSR を使ってみる
- Deno + dntでCJS・ESMに対応したnpmパッケージを作ろう
- dnt — the easiest way to publish a hybrid npm module for ESM and CommonJS
- denoland/dnt
- npm パッケージに認証バッジを付けてもらった話 (npm Provenance を試す)
- Introducing npm package provenance
- TypeScriptで同時実行数を制限しながら並行実行する
- shogo82148/limit-concurrency - GitHub
- @shogo82148/limit-concurrency - jsr
- @shogo82148/limit-concurrency - npm
- @std/async > pooledMap