CF WorkersでNodeJS依存ライブラリが使えない?できらぁ!!!~黒魔術ViteプラグインでNodeJS依存関数を透過的に呼ぶ話~
この記事は、Cloudflare Advent Calendar 2025の二十日目の記事です。
はじめに
みなさん、Cloudflare Workers使ってますか!? 安くて、他のCloudflare製品群とService Bindingsで簡単に結合出来て、課金体系がLLMと相性が良くて最高なので、僕はよく使っています。
これらのメリットは、Cloudflare Workers が V8 ベースの独自ランタイムで動くことに起因しますが、同時に大きなデメリットももたらしてしまいます。つまり、@napi-rs/canvas、pdfjs-dist、playwright など NodeJS への依存が強いライブラリを利用できない、という点です。
これまで僕は、NodeJS 依存部分を Stateless なコンテナとして Cloud Run などに切り出し、HonoRPC 経由で Workers から呼び出すという、大変冗長な事をしていました。(まぁ Workers が BFF/プロキシ用途を主に想定していることを踏まえると、この構成自体は筋が悪いわけではないと思いますが...)
このCF Workersユーザーが長年悩んできた問題にピリオドを打つべく、超黒魔術的なViteプラグインを作ったので、紹介したいと思います。
できたもの
というわけで、create-nodejs-fnというViteプラグインを作って、公開しました。まず初めに、当Viteプラグインを導入した後にCF Workersで実行できる、この世の終わりみたいなコードをお見せしましょう。
import { createCanvas } from "@napi-rs/canvas";
import { nodejsFn } from "./__generated__/create-nodejs-fn.runtime";
export const renderClock = nodejsFn(async () => {
const canvas = createCanvas(600, 200);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#1a1a2e";
ctx.fillRect(0, 0, 600, 200);
ctx.font = "bold 36px 'Noto Sans CJK JP', 'Noto Color Emoji', sans-serif";
ctx.fillStyle = "#eee";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const now = new Date().toISOString();
ctx.fillText(`🕐 ${now}`, 300, 100);
return await canvas.toDataURLAsync("image/webp");
});
import { Hono } from "hono";
import { renderClock } from "./clock.container";
const app = new Hono();
app.get("/clock", async (c) => {
const webpDataUrl = await renderClock();
return fetch(webpDataUrl);
});
export { NodejsFnContainer } from "./__generated__/create-nodejs-fn.do";
export default { fetch: app.fetch };
はい。@napi-rs/canvasという、どう見てもCF Workersで動かないライブラリが、平然とsrc/index.tsから直接importされ、平然と呼び出されています。特別目に付くのは、nodejsFnという謎の関数で@napi-rs/canvas呼び出し部分がラップされているぐらいでしょうか?
そして、このコードは動きます。このコードをnpm run devで起動し、http://localhost:5173/clockにアクセスすると...

