マルチランタイム時代のモダン JavaScript レジストリ JSR を使ってみる
はじめに
#JSR は JavaScript/TypeScript 用のパッケージレジストリです。
現在オープンベータの位置付けです。GitHub アカウントでサインアップ可能です。
Introducing JSR - the JavaScript Registry
Deno 社主体で開発されていますが、Deno、Node.js、Bun などの環境で利用可能です。
JSR が構築された背景として2009年から現在にかけて以下のような変化がありました。
- ESM の登場
- TypeScript の登場
- Node.js / Deno / Bun など多くの JavaScript ランタイムの登場
npm はこれらの変化を念頭において設計されていないので、npm を補完する存在としてパッケージレジストリを再設計したという位置付けのようです。ライアン・ダール氏は、JSR は npm を置き換えるものではなく補完するものであると述べています。
JSR Is Not Another Package Manager
JSR のドキュメントでも JSR を構築したモチベーションが書かれています。
- ネイティブ TypeScript サポート
- ESM オンリー
- クロスランタイムサポート
- 開発者体験
- 高速、セキュア、高信頼性
これらが JSR の利用を検討すべき理由として挙げられています。
Deno では 1.42 から JSR サポートが追加されました。
Deno 1.42: Better dependency management with JSR
JSR と npm との違いについても以下の点が FAQ に書かれています。
- ドキュメント自動生成
- パッケージのスコアリング
- ネイティブな TypeScript サポート
- ビルドステップ不要、よりよいユーザー体験
- サプライチェーン攻撃に対する耐性を備えたトークンレスパブリッシュ
Frequently Asked Questions - Docs - JSR
日本語のリソースとしては、Deno 社の日野澤さんのスライドがとてもわかりやすいです。
日野澤さんは Deno ユーザーは https import ではなく jsr import を使うべきという記事も書かれています。
Deno ユーザーは https import と jsr import のどちらを使うべきか?
deno init
で生成されるプロジェクトでも、テストコードで assert パッケージが jsr import されています。
import { assertEquals } from "jsr:@std/assert";
パッケージを利用する
#前置きが長くなりましたが、JSR パッケージの利用です。Deno、Node.js、Bun のプロジェクトで利用してみます。公式ドキュメントの例にあるようにルカさん[1]の cases パッケージを使います。
Deno で使ってみる
#まず Deno のプロジェクトを作ります。
mkdir hello-jsr && cd hello-jsr
deno init
deno add
でパッケージを追加します。
$ deno add @luca/cases
Add @luca/cases - jsr:@luca/cases@^1.0.0
deno.json の imports に追加されます。
{
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"@luca/cases": "jsr:@luca/cases@^1.0.0"
}
}
与えられた文字列をキャメルケースに変換する camelCase 関数を呼び出します。
import { camelCase } from "@luca/cases";
console.log(camelCase("hello jsr"));
実行結果。
$ deno run main.ts
helloJsr
jsr import をコードに直接書く場合、deno add で追加しなくても利用可能です。
import { camelCase } from "jsr:@luca/cases";
console.log(camelCase("hello jsr"));
推奨されるのは、deno add
による方式です。
筆者の設定が悪いのか、jsr import 方式では VS Code でエラーが出ないのですが、deno add
で deno.json に追加した場合、TypeScript の情報が取得できないようでエラー表示になっていました。もう少し調べたいと思います。
Node.js で使ってみる
#Node.js の プロジェクトで JSR パッケージを使用する場合、npm install ではなく npx で jsr コマンドを実行します。
まず Node.js のプロジェクトを作成します。
mkdir hello-jsr && cd hello-jsr
npm init --y
npx で jsr add
を実行します。jsr パッケージインストール後にターゲットのパッケージがインストールされます。
$ npx jsr add @luca/cases
Need to install the following packages:
jsr@0.12.4
Ok to proceed? (y)
Setting up .npmrc...ok
Installing @luca/cases...
$ npm install @luca/cases@npm:@jsr/luca__cases
added 1 package, and audited 2 packages in 599ms
found 0 vulnerabilities
Completed in 675ms
$ npm install @luca/cases@npm:@jsr/luca__cases
という行がありますが、これは出力されたメッセージに含まれていたものです。内部的には npm install が使われています。package.json には以下のように、依存が追加されました。
{
"name": "hello-jsr",
"version": "1.0.0",
"dependencies": {
"@luca/cases": "npm:@jsr/luca__cases@^1.0.0"
}
}
ESM として利用できます。
import { camelCase } from "@luca/cases";
console.log(camelCase("hello-jsr"));
実行結果。
$ node index.mjs
helloJsr
Bun で使ってみる
#Bun で JSR パッケージをインストールする場合、bun add
ではなく bunx で jsr を実行します。
まず Bun のプロジェクトを作成します。
mkdir hello-jsr && cd hello-jsr
bun init -y
bunx で jsr add
を実行します。
$ bunx jsr add @luca/cases
Setting up bunfig.toml...ok
Installing @luca/cases...
$ bun add @luca/cases@npm:@jsr/luca__cases
bun add v1.1.7 (b0b7db5c)
installed @luca/cases@1.0.0
1 package installed [499.00ms]
Completed in 519ms
これも内部的には bun add が呼び出されます。
package.json に依存が追加されています。
{
"name": "hello-jsr",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@luca/cases": "npm:@jsr/luca__cases"
}
}
ESM として利用できます。
import { camelCase } from "@luca/cases";
console.log(camelCase("hello bun"));
実行結果。
$ bun index.ts
helloBun
パッケージを作成して公開する
#最後にパッケージの公開もやってみます。Node.js や Bun の環境でも可能ですが、やはりネイティブサポートされる Deno 環境が楽なのではと思います。
Publishing packages - Docs - JSR
簡単な足し算のパッケージ adder を作りました(deno init で生成されるコードのままです)。
mkdir adder && cd adder
deno init
add 関数を export しています。JSR では JSDoc を頑張って書くと公開ページのドキュメントに反映されます。
/** Add two numbers
*
* @param a The first number
* @param b The second number
* @returns The sum of the two numbers
*/
export function add(a: number, b: number): number {
return a + b;
}
deno.json に、パッケージ情報を書きます。Deno を使用しない場合、deno.json ではなく jsr.json を作ればよいようです。
{
"name": "@kondoumh/adder",
"version": "0.1.0",
"exports": "./main.ts"
}
name
にはスコープ名 @kondoumh
パッケージ名 adder
を /
で区切って指定しています。
公開するファイルを exports
に指定します。モジュールなので、main.ts ではなく mod.ts の方が良かったかもしれません。
コードが書けたら公開します。Deno では publish コマンドで公開可能です。Node.js の場合は、npx jsr publish
のように実行します。
deno publish
実行すると、パッケージのチェックが行われ、待機状態になりブラウザで公開の画面が開きます。
Check file:///Users/kondoh/dev/adder/main.ts
Checking for slow types in the public API...
Check file:///Users/kondoh/dev/adder/main.ts
'@kondoumh/adder' doesn't exist yet. Visit https://jsr.io/new?scope=kondoumh&package=adder&from=cli to create the package
Waiting...
初めてパッケージを作るため JSR に @kondoumh
というスコープが存在しないので、Create
ボタンをクリックして作成します。
スコープができるとパッケージ作成のボタンが出てきます。
パッケージ作成のボタンをクリックすると Authorization の画面が出ますので Approve
をクリックします。
公開されました。
早速パッケージを使ってみます。Deno ではなく Bun のプロジェクトで使ってみます。
$ bunx jsr add @kondoumh/adder
Installing @kondoumh/adder...
$ bun add @kondoumh/adder@npm:@jsr/kondoumh__adder
bun add v1.1.7 (b0b7db5c)
installed @kondoumh/adder@0.1.0
1 package installed [437.00ms]
Completed in 455ms
インストールされました。
adder パッケージを使用するコードを追加します。
import { camelCase } from "@luca/cases";
import { add } from "@kondoumh/adder"
console.log(camelCase("hello bun"));
console.log(add(2, 3));
実行結果。
$ bun index.ts
helloBun
5
無事実行できました。
今回はローカルからアップロードする形で公開しましたが、GitHub Actions による公開がサポートされています。
Publishing from GitHub Actions | Publishing packages - Docs - JSR
JSR は GitHub Actions で公開した場合、Sigtore の透過性ログを作成しパッケージの出自をトレース可能にしています。
Provenance and trust - Docs - JSR
Future support としてアップロード時にパッケージのマニフェストに追加署名し、Sigstore 透過性ログに公開する実装が追加されるとのことです。サプライチェーン攻撃に強いレジストリとして位置付けられることでしょう。
GitHub Actions での Sigstore の利用については以下の記事でも取り上げています。
さいごに
#TypeScript でパッケージを作成し、ビルドステップなしで公開できるのはとても楽だと思いました。
JSR の利用方針としては、
- Deno ではマスト
- Node.js や Bun では CommonJS より ESM を採用し、中でも JSR にあるものを優先的に使う
のような感じで使っていって馴染んでいきたいと思います。
JSR がどのように構築されているかについてはルカさんのブログ記事で語られています。JSR の API は HA 構成の PostgreSQL クラスターと Rust のコードで構築されているようです。
Deno の Web フレームワーク Fresh の作者の方。 ↩︎