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上でエラーになる問題
1import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";23// eslint-disable-next-line @typescript-eslint/ban-ts-comment4// @ts-ignore - the server build file is generated by `remix vite:build`5// eslint-disable-next-line import/no-unresolved6import * as build from "../build/server";7import { getLoadContext } from "../load-context";89export const onRequest = createPagesFunctionHandler({10 build, // ここでエラー11 getLoadContext,12});
上記のエラーはVSCode上で表示されます。ビルドやデプロイ時にはエラーにならないものの気持ち悪いので原因を調べました。
このエラー表示を出さないためには、routes上にAPI用のエンドポイントを設置時、必ずloaderとactionの両方が揃っている必要があります。片方だけだとダメだという結果でした。
getLoadContextの型問題
Remixのドキュメントに従ってCloudflareから渡されるコンテキストを処理しようとすると、必要なものが消されてしまっており、まともに利用できません。
https://remix.run/docs/en/main/future/vite#augmenting-load-context
これを何とかするためには、本来渡されるはずのGetLoadContextFunctionを設定する必要があります。
load-context.ts
1import { GetLoadContextFunction } from "@remix-run/cloudflare-pages";2import { type PlatformProxy } from "wrangler";3import { initFetch } from "./app/init";4import { AppLoadContext } from "@remix-run/cloudflare";56// When using `wrangler.toml` to configure bindings,7// `wrangler types` will generate types for those bindings8// into the global `Env` interface.9// Need this empty interface so that typechecking passes10// even if no `wrangler.toml` exists.11// eslint-disable-next-line @typescript-eslint/no-empty-interface12interface Env {13 SECRET_KEY: string;14 DATABASE_URL: string;15}1617type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;1819declare module "@remix-run/cloudflare" {20 interface AppLoadContext {21 cloudflare: Cloudflare;22 }23}2425type GetLoadContext = (args: {26 request: Request;27 context: { cloudflare: Cloudflare };28}) => AppLoadContext;2930export const getLoadContext: GetLoadContext & GetLoadContextFunction<Env> = ({31 context,32 request,33}) => {34 const cloudflare = context.cloudflare;35 const env = cloudflare.env;36 const next = "next" in cloudflare ? cloudflare.next : undefined;37 initFetch(env, request, next);3839 return {40 ...context,41 ASSETS: { fetch },42 };43};
vite.config.ts
1import {2 vitePlugin as remix,3 cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,4} from "@remix-run/dev";5import { defineConfig } from "vite";6import tsconfigPaths from "vite-tsconfig-paths";7import { getLoadContext } from "./load-context";89export default defineConfig({10 plugins: [11 remixCloudflareDevProxy({12 getLoadContext,13 }),14 remix({}),15 tsconfigPaths(),16 ],1718 worker: {19 format: "es",20 },21});
全部functionsを通る問題
※ 現在のテンプレートには入っています
Remixの従来版のテンプレートには入っていて、Vite版には入っていないものがあります。
_headersと_routes.jsonです。特に後者が入っていないと、あらゆるコンテンツがfunctionから実行されるため、Workersのカウント数が跳ね上がります。
従来版のRemixとほぼ同等の設定内容は以下のようになります。これを追加しておけば、Workersの実行回数が節約できます。
public/_headers
1/favicon.ico2 Cache-Control: public, max-age=3600, s-maxage=36003/assets/*4 Cache-Control: public, max-age=31536000, immutable
public/_routes.json
1{2 "version": 1,3 "include": ["/*"],4 "exclude": ["/favicon.ico", "/assets/*"]5}
まとめ
私はこのあたりの修正で運良く動かすことができましたが、相性の悪いnpmパッケージを踏んだら、地獄を見ることになりそうです。ここは賢く様子を見たほうが良いかもしれません。