Cloudflare Workersのnodejs_compat_v2で何が変わったのか
nodejs_compat_v2 とは
nodejs_compat_v2 は、Cloudflare Workers ランタイムで Node.js API を有効にする互換性フラグです。 wrangler.toml ファイルに追加することで、組み込みの Node.js API とポリフィルを使用できるようになり、Node.js モジュールを "node:" 接頭辞なしでインポートし、nodejs_compat では利用できないポリフィルされた Node.js モジュールとグローバルを使用できるようになります。
従来の nodejs_compat フラグと比較して、nodejs_compat_v2 は、より多くの Node.js API とモジュールへのアクセスを提供します。 compatibility_dateが 2024-09-23 以降に設定されている限り、nodejs_compat 互換性フラグは nodejs_compat_v2 互換性フラグとまったく同じ動作をするようになります。
nodejs_compat_v2 を有効にすると、Wrangler は unenv を使用して Node.js API の使用を自動的に検出し、必要に応じてポリフィルを追加します。 これにより、サーバーレス関数のコンテキストでは動作しない Node.js API も解決しつつ、既存の npm パッケージとの互換性を高めることができます。
以下の各ランタイムのNode.js互換性カタログサイトで、nodejs_compat_v2 を有効にした場合に使用できる Node.js API の一覧を確認できます。workerdという項目がCloudflare Workersのランタイムがネイティブにサポートしているかで、wranglerの項目がWranglerがデプロイ時にポリフィルで吸収できるかです。
ネイティブなEventEmitterを使う
eventsがネイティブでサポートされたということは各モジュールで広く使われているEventEmitterもサポートされるということです。これを使ってみます。
npm create cloudflare@latest -- my-worker
cd my-worker
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-10-18"
compatibility_flags = ["nodejs_compat"] # nodejs_compat_v2として認識される
import { EventEmitter } from 'events';
export default {
async fetch(request, env, ctx): Promise<Response> {
const emitter = new EventEmitter();
emitter.on('hello', (...args: any[]) => {
console.log(...args);
});
emitter.emit('hello', 1, 2, 3);
return new Response('Hello World!');
},
} satisfies ExportedHandler<Env>;
npm run dev
curl http://localhost:8787/
[wrangler:inf] Ready on http://localhost:8787
1 2 3
[wrangler:inf] GET / 200 OK (9ms)
csv-stringifyを使ってみる
次にネイティブなstream.Transformが動作することを確認するために、csv-stringifyを使ってみます。
npm install csv-stringify
import { stringify } from 'csv-stringify';
export default {
async fetch(request, env, ctx): Promise<Response> {
const data = [
{ name: '田中太郎', age: 30, city: '東京' },
{ name: '佐藤花子', age: 25, city: '大阪' },
{ name: '鈴木一郎', age: 40, city: '名古屋' },
];
const csvData = await new Promise<string>((resolve, reject) => {
stringify(data, { header: true }, (err, output) => {
if (err) reject(err);
else resolve(output);
});
});
return new Response(csvData, {
headers: { 'Content-Type': 'text/csv' },
});
},
} satisfies ExportedHandler<Env>;
curl http://localhost:8787/
name,age,city
田中太郎,30,東京
佐藤花子,25,大阪
鈴木一郎,40,名古屋
osモジュールのポリフィルを確認する
osモジュールはネイティブでサポートされていないので、nodejs_compat_v2 を有効にしてもポリフィルされます。それがどのように動作するかを確認します。
import os from 'os';
export default {
async fetch(request, env, ctx): Promise<Response> {
const systemInfo = [
`ホスト名: ${os.hostname()}`,
`プラットフォーム: ${os.platform()}`,
`CPU アーキテクチャ: ${os.arch()}`,
`CPU コア数: ${os.cpus().length}`,
`空きメモリ: ${(os.freemem() / (1024 * 1024)).toFixed(2)} MB`,
`合計メモリ: ${(os.totalmem() / (1024 * 1024)).toFixed(2)} MB`
].join('\n');
return new Response(systemInfo, {
headers: { 'Content-Type': 'text/plain' },
});
},
} satisfies ExportedHandler<Env>;
curl http://localhost:8787/
ホスト名:
プラットフォーム: linux
CPU アーキテクチャ:
CPU コア数: 8
空きメモリ: 0.00 MB
合計メモリ: 0.00 MB
workerをCloudflare側でも動作させてみます。
wrangler dev -r
curl http://localhost:8787/
ホスト名:
プラットフォーム: linux
CPU アーキテクチャ:
CPU コア数: 8
空きメモリ: 0.00 MB
合計メモリ: 0.00 MB
実際にデプロイもしてみます。
wrangler deploy
❯ curl https://my-worker.laiso.workers.dev
ホスト名:
プラットフォーム: linux
CPU アーキテクチャ:
CPU コア数: 8
空きメモリ: 0.00 MB
合計メモリ: 0.00 MB
同じ結果を返すのでunenvのモックがあることがわかります。定義は以下から確認できます。
バンドルされたJSにもunenvのポリフィルが埋め込まれているのを確認できました。
wrangler deploy src/index.ts --dry-run --outdir out
grep "__unenv__" out/index.js
return Object.assign(fn2, { __unenv__: true });
}, { __unenv__: true });
if (prop === "__unenv__") {
__unenv__ = true;
__unenv__ = true;
__unenv__ = true;
__unenv__ = true;
__unenv__: { get: () => true }
Discussion