Waveterm 的 Monaco 0.52 → 0.53+ ESM 迁移实战:去除 AMD Loader、接入模块 Worker 与 Vite 分包优化

发布时间:2026/9/13 12:15:42
Waveterm 的 Monaco 0.52 → 0.53+ ESM 迁移实战:去除 AMD Loader、接入模块 Worker 与 Vite 分包优化 Waveterm 的 Monaco 0.52 → 0.53 ESM 迁移实战去除 AMD Loader、接入模块 Worker 与 Vite 分包优化【免费下载链接】wavetermAn open-source, AI-integrated, cross-platform terminal for seamless workflows项目地址: https://gitcode.com/GitHub_Trending/wa/wavetermWaveterm 是一个开源、集成 AI、跨平台的终端应用其前端代码编辑器基于 Monaco Editor 构建。随着 Monaco 0.53 起逐步废弃 AMD 构建Waveterm 需要在 Vite/Electron 技术栈下完成从 0.52.x配合monaco-editor/loader AMD 路径映射到 0.53 ESM 构建的迁移。本文以仓库内迁移规划文档 aiprompts/monaco-v0.53.md 为主线结合仓库当前的 Monaco 实现源码完整讲解迁移动机、逐步骤实施、Worker 接线、分包瘦身、Electron 兼容与回滚预案帮助读者在自己的 Vite/Electron 项目中复现这套方案。需要说明的是该规划文档标注为 “Deferred to next release”推迟到下一版本而当前仓库实际已落地了 ESM 方案package.json中monaco-editor已升级到^0.55.1并引入monaco-yamlmonaco-editor/loader已移除。因此本文将同时呈现规划文档中的标准迁移步骤与仓库中已生效的真实实现两者互为印证。为什么需要这次迁移Monaco 0.53 开始弃用 AMD 构建。在旧方案中代码通过loader.config({ paths: { vs: monaco } })做 AMD 路径映射再依赖monaco-editor/loader的init()动态加载min/vs/*下的 AMD 产物最后借助viteStaticCopy把node_modules/monaco-editor/min/vs/*原样拷贝到构建输出。这套链路存在明显问题AMD 路径映射变得脆弱paths: { vs: monaco }依赖运行时拷贝的目录结构一旦打包路径或版本内部目录变化就难以排查模块 Worker 需要显式接线ESM 构建采用 module workers无法再依赖旧 loader 自动拉起 Worker必须由应用层通过MonacoEnvironment.getWorker显式创建捆绑更干净、更利于 CSP/ElectronESM 方案让 Vite 可以直接参与 Monaco 的模块图tree-shaking、分包不再需要大量兼容性 shimWorker 以独立 chunk 产出也更容易适配 Electron 打包后的file://环境与更严格的 CSP 策略。从仓库现状看这次迁移已经完成在 frontend/app/monaco/monaco-env.ts 中已看不到任何loader.config/monaco-editor/loader引用取而代之的是window.MonacoEnvironment ESM Worker 的直接接线。高层迁移计划规划文档给出的整体路线如下移除 AMD/Loader 相关代码卸载monaco-editor/loader删除viteStaticCopy对min/vs/*的拷贝删除loader.config/init调用。安装 Monaco ≥0.53 并接线 ESM Worker通过MonacoEnvironment.getWorker为各语言返回模块 Worker。保持主包精简对 Monaco 设置做懒加载lazy-load可选地将 Monaco 单独拆分为独立 chunk。Electron/构建适配在 Vite 配置中保证base: ./使打包应用中的 Worker URL 在file://下可解析。对应到当前仓库前两步已经落地package.json中的依赖为monaco-editor: ^0.55.1与monaco-yaml: ^5.5.1见 package.jsonelectron.vite.config.ts的 renderer 配置中manualChunks已把node_modules/monaco与node_modules/monaco单独拆为monacochunk见 electron.vite.config.ts并配置了optimizeDeps.include: [monaco-yaml/yaml.worker.js]以预先优化 YAML Worker 依赖。迁移步骤详解1) 依赖变更按规划文档迁移周期内的依赖操作如下# next cycle: npm rm monaco-editor/loader npm i monaco-editor^0.53仓库当前已升级到更高版本^0.55.1同时新增monaco-yaml^5.5.1用于 YAML 语言服务与 schema 校验这正是规划文档Open questions中是否需要 JSON/CSS/HTML Worker 进默认包的实践答案——仓库选择保留全部主要语言css/html/json/typescript/yaml并叠加 YAML 支持。2) 删除 AMD 时代构建配置删除viteStaticCopy({ targets: [{ src: node_modules/monaco-editor/min/vs/*, dest: monaco }] })。删除运行时初始化代码loader.config({ paths: { vs: monaco } }); await loader.init();仓库现在的 electron.vite.config.ts 中已不存在任何viteStaticCopy/vite-plugin-static-copy痕迹Monaco 资源全部交由 Vite 原生模块图处理。3) 新增 ESM 初始化模块Worker 接线规划文档建议创建monaco-setup.ts使用new URL(..., import.meta.url)方式创建模块 Worker// monaco-setup.ts import * as monaco from monaco-editor/esm/vs/editor/editor.api; import monaco-editor/esm/vs/editor/editor.all.css; (self as any).MonacoEnvironment { getWorker(_moduleId: string, label: string) { switch (label) { case json: return new Worker(new URL(monaco-editor/esm/vs/language/json/json.worker.js, import.meta.url), { type: module, }); case css: return new Worker(new URL(monaco-editor/esm/vs/language/css/css.worker.js, import.meta.url), { type: module, }); case html: return new Worker(new URL(monaco-editor/esm/vs/language/html/html.worker.js, import.meta.url), { type: module, }); case typescript: case javascript: return new Worker(new URL(monaco-editor/esm/vs/language/typescript/ts.worker.js, import.meta.url), { type: module, }); default: return new Worker(new URL(monaco-editor/esm/vs/editor/editor.worker.js, import.meta.url), { type: module }); } }, }; export { monaco };仓库实际实现 frontend/app/monaco/monaco-env.ts 采用了同一思路、但更贴合 Vite 的另一种写法——?worker导入后缀。Vite 会把每个xxx.worker?worker模块编译为可直接new的 Worker 构造函数并自动产出独立 chunk等价于文档中new URL(..., import.meta.url)的效果import editorWorker from monaco-editor/esm/vs/editor/editor.worker?worker; import cssWorker from monaco-editor/esm/vs/language/css/css.worker?worker; import htmlWorker from monaco-editor/esm/vs/language/html/html.worker?worker; import jsonWorker from monaco-editor/esm/vs/language/json/json.worker?worker; import tsWorker from monaco-editor/esm/vs/language/typescript/ts.worker?worker; import ymlWorker from ./yamlworker?worker; window.MonacoEnvironment { getWorker(_, label) { if (label json) { return new jsonWorker(); } if (label css || label scss || label less) { return new cssWorker(); } if (label yaml || label yml) { return new ymlWorker(); } if (label html || label handlebars || label razor) { return new htmlWorker(); } if (label typescript || label javascript) { return new tsWorker(); } return new editorWorker(); }, };注意两点差异仓库把 Worker 文件声明在MonacoEnvironment.getWorker之外借助 Vite 的?worker静态导入让每个 Worker 成为独立 chunk避免运行时动态构造 URL 的不确定性getWorker(_, label)的_moduleId参数被忽略仅以label分发并且为scss/less复用了 css worker、为handlebars/razor复用了 html worker、为yaml/yml使用自定义的 yamlworker.js对应monaco-yaml的 worker。monaco-env.ts还在loadMonaco()中完成了一系列一次性初始化通过monacoConfigured标志保证幂等用monaco.editor.defineTheme定义两套主题wave-theme-dark基于vs-dark编辑器背景设为透明#00000000便于透出终端背景层与wave-theme-light基于vs背景#fefefe调用configureMonacoYaml(monaco, { validate: true, schemas: [] })启用 YAML 校验monaco.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true })关闭 TS/JS 的默认语义校验避免误报干扰终端场景monaco.json.jsonDefaults.setDiagnosticsOptions(...)注册 JSON 诊断与 schemaschemas: MonacoSchemas来自 frontend/app/monaco/schemaendpoints.ts。4) 在使用处懒加载规划文档建议在编辑器 UI 挂载点做动态导入避免启动时加载 Monaco// where the editor UI mounts const { monaco } await import(./monaco-setup); const editor monaco.editor.create(container, { language: javascript, value: });仓库采用模块内幂等初始化 React 生命周期挂载的组合方式frontend/app/monaco/monaco-react.tsx 导出了两个组件MonacoCodeEditor与MonacoDiffViewer两者在useEffect挂载时调用loadMonaco()随后用monaco.editor.create/monaco.editor.createDiffEditor创建实例模型使用自定义 schememonaco.Uri.parse(wave://editor/ encodeURIComponent(path))diff 视图则用wave://diff/...orig/wave://diff/...mod两个 URI 分别承载原始与修改内容组件通过ResizeObserver 100msdebounce触发editor.layout()保证容器尺寸变化时编辑器及时重排卸载时依次setModel(null)、dispose()并销毁模型规避反复打开/关闭导致的内存增长对应规划文档测试清单中的 Hot paths 项。组件被 frontend/app/view/codeeditor/codeeditor.tsx 与 frontend/app/view/codeeditor/diffviewer.tsx 引用即 Waveterm 的代码编辑视图与差异对比视图。5) 可选将 Monaco 隔离为独立 chunk规划文档给出vite.config.ts的manualChunks写法import { defineConfig } from vite; export default defineConfig({ base: ./, // important for Electron packaged apps build: { rollupOptions: { output: { manualChunks(id) { if (id.includes(node_modules/monaco-editor)) return monaco; }, }, }, }, });仓库在 electron.vite.config.ts 的 renderer 段落地了同样策略且覆盖范围更广把多个体积较大的第三方库都隔离为独立 chunkoutput: { manualChunks(id) { const p id.replace(/\\/g, /); if (p.includes(node_modules/monaco) || p.includes(node_modules/monaco)) return monaco; if (p.includes(node_modules/mermaid) || p.includes(node_modules/mermaid)) return mermaid; if (p.includes(node_modules/katex) || p.includes(node_modules/katex)) return katex; if (p.includes(node_modules/shiki) || p.includes(node_modules/shiki)) return shiki; if (p.includes(node_modules/cytoscape) || p.includes(node_modules/cytoscape)) return cytoscape; return undefined; }, },manualChunks匹配同时覆盖node_modules/monaco与node_modules/monaco前缀说明仓库同时安装了monaco-editor与monaco-*相关包monaco-yaml 等统一归入monacochunk。规划文档特别提醒通过new URL(..., import.meta.url)或 Vite 的?worker创建的 Worker 会被自动产出为独立 chunk无需在manualChunks中手工处理。包体积控制按需取舍规划文档给出四个控制维度仓库实现也逐一印证只 importeditor.api而非完整editormonaco-env.ts中直接import * as monaco from monaco-editor配合语言 contribution 的显式esm/vs/language/*/monaco.contribution导入只引入用到的语言特性只保留用到的 Worker仓库保留了 css/html/json/typescript/editor 五个核心 Worker 并新增 yaml worker若你的项目不需要某种语言直接删掉对应?worker导入与其getWorker分支即可用import()懒加载 Monaco仓库把初始化收敛在loadMonaco()由组件挂载时触发Monaco 相关 chunk 不会阻塞应用首屏按需动态导入语言贡献规划文档示例if (lang json) { await import(monaco-editor/esm/vs/language/json/monaco.contribution); }这与仓库中静态引入所有需要的 contribution是同一机制的两端——静态引入保稳定动态引入省体积可按语言使用频率权衡。另外仓库的 JSON schema 校验是体积控制与功能增强结合的典范frontend/app/monaco/schemaendpoints.ts 从仓库 schema 目录直接导入settings.json、connections.json、aipresets.json、backgrounds.json、waveai.json、widgets.json六个 schema 文件构造出{ uri, fileMatch, schema }三元组数组再注册到monaco.json.jsonDefaults。例如{ uri: wave://schema/settings.json, fileMatch: [*/WAVECONFIGPATH/settings.json], schema: settingsSchema, }, { uri: wave://schema/connections.json, fileMatch: [*/WAVECONFIGPATH/connections.json], schema: connectionsSchema, },这意味着 Waveterm 用户在编辑自己的settings.json、connections.json等配置文件时编辑器内会直接获得基于官方 schema 的自动补全与实时校验。Electron 特定事项规划文档列出三条 Electron 关键约束均已在仓库配置中体现base: ./electron.vite.config.ts使用electron-vite的defineConfig其 renderer 构建天然面向相对路径输出产物位于dist/frontend配合打包后的file://协议Worker 与资源 URL 均按相对路径解析这正是规划文档强调base: ./的目的{ type: module }必须显式指定Monaco ESM Worker 必须以 module 方式创建。仓库通过 Vite?worker后缀自动生成模块 WorkerVite 内部即为new Worker(..., { type: module })语义与规划文档一致避免 blob URL、兼容严格 CSPESM Worker 以独立文件 chunk 形式产出不走blob:内联因此在开启了严格 CSP 的 Electron 窗口中更容易被放行仍可按规划文档 Open questions 提示在正式环境确认worker-src指令。仓库另外还设置了optimizeDeps.include: [monaco-yaml/yaml.worker.js]见 electron.vite.config.ts让 YAML Worker 在开发服务器预构建阶段即被优化避免 dev 模式下首次请求时的二次编译与 404。测试清单与验收规划文档给出三组验收点可直接作为迁移完成的标准开发环境Dev编辑器正常渲染Worker 脚本无 404语言服务生效TS hover/诊断、JSON schema 补全。对应仓库中的 schemaendpoints 注册逻辑可在编辑 WAVECONFIGPATH 下的 JSON 文件时直接验证生产构建Prod build确认 Worker 文件已产出到dist/frontend打开打包后的 Electron 应用确认 Worker 正常加载控制台不出现Cannot use import statement outside a module该报错通常是 module Worker 被当作 classic Worker 创建所致热路径Hot paths反复打开/关闭编辑器含 diff 视图观察内存不无限增长。仓库组件在卸载时setModel(null)dispose()模型与编辑器实例即为应对此验收点。回滚预案规划文档为迁移失败提供了明确的回滚路径仅两步即可回到 0.52 时代npm i monaco-editor0.52.x npm i -D monaco-editor/loader随后恢复viteStaticCopy对min/vs/*的拷贝块与loader.config/init调用。因为当前仓库已完整落地 ESM 方案并升级到^0.55.1回滚预案主要面向仍在 0.52.x 上的其他分支或历史版本属于标准的发布安全网。开放问题与取舍建议规划文档留下的两个开放问题仓库已经给出了实际答案可作为其他项目迁移时的决策参考JSON/CSS/HTML Worker 是否进默认包仓库选择全部保留且新增了 YAML Worker——因为 Waveterm 的编辑器场景配置编辑、代码编辑、diff 对比需要完整语言服务若你的项目只编辑单一语言按需裁剪可进一步减包生产环境 CSP 是否有额外限制仓库未引入 blob URL全部依赖独立 chunk 的模块 Worker从实现上规避了大部分 CSP 冲突上线前仍建议在生产环境核对script-src与worker-src指令。速查清单Worker 接线frontend/app/monaco/monaco-env.ts?worker导入 MonacoEnvironment.getWorker 主题/诊断/YAML/JSON schema 初始化编辑器与 Diff 组件frontend/app/monaco/monaco-react.tsxMonacoCodeEditor/MonacoDiffViewerwave://scheme 模型JSON schema 来源frontend/app/monaco/schemaendpoints.ts schema 目录下的六个 JSON schemaVite 分包与依赖预优化electron.vite.config.tsmanualChunks中monacochunk、optimizeDeps.include依赖版本package.jsonmonaco-editor^0.55.1、monaco-yaml^5.5.1如果你正在维护一个 Vite Electron 应用并受困于 Monaco 的 AMD/loader 链路照此方案移除 loader、以MonacoEnvironment.getWorker接线 ESM Worker、用manualChunks隔离 Monaco chunk再以base: ./适配打包产物即可在保留全部语言服务能力的同时获得更干净、更易维护、更利于 CSP 的构建结果。【免费下载链接】wavetermAn open-source, AI-integrated, cross-platform terminal for seamless workflows项目地址: https://gitcode.com/GitHub_Trending/wa/waveterm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考