ちゃんとCanvasでレンダリングされた画像が表示されます。しれっと絵文字も使えてますね。
前述のリポジトリのQuick Startとまったく同じコードなので、皆さんの手元でも動かしてみてください(記事では省いていますが、パッケージのインストールや軽いセットアップが必要です)。
はい。こんな奇妙なことがなぜできるのでしょうか?
黒魔術のからくり
この黒魔術は、Cloudflare Containersのリリースと、@cloudflare/vite-pluginの成熟に支えられています。
Cloudflare Containers とは
Cloudflare Containersとは、Cloudflareが最近公開したサーバーレスコンテナプラットフォームで、Cloudflare Workersから呼び出せるDockerコンテナを簡単にデプロイできます。
Dockerコンテナが動くということは、何でも動かせるわけです。もちろんNodeJSも。ので、当プラグインはこれをフル活用します。
余談ですが、CF ContainersはGCPのCloudRunと色々異なり、プログラマブルにStatefullなふるまいを制御できたりします。これをうまく使った製品が、Cloudflare Sandboxです。
@cloudflare/vite-plugin とは
Cloudflare Workers を開発する場合、以前は wrangler CLI で開発環境の立ち上げ、ビルド、デプロイを行うのが一般的で、Vite でバンドルすることはできませんでした。結果として Vite プラグインが使えず、フロントエンドも絡めた開発などでは、面倒くさい事態が多発していました。
そんな中で登場したのが @cloudflare/vite-plugin です。こいつ使えば、Hot Reload や Service Binding など、wrangler dev 相当の開発体験を維持したまま Vite プラグインをぶっ刺せます(import {env} from "cloudflare:workers"出来るのようになったのも効いてますね)。さらに Vite Environment API の上で動くので、フロントエンドともいい感じに統合して実装できます。
とにかく、CF Workersの開発でもViteプラグインを差し込めるようになったわけです。
create-nodejs-fnが何をするか
create-nodejs-fnは、前述の二つの技術を基に、次の事をするViteプラグインです。
- nodejsFn部分の検出と解析:
*.container.tsにマッチするファイルをts-morphで解析し、nodejsFnで囲われている関数を抽出します。 - Cap'n WebのHTTPサーバー生成: 抽出した関数群を、
capnwebというRPCライブラリを使ったRPC on HTTPサーバーとしてラップし、esbuildでバンドルします。 - Dockerfile自動生成: プラグインのオプション(
baseImage、systemPackages、preInstallCommandsなど)を基に、コンテナをビルドするためのDockerfileを自動生成します。@napi-rs/canvasのようなネイティブ依存がある場合でも、systemPackagesに必要なパッケージを指定するだけでOKです。 - プロキシコード生: Workers側で
nodejsFnを呼び出している部分を、CF Containers経由でRPCリクエストを発行するプロキシコードに差し替えます。ViteのresolveIdフックで*.container.tsのimportを横取りし、生成済みのプロキシファイルに解決することで実現しています。
最高にロックでマッドですね(自画自賛)。要するに、nodejsFnで囲っておけば、その中身は自動的にコンテナ化され、Workers側からは透過的に呼び出せる開発体験になります。
ちなみに、そもそもCF WorkersからNodeJS依存なコードを呼ぶなら、外部のNodeJSランタイムに外出しして、HTTPで呼ぶことになるので、このプラグインのオーバーヘッドはそれと同等といえます。むしろ、手動でHTTPクライアントを書いたり、型定義を二重管理したりする手間が省けるので、トータルでは開発効率が上がるはずです。
このプラグインのこだわり
このプラグインは大半Vibe Codingで作ったわけですが、以下のような機能を備えており、出来るだけスムーズに開発できるようになっています。
1. initコマンドによる自動セットアップ
pnpm exec create-nodejs-fn initを実行すると、既存のWorkersプロジェクトに必要な設定を自動で追加してくれます。具体的には
-
wrangler.jsoncへのcontainers設定の追加 - Durable Objectのバインディング設定
-
src/__generated__/ディレクトリの作成 - エントリファイル(
index.tsなど)へのNodejsFnContainerのexport追加 -
.gitignoreへの生成ファイルの追加
これで、vite.config.tsに多少設定を追記すると、すぐこのプラグインが使える状態になります!!
2. Hot Reloadによるコンテナ自動リビルド
npm run dev中に*.container.tsファイルを編集すると、プラグインが変更を検知して自動でコンテナをリビルドし、Vite開発サーバーを再起動してくれます。debounce処理も入っているので、連続した変更でも無駄なリビルドが走らないようになっています。
3. 型安全なRPC呼び出し
生成されるプロキシコードは、元のnodejsFnの型情報を保持しているので、Workers側での呼び出しは完全に型安全です。TypeScriptの恩恵を受けながら、まるでローカル関数を呼ぶかのような体験で開発できます。
4. containerKeyによるインスタンス制御
nodejsFnの第2引数でcontainerKeyを指定すると、引数に応じて異なるコンテナインスタンスにルーティングできます。これで、負荷分散したり、頑張ったらStatefulなこともできるはずです。
おわりに
ここまで読んでくださりありがとうございます。もうお分かりかと思いますが、このプラグインは非常に危険でバグったらヤバいので、本番環境では使わないでください。
それでも、かなり面白いものだと思うので、ぜひ手元で動かしてみてください!!!きっと変な笑いが出ると思います。面白い使い方が出来たら、Twitterなどでツイートしてもらえると嬉しいです
Discussion