空雲 Blog

Eye catchCloudflare+Remix+Viteへブロクシステムを移植したときの問題

publication: 2024/03/02
update:2024/08/04

Cloudflare + Remix + Vite

RemixでViteを使用すると、開発時のホットリロードが爆速になるという利点が得られます。これを利用しない手はありません。ということで既存のCloudflare + Remixで作っていたシステムをViteでビルドできるように移植しました。

ちなみにこちらの記事を書いた時点では、Viteが含まれていないRemixで開発を行っていました。
https://next-blog.croud.jp/contents/eec22faf-3563-4a77-9a47-4dfddc604141

従来版Remixから移植する時に発生する問題

ランタイム認識問題

Remixを使っていること自体は変わらないのですが、Vite版を使用するか否かで以下のような違いが生じます。

Remixdevで使われるプログラムdevのランタイムimport時に選択されるモジュール
esbuildwrangleredge-runtimebrowser
Vitevitenode-rutimebrowser

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, immutable

public/_routes.json

{ "version": 1, "include": ["/*"], "exclude": ["/favicon.ico", "/assets/*"] }

まとめ

私はこのあたりの修正で運良く動かすことができましたが、相性の悪いnpmパッケージを踏んだら、地獄を見ることになりそうです。ここは賢く様子を見たほうが良いかもしれません。