Denoによるブラウザ向けバンドル

denoでブラウザ向けのプロジェクトをビルドする場合、nodeとの最大の違いはパッケージの依存解決です。

denoのパッケージ参照にはpackage.jsonではなくimport-maps.jsonを用います。
Import mapsは素朴なマップであり、明示的に記載されたルール以外の依存解決を行いません。

ライブラリの依存解決には、以下の2つのアプローチがありえます。

  • import-maps.jsonにCDNのパッケージリポジトリを記載し、CDNによる依存解決を利用する
  • ジェネレータを利用してimport-maps.jsonを生成する。依存解決はローカルのnpmリポジトリを利用

deno向けCDNの問題

denoがESMのHTTPインポートに対応しているため、denoのパッケージインポートはCDNを利用するような説明が一般的なのですが、CDNによる依存解決には課題があります。

インポートしたライブラリが参照している前提ライブラリはCDN内のURLを参照しています。
このメカニズムでは、同じライブラリの別バージョンを重複インポートすることがあります。

ファイルサイズの面でも問題がありますが、副作用を利用したライブラリの場合には重複が原因で動作不能になります。

CDNの機能改善により解決していくのかもしれませんが、ライブラリユーザーができる対策は何もありません。

Import mapsジェネレータ

CDNを利用しない場合のパッケージ依存解決の手順は以下のとおりです。

  1. パッケージをpackage.jsonに記述し、npm install
  2. ジェネレータを用いてimport-maps.jsonを生成
  3. denoベースのバンドラでプロジェクトをビルド

ジェネレータの例として、 gen-import-maps-jsonを利用できます。
gen-import-maps-jsonの出力は、node_modules/以下のpathを参照しています。

ただしpathの記述はビルドツールにより異なるため注意が必要です。

// gen-import-maps-jsonの出力。esbuildで動作する
"react": "/node_modules/react/index.js",
// deno bundleで動作する
"react": "./node_modules/react/index.js",

なお、パッケージ管理にはnpmを利用することになるため、nodeとdenoの両方が必要な構成になります。

ブラウザ向けバンドラ

denoにはdeno bundleが標準付属しています。
ただし、各種ブラウザ向けの出力やCSSの扱いなどに制約があるため、プロジェクト用途に適合するかはあらかじめ確認すべきでしょう。
また、ESM形式でないライブラリを解決できない制約もあります。

ほかに esbuildのdenoポートがあり、プラグインを含め拡張性はdeno標準のバンドラより高いと言えます。

esbuildのビルドスクリプト例は以下のとおりです。なお、esbuildについてはURLインポートで動作するため事前セットアップは不要です。

import * as esbuild from 'https://deno.land/x/[email protected]/mod.js'

await esbuild.build({
    entryPoints: ["./src/index.tsx"],
    outfile: "./dist/bundle.js",
    format: "esm",
    bundle: true,
});

esbuild.stop()

import-maps.jsonベースのプロジェクト構成をとっていれば、バンドラは差し替え可能です(ただし、バンドラ個別の機能に依存する部分をのぞく)。

denoが標準採用している swcの強化により、いずれ標準のビルドツールに移行できる可能性もあります。

現時点ではdenoをグルーコード部分でしか生かせていない構成ですが、連続的に移行するためには必要なステップに見えます。

中馬崇尋
Chuma Takahiro