From 489b2dbeecf2d0b4e2e4b376100f7f756a7e8461 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 8 Oct 2026 12:50:57 +0000 Subject: [PATCH] ui: monaco: upgrade to 0.57, drop monaco-editor-webpack-plugin - Migrate entry points to the 0.57 exports scheme (monaco-editor/editor, languages/features/typescript/register) - Wire workers via the official MonacoEnvironment.getWorker + new Worker(new URL(...)) integration; webpack emits them as shared-chunk classic workers (chrome 70 compatible), total assets -21% - Keep css/html/json language features (register.all) and route their worker labels; json is used by the cmeditor textareas - Replace removed esm/vs/nls.messages with an inline implementation in the nls shim - Drop MinChunkSizePlugin: it merged worker chunks into the main chunk and broke the runtime id module (webpack#15416) - Remove monaco-editor-webpack-plugin dependency - loadMonaco() now returns the loaded feature module exports, e.g. loadMonaco(['typescript']).typescript.javascriptDefaults --- packages/ui-default/build/config/webpack.ts | 25 ++++++--------- .../ui-default/components/monaco/index.ts | 29 ++++++++++++++++- .../components/monaco/languages/markdown.ts | 2 +- .../components/monaco/languages/typescript.ts | 32 ++++++++++++------- .../components/monaco/languages/yaml.ts | 4 +-- .../ui-default/components/monaco/loader.ts | 18 +++++++++-- packages/ui-default/components/monaco/nls.js | 8 ++++- .../scratchpad/ScratchpadEditorContainer.tsx | 2 +- .../components/scratchpad/index.tsx | 6 ++-- packages/ui-default/package.json | 3 +- packages/ui-default/typed.d.ts | 16 ++++++++++ 11 files changed, 103 insertions(+), 42 deletions(-) diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index 24525cca7..62dfc361c 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -8,7 +8,6 @@ import compat from 'core-js-compat'; import { EsbuildPlugin } from 'esbuild-loader'; import { DuplicatesPlugin } from 'inspectpack/plugin'; import ExtractCssPlugin from 'mini-css-extract-plugin'; -import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; import packageJson from 'package-json'; import { gt } from 'semver'; import webpack from 'webpack'; @@ -18,6 +17,8 @@ import WebpackBar from 'webpackbar'; import { version } from '../../package.json'; import root from '../utils/root'; +const monacoPath = join(dirname(require.resolve('monaco-editor/editor.js')), '..', '..'); + const { list, targets, @@ -134,6 +135,10 @@ export default async function (env: { watch?: boolean, production?: boolean, mea extensions: ['.js', '.jsx', '.ts', '.tsx', '.cjs'], alias: { vj: root(), + 'monaco-editor/esm/vs/editor/browser/editorExtensions': join(monacoPath, 'esm/vs/editor/browser/editorExtensions.js'), + 'monaco-editor/esm/vs/editor/editor.worker.js': join(monacoPath, 'esm/vs/editor/editor.worker.js'), + 'monaco-editor/esm/vs/platform/quickinput/common/quickInput': join(monacoPath, 'esm/vs/platform/quickinput/common/quickInput.js'), + 'monaco-editor/esm/vs/base/browser/markdownRenderer': join(monacoPath, 'esm/vs/base/browser/markdownRenderer.js'), 'react/jsx-runtime': require.resolve('react/jsx-runtime'), react: require.resolve('react'), 'react-dom/client': require.resolve('react-dom/client'), @@ -282,7 +287,7 @@ export default async function (env: { watch?: boolean, production?: boolean, mea jQuery: 'jquery', 'window.jQuery': 'jquery', React: 'react', - monaco: 'monaco-editor/esm/vs/editor/editor.api', + monaco: 'monaco-editor/editor', }), new ExtractCssPlugin({ filename: `[name]-${version}.css?[fullhash:6]`, @@ -313,24 +318,12 @@ export default async function (env: { watch?: boolean, production?: boolean, mea new webpack.DefinePlugin({ 'process.env.VERSION': JSON.stringify(require('@hydrooj/ui-default/package.json').version), }), - new webpack.optimize.MinChunkSizePlugin({ - minChunkSize: 128000, - }), + // MinChunkSizePlugin merges worker chunks into the main chunk and breaks + // the runtime id module (webpack#15416), do not re-enable it new webpack.NormalModuleReplacementPlugin(/\/(vscode-)?nls\.js/, require.resolve('../../components/monaco/nls')), new webpack.NormalModuleReplacementPlugin(/^prettier[$/]/, root('../../modules/nop.ts')), new webpack.NormalModuleReplacementPlugin(/^highlightjs[$/]/, root('../../modules/nop.ts')), new webpack.NormalModuleReplacementPlugin(/core-js\/stable/, root('__core-js.js')), - new MonacoWebpackPlugin({ - filename: '[name].[hash:6].worker.js', - customLanguages: [{ - label: 'yaml', - entry: require.resolve('monaco-yaml/index.js'), - worker: { - id: 'vs/language/yaml/yamlWorker', - entry: require.resolve('monaco-yaml/yaml.worker.js'), - }, - }], - }), ...env.measure ? [ new BundleAnalyzerPlugin({ analyzerPort: 'auto' }), new DuplicatesPlugin(), diff --git a/packages/ui-default/components/monaco/index.ts b/packages/ui-default/components/monaco/index.ts index 5f92b3881..b4e33faf9 100644 --- a/packages/ui-default/components/monaco/index.ts +++ b/packages/ui-default/components/monaco/index.ts @@ -1,8 +1,11 @@ import './monaco.styl'; +import 'monaco-editor/features/register.all.js'; +import 'monaco-editor/languages/definitions/register.all.js'; +import 'monaco-editor/languages/features/register.all.js'; import $ from 'jquery'; +import * as monaco from 'monaco-editor/editor'; import { EditorAction, registerEditorAction } from 'monaco-editor/esm/vs/editor/browser/editorExtensions'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; import list from 'monaco-themes/themes/themelist.json'; import { nanoid } from 'nanoid'; @@ -10,6 +13,30 @@ import { i18n, request } from 'vj/utils'; export { renderMarkdown } from 'monaco-editor/esm/vs/base/browser/markdownRenderer'; +self.MonacoEnvironment = { + getWorker(workerId: string, label: string): Worker { + if (label === 'editorWorkerService') { + return new Worker(new URL('monaco-editor/editor/editor.worker.js', import.meta.url), { type: 'module' }); + } + if (label === 'yaml') { + return new Worker(new URL('monaco-yaml/yaml.worker.js', import.meta.url), { type: 'module' }); + } + if (label === 'typescript' || label === 'javascript') { + return new Worker(new URL('monaco-editor/languages/features/typescript/ts.worker.js', import.meta.url), { type: 'module' }); + } + if (['css', 'scss', 'less'].includes(label)) { + return new Worker(new URL('monaco-editor/languages/features/css/css.worker.js', import.meta.url), { type: 'module' }); + } + if (['html', 'handlebars', 'razor'].includes(label)) { + return new Worker(new URL('monaco-editor/languages/features/html/html.worker.js', import.meta.url), { type: 'module' }); + } + if (label === 'json') { + return new Worker(new URL('monaco-editor/languages/features/json/json.worker.js', import.meta.url), { type: 'module' }); + } + throw new Error(`Unknown monaco worker label: ${label}`); + }, +} as monaco.Environment; + export default monaco; export const customOptions: monaco.editor.IStandaloneDiffEditorConstructionOptions = JSON.parse(localStorage.getItem('editor.config') || '{}'); export function saveCustomOptions() { diff --git a/packages/ui-default/components/monaco/languages/markdown.ts b/packages/ui-default/components/monaco/languages/markdown.ts index 15f50dac7..10ac8698b 100644 --- a/packages/ui-default/components/monaco/languages/markdown.ts +++ b/packages/ui-default/components/monaco/languages/markdown.ts @@ -1,6 +1,6 @@ import keyword from 'emojis-keywords'; import list from 'emojis-list'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import * as monaco from 'monaco-editor/editor'; import qface from 'qface'; import { api } from 'vj/utils'; diff --git a/packages/ui-default/components/monaco/languages/typescript.ts b/packages/ui-default/components/monaco/languages/typescript.ts index 3125bed82..9d7a572a7 100644 --- a/packages/ui-default/components/monaco/languages/typescript.ts +++ b/packages/ui-default/components/monaco/languages/typescript.ts @@ -1,27 +1,31 @@ -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import * as monaco from 'monaco-editor/editor'; +import type { CompilerOptions, DiagnosticsOptions } from 'monaco-editor/languages/features/typescript/register.js'; +import { + javascriptDefaults, ModuleKind, ModuleResolutionKind, ScriptTarget, + typescriptDefaults } from 'monaco-editor/languages/features/typescript/register.js'; const types = require.context('@types/node/', true, /\.d\.ts$/, 'lazy-once'); -const diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions = { +const diagnosticsOptions: DiagnosticsOptions = { noSemanticValidation: false, noSyntaxValidation: false, noSuggestionDiagnostics: true, }; -const compilerOptions: monaco.languages.typescript.CompilerOptions = { - target: monaco.languages.typescript.ScriptTarget.ES2020, - module: monaco.languages.typescript.ModuleKind.ESNext, +const compilerOptions: CompilerOptions = { + target: ScriptTarget.ES2020, + module: ModuleKind.ESNext, allowNonTsExtensions: true, }; -monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(diagnosticsOptions); -monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions(diagnosticsOptions); -monaco.languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions); -monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions); +javascriptDefaults.setDiagnosticsOptions(diagnosticsOptions); +typescriptDefaults.setDiagnosticsOptions(diagnosticsOptions); +javascriptDefaults.setCompilerOptions(compilerOptions); +typescriptDefaults.setCompilerOptions(compilerOptions); const libSource = [ 'declare function readline(): string;', 'declare function print(content: string): void;', ].join('\n'); const libUri = 'ts:filename/basic.d.ts'; -monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, libUri); +javascriptDefaults.addExtraLib(libSource, libUri); monaco.editor.createModel(libSource, 'typescript', monaco.Uri.parse(libUri)); const modules = []; @@ -34,12 +38,16 @@ export async function loadTypes() { modules.push(val.toString().split('declare module \'')[1].split('\'')[0]); } const uri = `ts:node/${key.split('./')[1]}`; - monaco.languages.typescript.javascriptDefaults.addExtraLib(val, uri); + javascriptDefaults.addExtraLib(val, uri); monaco.editor.createModel(val, 'typescript', monaco.Uri.parse(uri)); } let val = 'declare var require:'; for (const m of modules) val += `((id:'${m}')=>(typeof import('${m}')))&`; val += '((id:string)=>any)'; - monaco.languages.typescript.javascriptDefaults.addExtraLib(val, 'ts:node/require.d.ts'); + javascriptDefaults.addExtraLib(val, 'ts:node/require.d.ts'); monaco.editor.createModel(val, 'typescript', monaco.Uri.parse('ts:node/require.d.ts')); } + +export { + javascriptDefaults, ModuleKind, ModuleResolutionKind, ScriptTarget, typescriptDefaults, +}; diff --git a/packages/ui-default/components/monaco/languages/yaml.ts b/packages/ui-default/components/monaco/languages/yaml.ts index 55ae952ee..d87687f29 100644 --- a/packages/ui-default/components/monaco/languages/yaml.ts +++ b/packages/ui-default/components/monaco/languages/yaml.ts @@ -1,8 +1,8 @@ -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import * as monaco from 'monaco-editor/editor'; import { configureMonacoYaml } from 'monaco-yaml'; import problemConfigSchema from '../schema/problemconfig'; -configureMonacoYaml(monaco, { +configureMonacoYaml(monaco as any, { validate: true, enableSchemaRequest: true, hover: true, diff --git a/packages/ui-default/components/monaco/loader.ts b/packages/ui-default/components/monaco/loader.ts index f52b6547b..cf07fe628 100644 --- a/packages/ui-default/components/monaco/loader.ts +++ b/packages/ui-default/components/monaco/loader.ts @@ -32,7 +32,11 @@ const loaders = { if (resource) setLocaleData(resource); }, markdown: () => import('./languages/markdown'), - typescript: () => import('./languages/typescript').then((m) => m.loadTypes()), + typescript: async () => { + const m = await import('./languages/typescript'); + await m.loadTypes(); + return m; + }, yaml: () => import('./languages/yaml'), external: async (monaco, feat) => { for (const item of await getFeatures(`monaco-${feat}`)) { @@ -49,7 +53,13 @@ const loaders = { let loadPromise = Promise.resolve(); -export async function load(features = ['markdown']) { +export async function load(features = ['markdown']): Promise<{ + monaco: typeof import('monaco-editor/editor'); + registerAction: typeof import('./index').registerAction; + customOptions: typeof import('./index').customOptions; + renderMarkdown: typeof import('./index').renderMarkdown; + [feature: string]: any; +}> { let s = Date.now(); await loadPromise; let resolve; @@ -59,6 +69,7 @@ export async function load(features = ['markdown']) { console.log('Loading monaco editor'); } const res = await import('./index'); + const featureExports: Record = {}; if (!loaded) { console.log('Loaded monaco editor in', Date.now() - s, 'ms'); loaded = []; @@ -75,7 +86,7 @@ export async function load(features = ['markdown']) { s = Date.now(); console.log('Loading monaco feature:', feat); try { - if (loaders[feat]) await loaders[feat](); + if (loaders[feat]) featureExports[feat] = await loaders[feat](); else await loaders.external(res.default, feat); console.log('Loaded monaco feature:', feat, 'in', Date.now() - s, 'ms'); loaded.push(feat); @@ -90,6 +101,7 @@ export async function load(features = ['markdown']) { registerAction: res.registerAction, customOptions: res.customOptions, renderMarkdown: res.renderMarkdown, + ...featureExports, }; } diff --git a/packages/ui-default/components/monaco/nls.js b/packages/ui-default/components/monaco/nls.js index 647743d72..00c93af37 100644 --- a/packages/ui-default/components/monaco/nls.js +++ b/packages/ui-default/components/monaco/nls.js @@ -1,4 +1,10 @@ -export { getNLSLanguage, getNLSMessages } from 'monaco-editor/esm/vs/nls.messages'; +export function getNLSLanguage() { + return globalThis._VSCODE_NLS_LANGUAGE; +} + +export function getNLSMessages() { + return globalThis._VSCODE_NLS_MESSAGES || []; +} function format(message, args) { let result; diff --git a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx index 6e2256092..a0b9e8d4f 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx +++ b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.tsx @@ -1,4 +1,4 @@ -import type * as monaco from 'monaco-editor'; +import type * as monaco from 'monaco-editor/editor'; import React from 'react'; import { connect } from 'react-redux'; import { load } from 'vj/components/monaco/loader'; diff --git a/packages/ui-default/components/scratchpad/index.tsx b/packages/ui-default/components/scratchpad/index.tsx index dbd8ae696..a53340126 100644 --- a/packages/ui-default/components/scratchpad/index.tsx +++ b/packages/ui-default/components/scratchpad/index.tsx @@ -3,7 +3,7 @@ import SettingsIcon from '@vscode/codicons/src/icons/settings-gear.svg?react'; import { Allotment } from 'allotment'; import $ from 'jquery'; import _ from 'lodash'; -import type * as monaco from 'monaco-editor'; +import type * as monaco from 'monaco-editor/editor'; import React from 'react'; import { useDispatch, useSelector, useStore } from 'react-redux'; import Dom from 'vj/components/react/DomComponent'; @@ -38,9 +38,9 @@ class ScratchpadService extends Service { load: Promise; loadCallback: () => void; editor: monaco.editor.IStandaloneCodeEditor; - monaco: typeof import('monaco-editor'); + monaco: typeof import('monaco-editor/editor'); - init(editor: monaco.editor.IStandaloneCodeEditor, monaco: typeof import('monaco-editor')) { + init(editor: monaco.editor.IStandaloneCodeEditor, monaco: typeof import('monaco-editor/editor')) { this.editor = editor; this.monaco = monaco; this.loadCallback(); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index af9c54aa6..7472a12c2 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -71,9 +71,8 @@ "md-editor-rt": "^7.1.0", "mini-css-extract-plugin": "^2.10.2", "moment": "^2.31.0", - "monaco-editor": "0.52.2", + "monaco-editor": "0.57.0", "monaco-editor-nls": "^3.1.0", - "monaco-editor-webpack-plugin": "^7.1.1", "monaco-themes": "0.4.7", "monaco-yaml": "5.4.1", "nanoid": "^6.0.1", diff --git a/packages/ui-default/typed.d.ts b/packages/ui-default/typed.d.ts index c54c3f715..a0814120c 100644 --- a/packages/ui-default/typed.d.ts +++ b/packages/ui-default/typed.d.ts @@ -18,3 +18,19 @@ declare module '*.scss' { const content: string; export default content; } + +// monaco internal modules blocked by package exports; types declared here, runtime is aliased in webpack config +declare module 'monaco-editor/esm/vs/editor/browser/editorExtensions' { + export class EditorAction { + constructor(opts: { id: string; label: string; alias: string }); + run(accessor: any, editor: any): Promise; + } + export function registerEditorAction(ctor: any): void; +} +declare module 'monaco-editor/esm/vs/platform/quickinput/common/quickInput' { + // monaco DI token(createDecorator 返回值),运行时按值使用 + export const IQuickInputService: any; +} +declare module 'monaco-editor/esm/vs/base/browser/markdownRenderer' { + export function renderMarkdown(opts: any): any; +}