fix(editor): vite-plugin-top-level-await を外して本番ビルドのエディタ起動を直す - #256
Merged
Conversation
本番ビルド (Cloudflare Pages / vite preview) でエディタが一切起動せず、 console に以下が出ていた (#255): TypeError: Class extends value undefined is not a constructor or null TypeError: Le is not a function (= createDecorator is not a function) 原因は `vite-plugin-top-level-await`。このプラグインはチャンクの全エクスポートを 先頭の `let` へ巻き上げ、本体を async IIFE で包む。この変換で `export function` が 代入文に降格するため、**関数宣言の巻き上げに正当に依存している monaco** が壊れる: // vs/platform/instantiation/common/instantiation.js const IInstantiationService = createDecorator('instantiationService'); // 先に実行 function createDecorator(serviceId) { ... } // 後に代入 throw は async IIFE の中で起きるので `__tla` が reject するだけでモジュール評価は 「成功」扱いになり、以降のエクスポート代入が全て飛ぶ。その結果 `Action2` 等が undefined のまま参照され `Class extends value undefined` に至る。 monaco 0.55.1 では monaco が単一チャンクに収まりラップ対象になっていなかったため 顕在化せず、0.56.0 (#206) でチャンク構成が変わって踏んだ。 `build.target: 'esnext'` + `worker.format: 'es'` を既に指定しており、ネイティブ TLA が Chrome 89+ / Safari 15+ / Firefox 89+ で使える。このプラグインは「TLA 非対応ブラウザ 向けの降格」が本来の役目で、現在の target では不要な変換をかけているだけなので削除する。 あわせて、このプラグインが rollup を `require` するためだけに置いていた devDependencies の `rollup` も削除した (Vite 8 は rolldown ベースで本来不要)。 確認方法: - `npm run build` green (rollup 無しで通る)。生成物に `__tla` が 1 箇所も出ない - `vite preview` を実ブラウザで開いて console エラー 0 件・Monaco マウント済み - Python (Pyodide) と C (@wasmer/sdk) を実行して出力が返ることを実測 (ネイティブ TLA へ切り替わる影響を受けるため) Refs #255 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#255 は「dev サーバでは console エラー 0 件、本番ビルドだけエディタが起動しない」 という壊れ方をした。既存の E2E は全て vite dev サーバに対して走るため、チャンク 分割やバンドル変換に起因する退行を**原理的に検出できず**、CI が緑のまま main に 載ってしまった。この穴を塞ぐ。 変更点: - `tests/production-build.spec.ts` … dist に対するスモーク 1 本。オラクルは 「pageerror / console.error が 0 件」「Monaco が実際にマウントされる」 「打鍵がエディタに入る」の 3 点のみ。重い round-trip は dev 側の spec が担う - `playwright.build.config.ts` … editor を `vite build` してから `vite preview` (:4173) で dist を配信し、上の spec だけを走らせる専用 config。検証対象が 「いま build した dist」であることを保証するため `reuseExistingServer: false`。 workers も起動する (無いと fetch 失敗が console エラーとして混ざり、本題の バンドル起因エラーと区別がつかなくなるため) - `playwright.config.ts` … `testIgnore` でこの spec を除外。dev サーバに対して 走らせても目的の退行を検出できないため。既存シナリオの挙動は変えていない - `package.json` … `test:build` スクリプト - `deploy.yml` … 既存の `e2e` job の最後のステップとして実行する。この job は 既に `deploy-preview` / `deploy-staging` / `deploy-production` の必須ゲート (`needs: [test, check, e2e]`) なので、これで本番ビルドの起動が deploy 条件になる。 job を分けなかったのは npm ci + playwright install の重複を避けるため 確認方法 (「壊して赤を見る」): - `topLevelAwait()` を一時的に戻してビルド → このスモークが失敗し、収集した pageerror がそのまま #255 の症状を再現する: Class extends value undefined is not a constructor or null Le is not a function - 外すと 1 passed。dev 向け `npm run test -w @typedcode/e2e` は緑のまま Refs #255 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- editor/CLAUDE.md … 「rollup を devDependencies に明示せよ」という旧・罠の記述を 「`vite-plugin-top-level-await` を再導入しない (#255)」に差し替える。壊れ方 (monaco の関数巻き上げ破壊 → 全エクスポート代入が飛ぶ) と、dev では再現せず 本番ビルドだけが壊れること、番人が production-build.spec.ts であることを明記 - editor/README.md … 依存表から `vite-plugin-top-level-await` を削除 - e2e/CLAUDE.md … 2 つの config の役割分担を構成図と実行コマンドに反映。罠に 「dev サーバ向けの spec は本番ビルドの退行を検出できない」を追加し、この経路の spec を増やすときは dev 限定 API (`__tcTestInsertBlock`) に依存しないよう注意書き - e2e/README.md … `npm run test:build` の説明と、なぜ別 config が要るのかの節を追加 Refs #255 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🚀 Preview Deployment
Deployed from commit f6b52a5 |
Owner
Author
Cloudflare Pages プレビューでの実測https://cb518534.typedcode.pages.dev/casual — #255 が起きていたのと同じ経路 (Pages 配信の本番ビルド) で確認した。
残っている console エラー 4 件はすべてアプリ外のインフラ由来で、本件とは無関係:
ローカル |
shinyaoguri
added a commit
that referenced
this pull request
Aug 5, 2026
…259) ## 目的 Dependabot の security update (undici) が **failure で終わり続けていた**。自動更新が詰まっており、放置すると脆弱性が残る。 原因は依存の経路にある: ``` wrangler 4.115.0 → miniflare 4.20260722.1 → undici 7.28.0 (脆弱) ``` パッチ版は `7.29.0` だが、Dependabot が見つけた唯一の更新パスが **wrangler を 4.115.0 → 4.35.0 へダウングレードする**ものだったため、自ら中止していた。 **wrangler を上げても解決しない。** 最新の 4.119.0 が要求する `miniflare 5.20260801.0-alpha` も undici を `7.28.0` で固定している (確認済み)。そのため Dependabot 自身が提示するもう一方の案 — override による pin — を採る。 ## 影響度 対象は 5 件 (`GHSA-4cwx-7wf7-3272` high / 他 medium 4)。**いずれも `scope: development`** で、lockfile 上も `dev=true` の 1 エントリのみ。undici は wrangler・miniflare のローカル開発サーバとデプロイ用ツールにしか入っておらず、**Cloudflare Workers の本番ランタイムには配布されない** (Workers は自前の fetch 実装を使う)。実際の影響範囲はローカルの `wrangler dev` と CI の e2e 環境。 ## 変更点 - root `package.json` の `overrides` に `"undici": "^7.29.0"` を追加 - このリポは既に `vite` / `dompurify` / `ws` / `uuid` で同じ手を使っており、その延長 - `overrides` から `"rollup": "^4.59.0"` を**削除** - [#256](#256) で editor から `vite-plugin-top-level-await` (と、そのためだけの rollup 依存) を落とした結果、lockfile 上の rollup エントリは 0 件・依存元も 0 件で dead になった - `package-lock.json` は **undici 7.28.0 → 7.29.0 の 3 行のみ**。他のバージョン変動・パッケージの増減なし > 2 つの override 変更を 1 PR にまとめている。同じ 7 行のオブジェクトを触るため分割すると lockfile が衝突するのと、rollup の除去は #256 の直接の帰結で文脈が同じため。コミットは分けていない (どちらも 1 行の削除/追加)。 ## 確認方法 | 検証 | 結果 | |---|---| | `node_modules/undici` の解決先 | **7.29.0** | | `npm audit` | **found 0 vulnerabilities** | | rollup が lockfile に復活していないか | エントリ **0 件** | | `npm run lint` | exit 0 | | `npm run typecheck` | exit 0 | | ユニットテスト (全 workspace) | **608 passed** / 3 skipped | | `npm run build` | exit 0 | | **`npm run test -w @typedcode/e2e`** | **17 passed** | | `npm run test:build -w @typedcode/e2e` | 1 passed | **e2e が本命の検証。** miniflare は undici を `7.28.0` で厳密指定しているため、7.29.0 への差し替えは未テストの組み合わせになる。実際に `wrangler dev` (= miniflare) を起動する e2e が通ることを、この override を入れてよいかの判断材料にした。 ## 残る懸念 miniflare 側が undici を上げるまで、この override は「上流が想定していない組み合わせ」であり続ける。壊れた場合は e2e が CI で落ちるので気付ける。上流が追いついたら override を外せる。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
目的
本番ビルド (Cloudflare Pages /
vite preview) で エディタが一切起動しない 不具合を直す。Fixes #255
原因
犯人は
vite-plugin-top-level-await、引き金は monaco 0.56.0 (#206) でのチャンク構成変更。このプラグインはチャンクの全エクスポートを先頭の
letへ巻き上げ、本体を async IIFE で包む。この変換でexport functionが 代入文に降格 するため、関数宣言の巻き上げに正当に依存している monaco が壊れる:throw は async IIFE の中で起きるので
__tlaが reject するだけでモジュール評価は「成功」扱いになり、以降のエクスポート代入が全て飛ぶ。その結果Action2などが undefined のままclass extendsに渡り、二次症状のClass extends value undefinedに至る。monaco 0.55.1 では monaco が単一チャンクに収まりラップ対象になっていなかったため顕在化していなかった。
変更点
1. 修正本体 (
377bd40)packages/editor/vite.config.tsからtopLevelAwait()を削除 (理由をコメントで残す)。build.target: 'esnext'+worker.format: 'es'でネイティブ TLA が使えるため、そもそも不要な変換だったpackages/editor/package.jsonからvite-plugin-top-level-awaitと、そのプラグインがrequireするためだけに置いていたrollupを削除 (Vite 8 は rolldown ベース)package-lock.json更新 (削除のみ・730 行減、バージョン bump は無し)2. 再発防止 (
ebe3f09)#255 は「dev サーバでは console エラー 0 件、本番ビルドだけ壊れる」という形だったため、既存 E2E が全て vite dev サーバ向けで原理的に検出できず、CI が緑のまま main に載った。この穴を塞ぐ。
packages/e2e/tests/production-build.spec.ts— dist に対するスモーク 1 本。オラクルは「pageerror / console.error が 0 件」「Monaco が実際にマウントされる」「打鍵がエディタに入る」の 3 点のみpackages/e2e/playwright.build.config.ts— editor をvite build→vite preview(:4173) で配信し、この spec だけを走らせる専用 config。reuseExistingServer: falseで「いま build した dist」を保証するpackages/e2e/playwright.config.ts—testIgnoreでこの spec を除外 (既存の dev 向けシナリオの挙動は不変).github/workflows/deploy.yml— 既存のe2ejob の最終ステップとして実行。この job は既にdeploy-preview/deploy-staging/deploy-productionの必須ゲート (needs: [test, check, e2e]) なので、本番ビルドの起動が deploy 条件になる。job を分けなかったのはnpm ci+playwright installの重複を避けるため3. docs (
e6b1431)editor/CLAUDE.mdの「rollup を明示せよ」という旧・罠を「再導入しない (#255)」に差し替え。e2e/CLAUDE.md/README.mdに 2 config の役割分担を反映。確認方法
壊して赤を見た
topLevelAwait()を一時的に戻してビルドすると、スモークが 失敗し、収集した pageerror がそのまま #255 の症状を再現 した:外すと
1 passed。オラクルが機能していることを確認済み。実ブラウザでの実測 (
vite preview= dist 配信)ネイティブ TLA へ切り替わる影響を受けるため、E2E に spec が無い実行系を手で確認した。
@wasmer/sdkの wasm-bindgen deprecation)@wasmer/sdk)TLA_SMOKE_C_OK 42を出力TLA_SMOKE_PY_OK 45/Python exited with code 0コマンド
npm run lintnpm run typechecknpm run test:run --workspaces --if-presentnpm run build__tlaが 1 箇所も出ない)npm run test -w @typedcode/e2enpm run test:build -w @typedcode/e2e修正前後で
editor.apiの中身が違う (2,686.57 kB → 2,682.40 kB) のに、出力ファイル名のハッシュが両方editor.api-BYMKrlhl.jsで同一 だった。content hash が TLA プラグインのrenderChunk前 の内容で計算されていたため。デプロイしてもファイル名が変わらず CDN / ブラウザキャッシュから壊れた旧版が配られる恐れ がある。本番リリース時は Cloudflare 側のキャッシュパージを前提にすること。
🤖 Generated with Claude Code