注目イベント!
春の新人向け連載企画開催中
新人エンジニアの皆さん、2024春、私たちと一緒にキャリアアップの旅を始めませんか?
IT業界への最初の一歩を踏み出す新人エンジニアをサポートする新連載スタート!
mameyose

マルチランタイム時代のモダン JavaScript レジストリ JSR を使ってみる

| 8 min read
Author: masahiro-kondo masahiro-kondoの画像

はじめに

#

JSR は JavaScript/TypeScript 用のパッケージレジストリです。

JSR: the JavaScript Registry

Portal

現在オープンベータの位置付けです。GitHub アカウントでサインアップ可能です。

Introducing JSR - the JavaScript Registry

2024.05.15追記

Deno の標準ライブラリが JSR でホストされるようになりました。

The Deno Standard Library is now available on JSR

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 を構築したモチベーションが書かれています。

Why JSR? - Docs - 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 社の日野澤さんのスライドがとてもわかりやすいです。

JSR の紹介

日野澤さんは Deno ユーザーは https import ではなく jsr import を使うべきという記事も書かれています。

Deno ユーザーは https import と jsr import のどちらを使うべきか?

Information

deno init で生成されるプロジェクトでも、テストコードで assert パッケージが jsr import されています。

import { assertEquals } from "jsr:@std/assert";

パッケージを利用する

#

前置きが長くなりましたが、JSR パッケージの利用です。Deno、Node.js、Bun のプロジェクトで利用してみます。公式ドキュメントの例にあるようにルカさん[1]の cases パッケージを使います。

@luca/cases - JSR

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 に追加されます。

deno.json
{
  "tasks": {
    "dev": "deno run --watch main.ts"
  },
  "imports": {
    "@luca/cases": "jsr:@luca/cases@^1.0.0"
  }
}

与えられた文字列をキャメルケースに変換する camelCase 関数を呼び出します。

main.ts
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"));
Information

推奨されるのは、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 には以下のように、依存が追加されました。

package.json
{
  "name": "hello-jsr",
  "version": "1.0.0",
  "dependencies": {
    "@luca/cases": "npm:@jsr/luca__cases@^1.0.0"
  }
}

ESM として利用できます。

index.mjs
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 に依存が追加されています。

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 として利用できます。

index.ts
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 を頑張って書くと公開ページのドキュメントに反映されます。

main.ts
/** 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 を作ればよいようです。

deno.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 ボタンをクリックして作成します。

Publish a package

スコープができるとパッケージ作成のボタンが出てきます。

Create Package

パッケージ作成のボタンをクリックすると Authorization の画面が出ますので Approve をクリックします。

Authorization

公開されました。

Published

Information

公開直後は、タイムライン的なところにパッケージが出て恥ずかしかったのですが、あっという間に流れていきました(笑)。

Recent updates

早速パッケージを使ってみます。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 パッケージを使用するコードを追加します。

index.ts
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

Information

JSR は GitHub Actions で公開した場合、Sigtore の透過性ログを作成しパッケージの出自をトレース可能にしています。

Provenance and trust - Docs - JSR

Future support としてアップロード時にパッケージのマニフェストに追加署名し、Sigstore 透過性ログに公開する実装が追加されるとのことです。サプライチェーン攻撃に強いレジストリとして位置付けられることでしょう。

GitHub Actions での Sigstore の利用については以下の記事でも取り上げています。

ソフトウェアサプライチェーンセキュリティのための GitHub Actions ワークフロー

さいごに

#

TypeScript でパッケージを作成し、ビルドステップなしで公開できるのはとても楽だと思いました。

JSR の利用方針としては、

  • Deno ではマスト
  • Node.js や Bun では CommonJS より ESM を採用し、中でも JSR にあるものを優先的に使う

のような感じで使っていって馴染んでいきたいと思います。

Information

JSR がどのように構築されているかについてはルカさんのブログ記事で語られています。JSR の API は HA 構成の PostgreSQL クラスターと Rust のコードで構築されているようです。

How we built JSR


  1. Deno の Web フレームワーク Fresh の作者の方。 ↩︎

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。