Open6

React Routerのvite+wrangler v4化作業メモ ~環境分けでturborepoに注意しろ~

uruositeuruosite

RR7で、Cloudflare Viteプラグイン対応と、Wrangler v4対応を一気に行う。

Wrangler v4

ランタイム型が書き出されるようになった

React Router v7 (旧Remix)をCloudflare Workersで動かす場合、wrangler typesで worker-configuration.d.ts を生成する。

昔はこういう内容だったが、wrangler CLIをv4にしたらクソデカくなった。

worker-configuration.d.ts
// Generated by Wrangler by running `wrangler types`

interface Env {
	SUPABASE_URL: string;
	SUPABASE_ANON_KEY: string;
}

AFTER:

worker-configuration.d.ts
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: 609019d24d79c2fdca1f4fb535a9b39c)
// Runtime types generated with workerd@1.20250416.0 2025-03-17 nodejs_compat
declare namespace Cloudflare {
	interface Env {
		SUPABASE_URL: string;
		SUPABASE_ANON_KEY: string;
	}
}
interface Env extends Cloudflare.Env {}

// Begin runtime types
/*! *****************************************************************************
Copyright (c) Cloudflare. All rights reserved.
Copyright (c) Microsoft Corporation. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */

// 以下、延々とNodejs互換のための型が書かれている

これコミットしていいのか??? →コミットしていいはず。CIで生成すると、.dev.varsをコピーするとかしないと、シークレットだけEnvから欠けてしまう。

uruositeuruosite

なんでここにランタイム型があるんだよ

v3.66以降では、 --experimental-include-runtime フラグを有効にすると、.wrangler/types/runtime.d.tsにランタイム型が出力されていた。

If you are running a version of Wrangler that is greater than 3.66.0 but below 4.0.0, you will need to include the --experimental-include-runtime flag. During its experimental release, runtime types were output to a separate file (.wrangler/types/runtime.d.ts by default). If you have an older version of Wrangler, you can access runtime types through the @cloudflare/workers-types package.

v4でフラグのexperimentalが外れ、デフォルトでtrueになった。

https://developers.cloudflare.com/workers/wrangler/commands/#types

--include-runtime (default: true)
Whether to generate runtime types based on thecompatibility_date and compatibility_flags in your config file.

https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/CHANGELOG.md#400

wrangler types will now produce one file that contains both Env types and runtime types based on your compatibility date and flags. This is located at worker-configuration.d.ts by default.

で、React routerのテンプレートのwrangler.tomlを読んで、クソデカファイルが生成された。

uruositeuruosite

v4にしたら必要な対応

https://github.com/remix-run/react-router-templates/pull/109/files

compatibility dateを更新する

wranglerを更新した日にする

rr本体の更新

そもそもReact Routerが古い場合は最新のバージョンにする。

@cloudflare/workers-typesを消す

https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/CHANGELOG.md#400

If you were previously using @cloudflare/workers-types we recommend you run uninstall (e.g. npm uninstall @cloudflare/workers-types) and run wrangler types instead. Note that @cloudflare/workers-types will continue to be published.

tsconfig.cloudflare.json
- "types": ["@cloudflare/workers-types", "vite/client"],
+ "types": ["vite/client"],
tsconfig.node.json
- "types": ["@cloudflare/workers-types", "node"],
+ "types": ["node"],

load-context.tsの修正

CloudflareEnvironment は生成されるから不要になった。

load-context.ts
- declare global {
-   interface CloudflareEnvironment extends Env {}
- }

コンテキストはこんな感じになる。D1を使うならここにd1とか生やす。

load-context.ts
  export interface AppLoadContext {
    cloudflare: {
      env: Env
      ctx: Omit<ExecutionContext, "props">
    }
  }
uruositeuruosite

@cloudflare/vite-pluginを使う

https://developers.cloudflare.com/workers/vite-plugin/

