remix上でcloudflare workersのdurable objectを使う
概要
remixアプリケーションをcloudflare workersのmodule workerとして実装し、
remixからdurable objectsを使ってみました。
この記事では簡単なカウンターの実装例を紹介します。
ソースコードと実行方法
remix-durable-object-counterをチェックアウトして、以下のコマンドを実行して下さい。
ローカルでremixが起動します。
npm install
npm run dev
実装の簡単な説明
durable objectsを使うremixアプリケーションの実装
module workerを実装してdurable objectsを使うように設定すると、remixのload関数やaction関数が実行される際に引数としてdurable objectsを操作するオブジェクトが渡されます。
カウンターをインクリメントする実装は以下のようになります。
app/routes/index.tsx
import { json } from "@remix-run/cloudflare";
import type {
LoaderFunction,
DataFunctionArgs,
ActionFunction,
} from "@remix-run/cloudflare";
import { Form, useActionData, useLoaderData } from "@remix-run/react";
export const loader: LoaderFunction = async ({
request,
context,
}: DataFunctionArgs) => {
const url = new URL(request.url);
const counter = context.COUNTER as DurableObjectNamespace;
const id = counter.idFromName("A");
const obj = counter.get(id);
const resp = await obj.fetch(`${url.origin}/current`);
const count = await resp.text();
return json(count);
};
export const action: ActionFunction = async ({
request,
context,
}: DataFunctionArgs) => {
const url = new URL(request.url);
const counter = context.COUNTER as DurableObjectNamespace;
const id = counter.idFromName("A");
const obj = counter.get(id);
const resp = await obj.fetch(`${url.origin}/increment`);
const count = await resp.text();
return count;
};
export default function Index() {
const count = useLoaderData();
const actionData = useActionData();
return (
<div>
<h1>Welcome to Remix on cloudflare workers!</h1>
<div>count: {count}</div>
{actionData && <div>action result: {actionData}</div>}
<Form method="post">
<button type="submit">increment</button>
</Form>
</div>
);
}
module worker実装
durable objectsを使うために、remixアプリケーションを以下のようなmodule workerとして実装します。
worker/index.ts
import { createFetch } from "remix-cloudflare-workers-fetch";
import * as build from "../build";
//@ts-ignore
import assetJson from "__STATIC_CONTENT_MANIFEST";
import type { ServerBuild } from "remix-cloudflare-workers-fetch";
import { Counter } from "./counter";
const fetch = createFetch({
build: build as unknown as ServerBuild,
assetJson,
mode: "production",
options: {
cacheControl: {
bypassCache: true,
},
},
});
export default {
fetch,
};
export { Counter };
durable objectsとしてのCounterクラスの実装自体は公式ドキュメントのカウンター実装例をほぼそのまま使っています。
remixから使うための改修は不要です。
remixアプリケーションは以下のようにimportします。
import * as build from "../build";
こちらのimportはremixの静的ファイルをworkers sitesを使って配布するためのものです。
//@ts-ignore
import assetJson from "__STATIC_CONTENT_MANIFEST";
workers sitesについては公式ドキュメントを参照して下さい。
remixのビルド
remixアプリケーションとmodule workerを別々にビルドするために、remix.config.jsからserverプロパティを削除します。
remix.config.js
/**
* @type {import('@remix-run/dev').AppConfig}
*/
module.exports = {
serverBuildTarget: "cloudflare-workers",
// server: "./server.js",
devServerBroadcastDelay: 1000,
ignoredRouteFiles: ["**/.*"],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",
// publicPath: "/build/",
};
この設定にして以下のコマンドを実行するとサーバーサイドのモジュールが/buildに出力されます。
npx remix build
remix buildで作成したモジュールはworker/index.tsでimportしています。
module workerのビルド
サンプルコードではworkerをworkerディレクトリに実装しています。
workerのビルドはesbuildで行います。
ビルドの設定は以下の通りです。
build-worker.ts
import { build } from "esbuild";
build({
entryPoints: ["./worker/index.ts"],
bundle: true,
sourcemap: true,
format: "esm",
outfile: "dist/index.mjs",
minify: true,
external: ["__STATIC_CONTENT_MANIFEST"],
}).catch(() => process.exit(1));
以下のコマンドでビルドを実行します。
npx ts-node build-worker.ts
ビルドが成功するとdist/index.mjsが作成されます。
workerの実行
wrangler.tomlを以下のように設定します。
name = "remix-durable-object-counter"
main = "dist/index.mjs"
workers_dev = true
compatibility_date = "2022-05-18"
[site]
bucket = "./public"
[durable_objects]
bindings = [{ name = "COUNTER", class_name = "Counter" }]
以下のコマンドで実行します。
npx wrangler dev
以上
Discussion