Deno v1.15で導入されたNode.js互換モードについて
はじめに
2021/10/12にDeno v1.15がリリースされました。
この記事では、Deno v1.15で新しく導入されたNode.jsの互換モードについて解説します。
Node.jsの互換モードとは?
まず、以下のようなJavaScriptファイルがあったとします。
import { EventEmitter } from "events";
const emitter = new EventEmitter();
emitter.on("foo", () => console.log("foo"));
emitter.emit("foo");
Node.jsの組み込みモジュールであるevents
モジュールを利用しているため、通常ではこのファイルをDenoで実行することはできません。
$ deno run main.mjs
error: Relative import path "events" not prefixed with / or ./ or ../ from "file:///home/uki00a/ghq/github.com/uki00a/deno-sandbox/main.mjs"
at file:///home/uki00a/ghq/github.com/uki00a/deno-sandbox/main.mjs:1:30
それでは、このファイルを--compat
オプションを付けて実行してみます。
$ deno run --compat --unstable main.mjs
foo
このように--compat
オプションを付与することで、Node.jsの互換モードが有効化され、DenoからNode.jsの組み込みモジュールを利用できるようになります。
また、このモードではglobal
やprocess
などのオブジェクトを参照することもできます。
console.log(global.Buffer.from('Hello'));
console.log(process.version);
どうやって実現されてるの?
内部的にはImport mapsとdeno_std/node
を併用することで実現されています。
まずはじめにこれら2つについて説明します。
Import maps
Import mapsとはWICGで提案されている機能であり、Denoや一部のブラウザなどでサポートされています。
例えば、以下のような内容のJSONファイルを用意します。
{
"imports": {
"std/": "https://deno.land/std@0.111.0/",
"redis": "https://deno.land/x/redis@v0.24.0/mod.ts"
},
"scopes": {}
}
このようなファイルを用意しておくことで、ソースコード中では以下のようにしてモジュールを読み込むことができます。
import { connect } from "redis"; // => https://deno.land/x/redis@v0.24.0/mod.ts
import * as path from "std/path/mod.ts"; // => https://deno.land/std@0.111.0/path/mod.ts
const redis = await connect({ hostname: "localhost", port: 6379 });
console.log(await redis.get("foo"));
await redis.quit();
Import mapsを利用する際は、Denoを実行する際に--import-map
オプションでJSONファイルのパスを指定する必要があります。
$ deno run --import-map=import_map.json --allow-net main.ts
deno_std/node
deno_stdとはDeno公式によって提供されているDenoの標準モジュールです。
このdeno_std
はNode.jsの互換レイヤとしてdeno_std/nodeを提供しています。
これを利用することで、Common JS形式のJavaScriptモジュールを読み込むことができます。
import { createRequire } from "https://deno.land/std@0.111.0/node/module.ts";
const require = createRequire(import.meta.url);
// 組み込みモジュールの読み込み
const EventEmitter = require("events");
// Common JS形式のモジュールの読み込み
const add = require("./add");
上記ファイルを実行するには、deno_std v0.111.0の時点では--unstable
が必要です。
$ deno run --unstable --allow-read main.ts
Node.js互換モードの内部実装
Denoの起動時に--compat
オプションが指定されると、Denoプロセスは内部で保持しているImport mapsにdeno_std/node/events.tsやdeno_std/node/net.tsなどの各種Polyfillモジュールを登録していきます。
そして、Denoの引数として与えられたメインモジュールを読み込む直前にdeno_std/node/global.tsを読み込むことで、各種グローバル変数などを登録しています。
これにより、Node.jsの組み込みモジュールやglobal
などのグローバル変数の透過的な読み込みが実現されています。
deno_std/nodeでサポートされている組み込みモジュールについて
現時点(deno_std v0.111.0)でサポートされている組み込みモジュールの一覧は、以下から確認できます。
まだ全てのモジュールがサポートされているわけではありませんが、直近でもnet
やdns
などのモジュールが実装されており、今後徐々に互換性は向上していくと思われます。
実際にいつ使えばいいの?
個人的には既存のNode.jsで書かれたツールなどに変更を加えることなく、そのままDenoで動かしたい場合に使用するのがよいと思っています。
理由については後述しますが、DenoにはNode.js互換モード以外にもNode.jsの資産を活かすための代替手段があるためです。
Node.js互換モードの代替手段
今回のリリースでNode.jsの互換モードが導入されましたが、実はこれを使用しなくとも、Denoでは既存のNode.jsの資産を活かす手段があります。
例えば、esm.shやSkypackなどのCDNは、Node.jsの組み込みモジュールの読み込みを検出すると、自動的にdeno_std/node
を読み込むように置換してくれます。
これらのCDNからnpmパッケージをimportすることで、Node.js互換モードを使用せずとも既存の資産をある程度活かすことができます。
そのため、実用上は、すぐにNode.js互換モードを使用するのではなく、まずはこれらのCDNからパッケージを使用できないか試してみるとよいのではないかと個人的には思います。
おわりに
この記事ではDeno v1.15で導入されたNode.js互換モードについて解説しました。
Deno v1.15には、これ以外にも様々な機能が追加されています。
例)
- 入れ子のテストケースへの実験的なサポート
-
deno uninstall
コマンド (deno install
コマンドでインストールされたツールのアンインストール用コマンド) -
Deno.kill
/Deno.resolveDns
/URLPattern
などの安定化
もし興味がありましたら、ぜひ試してみてください!
Discussion