Open3

vite environment api で vite + workerd をワンストップで動かす

mizchimizchi

今までの問題

vite dev server でビルドしたSSR含むプログラムを workerd 環境で動かすには、一度(フル)ビルドしてから wrangler (workerd) で起動し直す必要があった。

vite6 の environment api を使うと、実行環境とビルドサーバー(vite dev)を分離できるので、 vite dev server の のインクリメンタルビルドに載せたまま workerd で動かすことができる。

https://github.com/cloudflare/workers-sdk/tree/main/packages/vite-plugin-cloudflare

mizchimizchi

npx create-vite でスキャフォルドしたプロジェクトを想定

やりたいこと

worker/index.ts # workerd のエントリポイント
client/main.tsx # react spa app
vite.config.ts

ここから vite dev 一発で起動する状態を作りたい。

やっていく。


$ npm install -D @cloudflare/vite-plugin

vite.config.ts で指定

import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
  plugins: [cloudflare({ persistState: false })],
});

wrangler.toml で worker/index.ts を指定しつつ、アセットハンドラを設定

name = "<your-app-name>"
main = "./worker/index.ts"
compatibility_date = "2024-12-30"
assets = { not_found_handling = "single-page-application", binding = "ASSETS"}

これで vite dev で両方立ち上がる。

デプロイするときは、 vite build && wrangler deploy で おk

$ pnpm vite build --app 
vite v6.0.11 building for production...
✓ 27 modules transformed.
dist/client/index.html                  0.37 kB │ gzip:  0.27 kB
dist/client/assets/index-9COutgvQ.js  143.40 kB │ gzip: 46.16 kB
✓ built in 401ms
vite v6.0.11 building SSR bundle for production...
✓ 1 modules transformed.
dist/react_spa/wrangler.json  1.03 kB
dist/react_spa/index.js       0.35 kB
✓ built in 9ms

worker の実装サンプル

worker/index.ts
interface Env {
  ASSETS: Fetcher;
}

export default {
  fetch(request, env) {
    const url = new URL(request.url);
    console.log(url.pathname);

    if (url.pathname.startsWith("/api/")) {
      const endpoint = url.pathname.replace("/api/", "");
      return Response.json({
        endpoint,
      });
    }

    return env.ASSETS.fetch(request);
  },
} satisfies ExportedHandler<Env>;

今回の設定では静的アセットが優先されるので、 env.ASSETS 側のハンドラは多分来ない。

これをフロントエンドから呼ぶサンプル

client/App.tsx
import { useEffect, useState } from "react";

function App() {
  const [data, setData] = useState<any>(null);
  useEffect(() => {
    (async () => {
      const response = await fetch("/api/hello");
      const data = await response.json();
      setData(data);
    })();
  }, []);
  return (
    <>
      <h1>Vite + React</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </>
  );
}

export default App;

同一のサーバープロセスとして振る舞っている。

mizchimizchi

今回は単純なSPA+APIエンドポイントを一つにしただけだが、これによって workerd 経由で vite SSR してたフレームワークの開発体験が大幅に向上する。

具体的には remix cloudflare adapter や qwik 等。

多少は各フレームワーク側の対応も必要だが、これで workerd でしか使えないAPIをフルビルド無しで開発できる基盤が整ったと言える。