はじめに
先日、VoidZero から Vite+のAlpha版が発表された。Vite+とは、フロントエンドの各種ツールをワンセットにして管理しやすくした統合ツールチェーンである。
個人的にもうこれでいいだろって思ってしまうほどの良いツールなので、触ってない方はぜひ試してほしい。
試しに現在作成している個人開発アプリのプロジェクトに導入してみた。このプロジェクトでは GitHub Actions を利用して Cloudflare にデプロイするワークフローが組まれており、Vite+ の導入後に初めて CI/CD を回したところ、色々と面倒なエラーで詰まってしまった。
Vite+ のCIガイドを参考にしつつも解決せず、最終的に Vite+ のコマンド vp run と Alchemy の相性の悪さが原因だと分かった。
技術スタックは以下の通り。
- フレームワーク: TanStack Start (React)
-
ビルドツール: Vite+ (
vp) -
IaC / デプロイ: Alchemy (
alchemy deploy) - ホスティング: Cloudflare Workers
Alchemy は TypeScript で Cloudflare のリソース(Workers・R2・D1 など)を宣言的に管理する IaC ツールで、alchemy.run.ts にリソース定義を書いて alchemy deploy を実行するだけでインフラのプロビジョニングとデプロイが完結する。TanStack Start のような SSR フレームワークを Cloudflare Workers にデプロイするための TanStackStart ヘルパーも提供している。
https://alchemy.run/guides/cloudflare-tanstack-start/
第1のエラー:Command "vite" not found
エラー内容
GitHub Actionsのエラーログを確認すると以下の内容が流れていた。
Viteが見つからないらしい...
ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command "vite" not found
Error: Command failed with exit code 254: pnpm vite build
at async Website (alchemy/lib/cloudflare/website.js:139:9)
at async Vite (alchemy/lib/cloudflare/vite/vite.js:38:12)
at async TanStackStart (alchemy/lib/cloudflare/tanstack-start/tanstack-start.js:9:12)
▼ Deployステップにてpnpm alchemy deployで失敗している。
- name: Deploy
run: pnpm alchemy deploy --stage ${{ env.STAGE }} --adopt
env:
ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
原因
Vite消失の原因は2つの事情が重なって起きていた。
① Vite+ への移行で node_modules/.bin/vite が消える
Vite+ に移行すると、package.json に以下が自動で設定される。
"devDependencies": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest"
},
"pnpm": {
"overrides": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest"
}
}
pnpm.overrides により、vite パッケージが @voidzero-dev/vite-plus-core に丸ごと差し替えられる。これは既存の Vite プラグインやフレームワークが vite という名前でパッケージを参照するため、プラグイン側を一切変更せずに Vite+ へ置き換えられるようにするための設計だ。
しかしこの差し替え後のパッケージは node_modules/.bin/vite というバイナリを登録しない。Vite+ の CLI は ~/.vite-plus/bin/vp としてグローバルにインストールされるものであり、node_modules/.bin には存在しない。
② Alchemy が pnpm vite build を自動生成して実行する
Alchemy には TanStack Start アプリを Cloudflare Workers にデプロイするための TanStackStart ヘルパーが用意されており、alchemy.run.ts でこのように使う。
// alchemy.run.ts
export const worker = await TanStackStart("website");
この TanStackStart はデプロイ時に内部でビルドコマンドを組み立てて実行する。プロジェクトのパッケージマネージャーを検出し、${runner} vite build の形でコマンドを構成する。
// alchemy/lib/cloudflare/vite/vite.js(抜粋)
const runner = await getPackageManagerRunner(); // → "pnpm"
build: spreadBuildProps(props, `${runner} vite build`),
// → "pnpm vite build" が実行される
① と ② が重なった結果、pnpm vite build が node_modules/.bin/vite を探しに行くが存在せず、Command "vite" not found になる。
対処
2か所を修正する。
ワークフロー
Vite+が提供する voidzero-dev/setup-vp@v1 を使う。
セットアップ後は ~/.vite-plus/bin が PATH に追加され、vp コマンドが使用可能になる。
# 変更前
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: pnpm
- name: Install dependencies
run: pnpm install
# 変更後
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version: "24"
cache: true
- name: Install dependencies
run: vp install
alchemy.run.ts:
Alchemy の TanStackStart は build.command オプションでビルドコマンドを上書きできる。デフォルトの pnpm vite build を vp build に変更する。これにより、Command "vite" not foundを引き起こさなくなる。
// 変更前
export const worker = await TanStackStart("website");
// 変更後
export const worker = await TanStackStart("website", {
build: { command: "vp build" },
});
第2のエラー:vp run と Alchemy の相性問題
CI ガイドに倣い、Workflowファイルのpnpm install を vp install に、pnpm alchemy deploy を vp run deploy に変更したところ、次のエラーが出た。
エラー内容
failed to load config from vite.config.ts
[Vite+] resolve universal vite config error: Error: The Wrangler config path,
".../.alchemy/local/wrangler.jsonc", could not be found.
Please run `alchemy dev` or `alchemy deploy` to create it.
error: Failed to load task graph
原因
workflowsのDeploy ステップで vp run deploy(package.json の deploy スクリプトを実行するコマンド)を使っていた。
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version: "24"
cache: true
- name: Install dependencies
run: vp install
- name: Deploy
run : vp run deploy
"build": "vp build",
"preview": "vp preview",
"deploy": "alchemy deploy --stage $STAGE --adopt",
"destroy": "alchemy destroy --stage $STAGE",
ここで問題が起きる。vp run はスクリプトを実行する前に vite.config.ts を読み込んでタスクグラフを構築する。この vite.config.ts 内には Alchemy の vite プラグインが含まれており、そのプラグインが .alchemy/local/wrangler.jsonc を要求する。
// vite.config.ts
import alchemy from "alchemy/cloudflare/tanstack-start";
import { defineConfig } from "vite-plus";
export default defineConfig({
plugins: [
alchemy(), // ← こいつが wrangler.jsonc を要求する
// ...他のプラグイン
],
});
しかしwrangler.jsoncは alchemy deploy を実行して初めて生成されるものだ。つまり
vp run deploy を実行
→ vite.config.ts を読む
→ Alchemy vite プラグインが wrangler.jsonc を要求
→ wrangler.jsonc は alchemy deploy が生成する
→ alchemy deploy はまだ実行されていない → エラー
鶏と卵の状態になっている。
vp run を使うことが根本的な相性問題
Vite+ のCI ガイドは pnpm <script> を vp run <script> に置き換える方針を示している。
# 公式ガイドの "After" 例
- run: vp install && vp run dev:setup
- run: vp check
- run: vp test
この方針は一般的なプロジェクトでは成立する。しかし Alchemy のように vite.config.ts に初期化時の副作用を持つプラグインを含む場合、vp run 経由での実行は使えない。vp run が常に vite.config.ts を読むという仕様が仇になる。
対処
vp run を経由せず、node_modules/.bin/alchemy を直接呼ぶ。
alchemy deploy の実行順序は「Cloudflare リソースを作成 → wrangler.jsonc を生成 → vp build を呼ぶ」なので、vp build が vite.config.ts を読む時点では wrangler.jsonc がすでに存在する。直接呼び出しにすることで鶏と卵の問題が回避できる。
- name: Deploy
run: ./node_modules/.bin/alchemy deploy --stage ${{ env.STAGE }} --adopt
最終的なワークフロー
name: Deploy
on:
push:
branches:
- main
- dev
env:
STAGE: ${{ github.ref_name == 'main' && 'prod' || github.ref_name }}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version: "24"
cache: true
- name: Install dependencies
run: vp install
- name: Deploy
run: ./node_modules/.bin/alchemy deploy --stage ${{ env.STAGE }} --adopt
env:
ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
おわりに
まとめになるがvp run と Alchemy の組み合わせによって発生したエラーであった。
node_modules/.bin のバイナリを直接呼ぶことで回避できた。