Bugfix: Monaco 离线加载(预构建 min/vs 自托管) #13

Closed
opened 2026-08-20 20:09:54 +08:00 by tao.chen · 2 comments
Owner

Bug

Python 编辑与预览一直卡在 loading,控制台报 Monaco initialization: error。但 contents API(/jupyter/.../api/contents/...)正常返回内容,排除了后端问题。

根因

@monaco-editor/react 默认通过 @monaco-editor/loader 从 jsDelivr CDN(https://cdn.jsdelivr.net/npm/monaco-editor@.../min/vs)异步加载 Monaco 核心 + worker。离线 / 内网环境 CDN 不可达 → <Editor> 初始化失败、永远停在内部 loading 状态。PythonEditor 和 PythonPreview 都依赖 <Editor>,所以一起打不开。

修复路线(已验证可走通)

monaco 预构建 min/vs(IIFE/AMD)+ loader.config({ paths }) 自托管 路线,绕开 Vite 8 / Rolldown 与 monaco ESM 的 new URL(...?esm, import.meta.url) worker bootstrap 兼容坑(Failed to resolve module specifier "../../../base/common/worker/webWorkerBootstrap.js")。

不要走 loader.config({ monaco }) + import * as monaco from "monaco-editor" 的 ESM bundle 路线 —— Rolldown 不会改写 monaco 内部深嵌套相对 worker URL,运行时崩。

改动文件

  • frontend/app/lib/monacoSetup.client.ts(新建,.client 后缀防 SSR 预渲染崩)
  • frontend/scripts/copy-monaco.mjs(新建,fs.cpSync 复制 node_modules/monaco-editor/min/vspublic/monaco/vs)
  • frontend/package.json(加 monaco-editor@^0.56.0 依赖;scripts 同时挂 postinstall / predev / prebuild 跑 copy —— pnpm v11 idempotent install 跳 postinstall,三处都挂才稳)
  • frontend/.gitignore(加 /public/monaco/vs/,24MB min/vs 不进 git)
  • frontend/public/monaco/.gitkeep(占位)
  • frontend/app/root.tsx(顶部 import monacoSetup.client)

验证

  • pnpm buildbuild/client/monaco/vs/loader.js + editor.main.js 完整
  • pnpm typecheck
  • server bundle 18KB(monaco 由预构建 loader 懒加载,不再进 bundle)

记忆

已更新 ~/.claude/projects/.../memory/monaco-offline-loading.md,记录正确路线与 ESM 路线失败原因。


Task: xx2rymd46h42lopsayehbkag

## Bug Python 编辑与预览一直卡在 loading,控制台报 `Monaco initialization: error`。但 contents API(`/jupyter/.../api/contents/...`)正常返回内容,排除了后端问题。 ## 根因 `@monaco-editor/react` 默认通过 `@monaco-editor/loader` 从 jsDelivr CDN(`https://cdn.jsdelivr.net/npm/monaco-editor@.../min/vs`)异步加载 Monaco 核心 + worker。离线 / 内网环境 CDN 不可达 → `<Editor>` 初始化失败、永远停在内部 loading 状态。PythonEditor 和 PythonPreview 都依赖 `<Editor>`,所以一起打不开。 ## 修复路线(已验证可走通) 走 **monaco 预构建 `min/vs`(IIFE/AMD)+ `loader.config({ paths })` 自托管** 路线,绕开 Vite 8 / Rolldown 与 monaco ESM 的 `new URL(...?esm, import.meta.url)` worker bootstrap 兼容坑(`Failed to resolve module specifier "../../../base/common/worker/webWorkerBootstrap.js"`)。 不要走 `loader.config({ monaco })` + `import * as monaco from "monaco-editor"` 的 ESM bundle 路线 —— Rolldown 不会改写 monaco 内部深嵌套相对 worker URL,运行时崩。 ## 改动文件 - `frontend/app/lib/monacoSetup.client.ts`(新建,`.client` 后缀防 SSR 预渲染崩) - `frontend/scripts/copy-monaco.mjs`(新建,`fs.cpSync` 复制 `node_modules/monaco-editor/min/vs` → `public/monaco/vs`) - `frontend/package.json`(加 `monaco-editor@^0.56.0` 依赖;scripts 同时挂 `postinstall` / `predev` / `prebuild` 跑 copy —— **pnpm v11 idempotent install 跳 postinstall,三处都挂才稳**) - `frontend/.gitignore`(加 `/public/monaco/vs/`,24MB min/vs 不进 git) - `frontend/public/monaco/.gitkeep`(占位) - `frontend/app/root.tsx`(顶部 import `monacoSetup.client`) ## 验证 - `pnpm build` ✓ `build/client/monaco/vs/loader.js` + `editor.main.js` 完整 - `pnpm typecheck` ✓ - server bundle 18KB(monaco 由预构建 loader 懒加载,不再进 bundle) ## 记忆 已更新 `~/.claude/projects/.../memory/monaco-offline-loading.md`,记录正确路线与 ESM 路线失败原因。 --- <sub>Task: xx2rymd46h42lopsayehbkag</sub>
tao.chen added the priority:highstatus:done labels 2026-08-20 20:09:57 +08:00
Author
Owner

tao.chen commented:

同步记录

修复路径: 预构建 min/vs + loader.config({ paths }) 自托管到 /monaco/vs

关键经验(已沉淀到项目记忆 monaco-offline-loading.md):

  • loader.config({ monaco }) + import * as monaco from "monaco-editor" —— Vite 8 / Rolldown 与 monaco ESM 的 new URL(...?esm, import.meta.url) worker bootstrap 不兼容,运行时 Failed to resolve module specifier webWorkerBootstrap.js
  • 预构建 min/vs(IIFE/AMD) + loader.config({ paths: { vs: "/monaco/vs" } }) —— 自带 worker 加载,完全绕开 bundler
  • ⚠️ pnpm v11 idempotent install 跳 postinstall,copy-monaco 必须同时挂 postinstall / predev / prebuild 兜底
  • ⚠️ React Router SPA pnpm build 仍跑 server 预渲染,monaco 配置必须用 .client 后缀

验证: pnpm build ✓ / pnpm typecheck ✓ / build/client/monaco/vs/ 完整。

**tao.chen** commented: > ## 同步记录 **修复路径:** 预构建 `min/vs` + `loader.config({ paths })` 自托管到 `/monaco/vs`。 **关键经验(已沉淀到项目记忆 `monaco-offline-loading.md`):** - ❌ `loader.config({ monaco })` + `import * as monaco from "monaco-editor"` —— Vite 8 / Rolldown 与 monaco ESM 的 `new URL(...?esm, import.meta.url)` worker bootstrap 不兼容,运行时 `Failed to resolve module specifier webWorkerBootstrap.js` - ✅ 预构建 `min/vs`(IIFE/AMD) + `loader.config({ paths: { vs: "/monaco/vs" } })` —— 自带 worker 加载,完全绕开 bundler - ⚠️ pnpm v11 idempotent install 跳 postinstall,`copy-monaco` 必须同时挂 `postinstall` / `predev` / `prebuild` 兜底 - ⚠️ React Router SPA `pnpm build` 仍跑 server 预渲染,monaco 配置必须用 `.client` 后缀 **验证:** `pnpm build` ✓ / `pnpm typecheck` ✓ / `build/client/monaco/vs/` 完整。
Author
Owner

tao.chen commented:

Follow-up: Docker 构建修复

报错: Cannot find module '/app/scripts/copy-monaco.mjs'

根因: frontend/Dockerfile 分层复制利用缓存 —— 先 COPY frontend/package.json frontend/pnpm-lock.yaml ./RUN pnpm install --frozen-lockfile,完整源码(含 scripts/)在 COPY frontend/ ./ 之后才进来postinstall 在 install 阶段跑,此时 /app/scripts/ 还不存在,直接 MODULE_NOT_FOUND

修复: frontend/package.json 删除 postinstall,只保留 predev + prebuild(外加 copy:monaco 作手动入口)。

Why not 修 Dockerfile(把 COPY frontend/ ./ 提前): 会破坏 install 层缓存,每次源码改动都重装依赖,得不偿失。

Why not 只删 postinstall 的 Docker 坑、保留 pnpm 兜底: pnpm v11 idempotent install 本来就会跳 postinstall,postinstall 本身就不可靠。两层问题都指向同一个解法 —— 不要用 postinstall

验证: pnpm build ✓ (prebuild 跑 copy 正常)。Docker 重新构建应能通过。

**tao.chen** commented: > ## Follow-up: Docker 构建修复 **报错:** `Cannot find module '/app/scripts/copy-monaco.mjs'` **根因:** `frontend/Dockerfile` 分层复制利用缓存 —— 先 `COPY frontend/package.json frontend/pnpm-lock.yaml ./` 再 `RUN pnpm install --frozen-lockfile`,**完整源码(含 `scripts/`)在 `COPY frontend/ ./` 之后才进来**。`postinstall` 在 install 阶段跑,此时 `/app/scripts/` 还不存在,直接 `MODULE_NOT_FOUND`。 **修复:** `frontend/package.json` **删除 `postinstall`**,只保留 `predev` + `prebuild`(外加 `copy:monaco` 作手动入口)。 **Why not 修 Dockerfile(把 `COPY frontend/ ./` 提前):** 会破坏 install 层缓存,每次源码改动都重装依赖,得不偿失。 **Why not 只删 postinstall 的 Docker 坑、保留 pnpm 兜底:** pnpm v11 idempotent install 本来就会跳 postinstall,postinstall 本身就不可靠。两层问题都指向同一个解法 —— **不要用 postinstall**。 **验证:** `pnpm build` ✓ (prebuild 跑 copy 正常)。Docker 重新构建应能通过。
tao.chen added status:archived and removed status:done labels 2026-08-24 10:09:10 +08:00
tao.chen added the status:in-review label 2026-08-24 14:26:25 +08:00
tao.chen added status:done and removed status:in-review labels 2026-08-24 14:53:29 +08:00
Sign in to join this conversation.