🗺️

【Deno】esm.shによるnpmパッケージ管理に便利なCLIスクリプトについて

2022/09/23に公開

はじめに

新たなJavaScript/TypeScriptランタイムであるDenoでは、Node.js向けであるnpmのパッケージが使えないというデメリットがあります。が、様々な人による様々な努力によって、それは改善されてきています。この記事では、Denoからnpmのパッケージを使うときによく用いられるCDNであるesm.shによる「努力」について簡単にまとめます。

Denoとesm.sh

Denoでは以下のように、URLによってモジュールを指定します。

import * as flags from "https://deno.land/std/flags/mod.ts";

このコードは、Denoの標準モジュールにあるflagsという、コマンドライン引数を処理するモジュールを読み込んでいます。Denoにおいてnpmのパッケージを読み込んで使いたいときには、まずこの例のようにURLで指定しなければなりません。

しかしnpmのパッケージは、URLで指定したとしてもそのままでは使えません。なぜなら、fschild_processなどのNode.jsにしかない内部モジュールを使っているからです。そこで用いられるのが、Denoの標準モジュールのnodeと、それをうまく適用してくれるCDN「esm.sh」です。

esm.shはesbuildを使ってnpmパッケージをES Module形式にして配信しているCDNです。esm.shはUser-Agentの値を元に配信するモジュールを自動で選定しますが、Denoからのアクセスには、node標準モジュールを使用してNode.js内部モジュールの解決を行ったモジュールを配信します。それにより、多くのnpmのパッケージをDenoから使うことができます。

import * as msgpack from "https://esm.sh/@msgpack/msgpack@2.8.0";

またesm.shは、X-TypeScript-TypesというHTTP Headerを返してくれます。これはDenoのためにそのモジュールの型定義ファイルの場所を示すもので、Denoはこの情報を元に型定義ファイルを読み込みに行きます。これにより、エディターの補完が効くなどの利便性を受けることができます。


最近のバージョンのDenoには試験的に、npmのパッケージを読み込む機能が搭載されていますが、それを使うには--unstableオプションが必須になってしまうため、ここでは紹介しません。

esm.shによるCLIスクリプト

そんなesm.shですが、(具体的な日時は不明ですがいつからか)Deno向けに便利なスクリプトを公開し始めました。

The CLI script is using to manage the imports with import maps, it will resolve the dependencies automatically and always pin the build version. To use the CLI mode, you need to run the init command in your project root directory:

deno run -A -r https://esm.sh init

https://esm.sh/

上記のコマンドを実行すると、次のようなdeno.json(Denoの設定ファイル)が作られます。

deno.json
{
  "importMap": "import_map.json",
  "tasks": {
    "npm:add": "deno run -A https://esm.sh/v95 add",
    "npm:update": "deno run -A https://esm.sh/v95 update",
    "npm:remove": "deno run -A https://esm.sh/v95 remove"
  }
}

見ての通りこれは、npm addなどのコマンドの代替を追加するものです。deno task npm:addのようにして使います。

deno task npm:add @msgpack/msgpack

仕組み

https://esm.shもまたesm.shが提供するモジュールのように、User-Agentを見て返す内容を選定しています。Denoからアクセスされたときには次のファイルの内容を返します。

https://github.com/esm-dev/esm.sh/blob/464df67df2aeb00faea86a89b1a256a2b8a85445/CLI.deno.ts

これは、コマンドライン引数に応じてimport_map.jsonというファイルを書き換えるスクリプトです。

Import mapとは、JavaScript/TypeScriptのimportの挙動を変えるために使われるもので、Webブラウザでも使えるほか、Denoでも使うことができます。Denoではdeno.jsonにImport mapを記述したファイルへのパスを書いて使います。

実際のimport_map.jsonの内容は次のようになります。

import_map.json
{
  "imports": {
    "@msgpack/msgpack": "https://esm.sh/v95/@msgpack/msgpack@2.8.0"
  },
  "scopes": {}
}

このImport mapによりスクリプト内では次のように書けるようになります。

- import * as msgpack from "https://esm.sh/@msgpack/msgpack@2.8.0";
+ import * as msgpack from "@msgpack/msgpack";

おわりに

今回紹介したesm.shによる「努力」とは、import_map.jsonをうまく書き換えるスクリプトをtask機能で呼び出せるようにすることで、Node.jsでの開発のときのようにnpmのパッケージを管理できるようにする、というものでした。

なかなか開発体験が高くなる、よいものだと感じました。(これまではdeps.tsを使って頑張っていましたから……。)Deno本体のnpmパッケージ読み込み機能が今後どうなるかがわかりませんが、少なくとも記事執筆時点では、この方法はとても有用なものであると感じます。

Discussion