cotoblog

Denoのパッケージ管理システム「JSR」について

Denoのパッケージ管理システム「JSR」について

プログラミング

「JSR」(JavaScript Registry)は、JavaScript実行環境Denoにおけるパッケージ管理システムです。
この記事では、JSRの主な特徴や利点について説明します。

  • JSRの導入背景
  • JSRについて
  • JSRのデメリット
  • Denoから見たJSR
  • JSRを利用する際の注意点

JSRの導入までの経緯と背景

まず始めに、JavaScript実行環境Denoにおけるパッケージ管理システムであるJSRが導入されるまでの経緯と背景についてです。
ESモジュールがWebプラットフォームの標準モジュール形式として採用されましたが、JavaScript実行環境Nodeにおけるパッケージ管理システムのnpmは、ESモジュールではなく主にCommonJS形式を前提としています。
従来のNode.js中心のエコシステム(npmを使ったパッケージ管理)は、DenoやBun、workerdなどの新しいランタイム環境と完全に互換性があるわけではありません。そのため、これらの環境向けに新しいパッケージ管理が必要となります。
DenoやBunはESモジュール(ESM)を標準として、さらにTypeScriptをサポートしているため、CommonJS形式のnpmの代替として、新しいパッケージ管理システムの「JSR」がリリースされました。
※TypeScriptはJavaScriptのスーパーセットとして、JavaScriptの最新機能を早期に試すことができ、主要なJavaScriptプロジェクトに選ばれる標準技術として定着しています。

JSRについて

次にJSRについて、JSRの公式ドキュメント「Why JSR?」を参考に整理しました。

  • JSRはTypeScriptを直接サポートする
  • TypeScript非対応の環境(Node.js等)にも対応する
    • ソースコードをJavaScriptにトランスパイルする
    • TypeScriptの型定義ファイルを提供する
  • リファレンスドキュメントを自動生成する
    • JSRは、コード内のJSDocコメントに基づいてドキュメントが生成される
  • ESMに特化した設計
  • npmのスーパーセット  
    • JSRはnpmベースのプロジェクトとも互換性があり、npm依存をインポートできる
    • Node.jsの node_modules を使用する環境でも、JSRのパッケージが利用可能
  • CDNからパッケージを取得して、ローカルキャッシュされる
  • 効率的なダウンロード 
    • 必要最小限のモジュールのみを都度取得する仕組みを取る
    • 一方でnpmはパッケージ全体がインストールされ、依存関係も含めてローカルにコピーされる
  • OIDCを使ってGitHub Actionsからパッケージを公開できる
    • トークンレス認証フローでローカルから公開することが可能
  • 品質スコアが表示される
    • 作成者がパッケージのパフォーマンスやドキュメントの改善動機付けとなる
  • 標準ライブラリが存在する
    • DenoやJSRのコミュニティで標準的にサポートしているので信頼性が高い

次に問題点について整理します。

JSRの現状の問題点

  • Node.jsの完全な互換性がない
    • Node.jsのエコシステム(node_modulesやpackage.json管理)とは異なるため、既存のNode.jsプロジェクトにそのまま移行するのが難しい。※一応、利用はできるようです。参考資料
  • Deno環境での利用に最適化
    • Deno環境以外でJSRの利用は注意が必要
    • ESM特化された設計のため、CommonJSのモジュール、特にNode.js固有のモジュールとの互換性に課題を残す
  • 利用者がまだ少ないため情報が少ない
    • サポート情報やコミュニティによるドキュメントが限られている
    • トラブルシューティングの情報が少ない

Denoから見たJSR

Deno v1では、URLインポートを依存関係の解決にしていました。

import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";


パッケージをURL経由でインポートすることにより、 HTTPS経由の分散システムへの置き換えを果たし、さらにpackage.jsonファイルやnode_modulesフォルダが不要となります。
一方で、同時に問題点も生じます。

問題点

Deno v1でパッケージをURL経由でインポートする際、バージョンを明示的に指定しますが、バージョン範囲(例: ^1.0.0 や ~1.2.0)指定の柔軟性がなく、更新を手動で行う必要があります。

import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";

上記の指定を例にすると、std@0.224.0 の新しいバグ修正や機能追加がリリースされたとしても、自動的に適用されないため、開発者が手動でバージョンを更新しなければなりません。
例えば、新しいバージョン(std@0.225.0)にアップグレードするには、次のようにコードを変更する必要があります。

import { assertEquals } from "https://deno.land/std@0.225.0/assert/mod.ts";


Node.js の場合、package.json ファイルで以下のようにバージョン範囲を指定することで、自動的に最新の適切なバージョンをインストールできます。

"dependencies": {
 "@types/node": "^16.0.0"
}


Deno v1にはこのようなバージョン範囲の解決機能がありません。
しかし、Deno v2からJSRに移行することで、これらの問題が解決されます。

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


※旧来のhttpsベースのレジストリ(https://deno.land/x)は削除されませんが、実質的に非推奨となります。
※キャレット(^)記法について
^1.0.6 は、1.0.6以上のバージョンで、同じメジャーバージョン(1.x.x)内の最新バージョンを意味します。つまり、1.0.6以上であれば、例えば1.1.0や1.2.3のようなバージョンも適用されますが、2.0.0以上のメジャーアップデートには適用されません。
^1.0.6 の場合、1.0.6以上で、1.x.xの範囲(1.1.0、1.2.3など)までがインストール対象
~1.0.6 の場合、1.0.6以上で、1.0.xの範囲(1.0.7、1.0.8など)のみが適用され、1.1.0は対象外

参考サイト

JSRを利用する際の注意点

Deno v2からJSRを利用することができます。
そのJSRを大規模なプロジェクトで利用する場合、オプションでインポートマップを追加して、インポート指定子を短くし、ファイル間のバージョン管理を行うことが望ましいです。

// deno.json
{
 "imports": {
  "@std/assert": "jsr:@std/assert@1"
 }
}


// ファイルから利用する
import { assertEquals } from "@std/assert";
assert(1, 2);


まとめ

いかがでしたでしょうか。
簡単ではありますが、JSRの導入経緯と背景、さらに利用における注意点などをまとめました。
新しい技術が導入される経緯と背景を理解することは、既存の問題点を把握し、それらがどのようにエンジニアリングで解決されたのかを知る良い機会ではないでしょうか。