Cloudflare+Remix+Viteへブロクシステムを移植したときの問題
Cloudflare + Remix + Vite
RemixでViteを使用すると、開発時のホットリロードが爆速になるという利点が得られます。これを利用しない手はありません。ということで既存のCloudflare + Remixで作っていたシステムをViteでビルドできるように移植しました。
ちなみにこちらの記事を書いた時点では、Viteが含まれていないRemixで開発を行っていました。
https://next-blog.croud.jp/contents/eec22faf-3563-4a77-9a47-4dfddc604141
従来版Remixから移植する時に発生する問題
ランタイム認識問題
Remixを使っていること自体は変わらないのですが、Vite版を使用するか否かで以下のような違いが生じます。
| Remix | devで使われるプログラム | devのランタイム | import時に選択されるモジュール |
|---|---|---|---|
| esbuild | wrangler | edge-runtime | browser |
| Vite | vite | node-rutime | browser |
vite版でdev起動するとnode-runtimeが使われます。しかしimport時に呼び出されるnpmパッケージはnodeではなく、browserでexportされているものが選択されます。これが仕様なのかバグなのかわかりませんが、違うRuntimeを想定したモジュールが呼び出されてしまうのです。
問題になったのはJWT関係で使っていたjoseというパッケージです。WebAPI前提でcryptoを直接呼び出していたため、Node.jsの18系統ではエラーになりました。Node.jsのWebAPI対応が強化された20系統にしたところエラーが出なくなりましたが、この問題は他のパッケージでも影響がありそうです。
なんかデプロイできない問題
ローカルでdevもstart起動も完璧するのを確認し、いざデプロイすると、なんのエラーも出さずにデプロイが失敗しました。完全に原因不明です。こうなるとデプロイ可能な状態になるまで、コードを切り取っていくしかありません。少しずつコードを削っていった結果、コードハイライトに使っているreact-syntax-highlighterを使っているとデプロイ不能になるという結論に至りました。パッケージをprism-react-rendererに入れ替えた結果、問題なくデプロイされました。
なぜreact-syntax-highlighterだとデプロイできないのか、未だ原因はわかっていません。
functions/[[path]].ts がVSCode上でエラーになる問題
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
// eslint-disable-next-line import/no-unresolved
import * as build from "../build/server";
import { getLoadContext } from "../load-context";
export const onRequest = createPagesFunctionHandler({
build, // ここでエラー
getLoadContext,
});上記のエラーはVSCode上で表示されます。ビルドやデプロイ時にはエラーにならないものの気持ち悪いので原因を調べました。
このエラー表示を出さないためには、routes上にAPI用のエンドポイントを設置時、必ずloaderとactionの両方が揃っている必要があります。片方だけだとダメだという結果でした。
getLoadContextの型問題
Remixのドキュメントに従ってCloudflareから渡されるコンテキストを処理しようとすると、必要なものが消されてしまっており、まともに利用できません。
https://remix.run/docs/en/main/future/vite#augmenting-load-context
これを何とかするためには、本来渡されるはずのGetLoadContextFunctionを設定する必要があります。
load-context.ts
import { GetLoadContextFunction } from "@remix-run/cloudflare-pages";
import { type PlatformProxy } from "wrangler";
import { initFetch } from "./app/init";
import { AppLoadContext } from "@remix-run/cloudflare";
// When using `wrangler.toml` to configure bindings,
// `wrangler types` will generate types for those bindings
// into the global `Env` interface.
// Need this empty interface so that typechecking passes
// even if no `wrangler.toml` exists.
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Env {
SECRET_KEY: string;
DATABASE_URL: string;
}
type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;
declare module "@remix-run/cloudflare" {
interface AppLoadContext {
cloudflare: Cloudflare;
}
}
type GetLoadContext = (args: {
request: Request;
context: { cloudflare: Cloudflare };
}) => AppLoadContext;
export const getLoadContext: GetLoadContext & GetLoadContextFunction<Env> = ({
context,
request,
}) => {
const cloudflare = context.cloudflare;
const env = cloudflare.env;
const next = "next" in cloudflare ? cloudflare.next : undefined;
initFetch(env, request, next);
return {
...context,
ASSETS: { fetch },
};
};vite.config.ts
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { getLoadContext } from "./load-context";
export default defineConfig({
plugins: [
remixCloudflareDevProxy({
getLoadContext,
}),
remix({}),
tsconfigPaths(),
],
worker: {
format: "es",
},
});全部functionsを通る問題
※ 現在のテンプレートには入っています
Remixの従来版のテンプレートには入っていて、Vite版には入っていないものがあります。
_headersと_routes.jsonです。特に後者が入っていないと、あらゆるコンテンツがfunctionから実行されるため、Workersのカウント数が跳ね上がります。
従来版のRemixとほぼ同等の設定内容は以下のようになります。これを追加しておけば、Workersの実行回数が節約できます。
public/_headers
/favicon.ico
Cache-Control: public, max-age=3600, s-maxage=3600
/assets/*
Cache-Control: public, max-age=31536000, immutablepublic/_routes.json
{
"version": 1,
"include": ["/*"],
"exclude": ["/favicon.ico", "/assets/*"]
}まとめ
私はこのあたりの修正で運良く動かすことができましたが、相性の悪いnpmパッケージを踏んだら、地獄を見ることになりそうです。ここは賢く様子を見たほうが良いかもしれません。