ありがたいことに、React Routerを簡単に使えるプラグインが作られた。

RR7の場合、このような設定になる。バンドルの設定が必要なくなり、めちゃくちゃスッキリした。

vite.config.ts
import { cloudflare } from "@cloudflare/vite-plugin"
import { reactRouter } from "@react-router/dev/vite"
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "vite"
import tsconfigPaths from "vite-tsconfig-paths"

export default defineConfig({
  plugins: [
    cloudflare({ viteEnvironment: { name: "ssr" } }),
    tailwindcss(),
    reactRouter(),
    tsconfigPaths(),
  ],
})

つまり、mainの指定もtsを直接指定する形になる。

wrangler.jsonc
  "main": "./workers/app.ts",

CIを直す必要が生じた

CIでwrangler deploy --envをしていると、エラー

You need to set the environment in your build tool, rather than via Wrangler

https://developers.cloudflare.com/workers/vite-plugin/reference/cloudflare-environments/

CLOUDFLARE_ENVでビルド時に環境を決定しろということらしい。

今までは--envが決定権を持っていたが、@cloudflare/vite-pluginを使う場合は CLOUDFLARE_ENV が環境を決定する。

uruositeuruosite

Environments機能による名前分けについて

https://developers.cloudflare.com/workers/wrangler/environments/

まずViteのcloudflareプラグインでビルドした場合の挙動について。

The default Vite environment name for a Worker is always the top-level Worker name. This enables you to reference the Worker consistently in your Vite config when using multiple Cloudflare environments. See Vite Environments for more information.

https://developers.cloudflare.com/workers/vite-plugin/reference/vite-environments/#environment-configuration

vite configのenvironmentsは、常にtop-levelのワーカー名になる。

When you create an environment, Cloudflare effectively creates a new Worker with the name <top-level-name>-<environment-name>. For example, a Worker project named my-worker with an environment dev would deploy as a Worker named my-worker-dev.

ただし、実際のワーカー名は、環境を接尾辞に付けますよ とも書かれている。

試しに CLOUDFLARE_ENV=dev でビルドすると、このようにnameが変化する。

build/server/wrangler.json
{
  "topLevelName": "foobar-frontend",
  ...
  "name": "foobar-dev",
  "main": "index.js",
  ...
}

よく見ると "legacy_env": trueという項目がある。

legacy_env: trueの場合

v3~v4.12.0時点では、trueがデフォルトだった。

legacy_env: falseの場合

「Service Environment」という機能がGAになれば、falseがデフォルトになるらしい。

最終的な挙動の違いは謎。env内にnameを書くことが禁止される(そもそも書く必要はないが)

uruositeuruosite

環境分けする際はturborepoに注意

      - name: "ビルド"
        run: pnpm build
        env:
          # ビルド時にこの環境変数で環境を判定する
          CLOUDFLARE_ENV: ${{ inputs.wrangler_env }}

      - name: "Cloudflareにデプロイ"
        id: deploy
        uses: cloudflare/wrangler-action@v3
        with:
          wranglerVersion: "3.114.6"
          command: deploy
          apiToken: ${{ secrets.cf_api_token }}
          accountId: ${{ secrets.cf_account_id }}

が、pnpmのモノレポでCLOUDFLARE_ENVをうまく渡せない。nameが変化しない現象が発生。

→ turborepoがENVをガン無視していたことが判明!!!
buildタスクの設定で"env": ["CLOUDFLARE_ENV"], を使おう。

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [
    ".env"
  ],
  "globalEnv": [
    "NODE_ENV"
  ],
  "tasks": {
    "build": {
      "env": [
 +       "CLOUDFLARE_ENV"
      ],
      "dependsOn": [
        "^build"
      ],
      "inputs": [
        "$TURBO_DEFAULT$",
        ".env*",
        ".dev.vars"
      ],
      "outputs": [
        "build/**",
        "dist/**"
      ]
    }
  }
}