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
This commit is contained in:
undefined committed 2026-10-09 16:26:44 +00:00
1 parent 80638bf9ab
commit 489b2dbeec
11 files changed
+103 -42

No files matched your search

+9 -16
View File
@@ -8,7 +8,6 @@ import compat from 'core-js-compat';
import { EsbuildPlugin } from 'esbuild-loader'; import { EsbuildPlugin } from 'esbuild-loader';
import { DuplicatesPlugin } from 'inspectpack/plugin'; import { DuplicatesPlugin } from 'inspectpack/plugin';
import ExtractCssPlugin from 'mini-css-extract-plugin'; import ExtractCssPlugin from 'mini-css-extract-plugin';
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
import packageJson from 'package-json'; import packageJson from 'package-json';
import { gt } from 'semver'; import { gt } from 'semver';
import webpack from 'webpack'; import webpack from 'webpack';
@@ -18,6 +17,8 @@ import WebpackBar from 'webpackbar';
import { version } from '../../package.json'; import { version } from '../../package.json';
import root from '../utils/root'; import root from '../utils/root';
const monacoPath = join(dirname(require.resolve('monaco-editor/editor.js')), '..', '..');
const { const {
list, list,
targets, targets,
@@ -134,6 +135,10 @@ export default async function (env: { watch?: boolean, production?: boolean, mea
extensions: ['.js', '.jsx', '.ts', '.tsx', '.cjs'], extensions: ['.js', '.jsx', '.ts', '.tsx', '.cjs'],
alias: { alias: {
vj: root(), 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/jsx-runtime': require.resolve('react/jsx-runtime'),
react: require.resolve('react'), react: require.resolve('react'),
'react-dom/client': require.resolve('react-dom/client'), 'react-dom/client': require.resolve('react-dom/client'),
@@ -282,7 +287,7 @@ export default async function (env: { watch?: boolean, production?: boolean, mea
jQuery: 'jquery', jQuery: 'jquery',
'window.jQuery': 'jquery', 'window.jQuery': 'jquery',
React: 'react', React: 'react',
monaco: 'monaco-editor/esm/vs/editor/editor.api', monaco: 'monaco-editor/editor',
}), }),
new ExtractCssPlugin({ new ExtractCssPlugin({
filename: `[name]-${version}.css?[fullhash:6]`, filename: `[name]-${version}.css?[fullhash:6]`,
@@ -313,24 +318,12 @@ export default async function (env: { watch?: boolean, production?: boolean, mea
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.VERSION': JSON.stringify(require('@hydrooj/ui-default/package.json').version), 'process.env.VERSION': JSON.stringify(require('@hydrooj/ui-default/package.json').version),
}), }),
new webpack.optimize.MinChunkSizePlugin({ // MinChunkSizePlugin merges worker chunks into the main chunk and breaks
minChunkSize: 128000, // 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(/\/(vscode-)?nls\.js/, require.resolve('../../components/monaco/nls')),
new webpack.NormalModuleReplacementPlugin(/^prettier[$/]/, root('../../modules/nop.ts')), new webpack.NormalModuleReplacementPlugin(/^prettier[$/]/, root('../../modules/nop.ts')),
new webpack.NormalModuleReplacementPlugin(/^highlightjs[$/]/, root('../../modules/nop.ts')), new webpack.NormalModuleReplacementPlugin(/^highlightjs[$/]/, root('../../modules/nop.ts')),
new webpack.NormalModuleReplacementPlugin(/core-js\/stable/, root('__core-js.js')), 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 ? [ ...env.measure ? [
new BundleAnalyzerPlugin({ analyzerPort: 'auto' }), new BundleAnalyzerPlugin({ analyzerPort: 'auto' }),
new DuplicatesPlugin(), new DuplicatesPlugin(),
+28 -1
View File
@@ -1,8 +1,11 @@
import './monaco.styl'; 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 $ from 'jquery';
import * as monaco from 'monaco-editor/editor';
import { EditorAction, registerEditorAction } from 'monaco-editor/esm/vs/editor/browser/editorExtensions'; 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 { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput';
import list from 'monaco-themes/themes/themelist.json'; import list from 'monaco-themes/themes/themelist.json';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@@ -10,6 +13,30 @@ import { i18n, request } from 'vj/utils';
export { renderMarkdown } from 'monaco-editor/esm/vs/base/browser/markdownRenderer'; 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 default monaco;
export const customOptions: monaco.editor.IStandaloneDiffEditorConstructionOptions = JSON.parse(localStorage.getItem('editor.config') || '{}'); export const customOptions: monaco.editor.IStandaloneDiffEditorConstructionOptions = JSON.parse(localStorage.getItem('editor.config') || '{}');
export function saveCustomOptions() { export function saveCustomOptions() {
@@ -1,6 +1,6 @@
import keyword from 'emojis-keywords'; import keyword from 'emojis-keywords';
import list from 'emojis-list'; 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 qface from 'qface';
import { api } from 'vj/utils'; import { api } from 'vj/utils';
@@ -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 types = require.context('@types/node/', true, /\.d\.ts$/, 'lazy-once');
const diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions = { const diagnosticsOptions: DiagnosticsOptions = {
noSemanticValidation: false, noSemanticValidation: false,
noSyntaxValidation: false, noSyntaxValidation: false,
noSuggestionDiagnostics: true, noSuggestionDiagnostics: true,
}; };
const compilerOptions: monaco.languages.typescript.CompilerOptions = { const compilerOptions: CompilerOptions = {
target: monaco.languages.typescript.ScriptTarget.ES2020, target: ScriptTarget.ES2020,
module: monaco.languages.typescript.ModuleKind.ESNext, module: ModuleKind.ESNext,
allowNonTsExtensions: true, allowNonTsExtensions: true,
}; };
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(diagnosticsOptions); javascriptDefaults.setDiagnosticsOptions(diagnosticsOptions);
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions(diagnosticsOptions); typescriptDefaults.setDiagnosticsOptions(diagnosticsOptions);
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions); javascriptDefaults.setCompilerOptions(compilerOptions);
monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions); typescriptDefaults.setCompilerOptions(compilerOptions);
const libSource = [ const libSource = [
'declare function readline(): string;', 'declare function readline(): string;',
'declare function print(content: string): void;', 'declare function print(content: string): void;',
].join('\n'); ].join('\n');
const libUri = 'ts:filename/basic.d.ts'; 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)); monaco.editor.createModel(libSource, 'typescript', monaco.Uri.parse(libUri));
const modules = []; const modules = [];
@@ -34,12 +38,16 @@ export async function loadTypes() {
modules.push(val.toString().split('declare module \'')[1].split('\'')[0]); modules.push(val.toString().split('declare module \'')[1].split('\'')[0]);
} }
const uri = `ts:node/${key.split('./')[1]}`; 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)); monaco.editor.createModel(val, 'typescript', monaco.Uri.parse(uri));
} }
let val = 'declare var require:'; let val = 'declare var require:';
for (const m of modules) val += `((id:'${m}')=>(typeof import('${m}')))&`; for (const m of modules) val += `((id:'${m}')=>(typeof import('${m}')))&`;
val += '((id:string)=>any)'; 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')); monaco.editor.createModel(val, 'typescript', monaco.Uri.parse('ts:node/require.d.ts'));
} }
export {
javascriptDefaults, ModuleKind, ModuleResolutionKind, ScriptTarget, typescriptDefaults,
};
@@ -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 { configureMonacoYaml } from 'monaco-yaml';
import problemConfigSchema from '../schema/problemconfig'; import problemConfigSchema from '../schema/problemconfig';
configureMonacoYaml(monaco, { configureMonacoYaml(monaco as any, {
validate: true, validate: true,
enableSchemaRequest: true, enableSchemaRequest: true,
hover: true, hover: true,
@@ -32,7 +32,11 @@ const loaders = {
if (resource) setLocaleData(resource); if (resource) setLocaleData(resource);
}, },
markdown: () => import('./languages/markdown'), 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'), yaml: () => import('./languages/yaml'),
external: async (monaco, feat) => { external: async (monaco, feat) => {
for (const item of await getFeatures(`monaco-${feat}`)) { for (const item of await getFeatures(`monaco-${feat}`)) {
@@ -49,7 +53,13 @@ const loaders = {
let loadPromise = Promise.resolve(); 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(); let s = Date.now();
await loadPromise; await loadPromise;
let resolve; let resolve;
@@ -59,6 +69,7 @@ export async function load(features = ['markdown']) {
console.log('Loading monaco editor'); console.log('Loading monaco editor');
} }
const res = await import('./index'); const res = await import('./index');
const featureExports: Record<string, any> = {};
if (!loaded) { if (!loaded) {
console.log('Loaded monaco editor in', Date.now() - s, 'ms'); console.log('Loaded monaco editor in', Date.now() - s, 'ms');
loaded = []; loaded = [];
@@ -75,7 +86,7 @@ export async function load(features = ['markdown']) {
s = Date.now(); s = Date.now();
console.log('Loading monaco feature:', feat); console.log('Loading monaco feature:', feat);
try { try {
if (loaders[feat]) await loaders[feat](); if (loaders[feat]) featureExports[feat] = await loaders[feat]();
else await loaders.external(res.default, feat); else await loaders.external(res.default, feat);
console.log('Loaded monaco feature:', feat, 'in', Date.now() - s, 'ms'); console.log('Loaded monaco feature:', feat, 'in', Date.now() - s, 'ms');
loaded.push(feat); loaded.push(feat);
@@ -90,6 +101,7 @@ export async function load(features = ['markdown']) {
registerAction: res.registerAction, registerAction: res.registerAction,
customOptions: res.customOptions, customOptions: res.customOptions,
renderMarkdown: res.renderMarkdown, renderMarkdown: res.renderMarkdown,
...featureExports,
}; };
} }
+7 -1
View File
@@ -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) { function format(message, args) {
let result; let result;
@@ -1,4 +1,4 @@
import type * as monaco from 'monaco-editor'; import type * as monaco from 'monaco-editor/editor';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { load } from 'vj/components/monaco/loader'; import { load } from 'vj/components/monaco/loader';
@@ -3,7 +3,7 @@ import SettingsIcon from '@vscode/codicons/src/icons/settings-gear.svg?react';
import { Allotment } from 'allotment'; import { Allotment } from 'allotment';
import $ from 'jquery'; import $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
import type * as monaco from 'monaco-editor'; import type * as monaco from 'monaco-editor/editor';
import React from 'react'; import React from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux'; import { useDispatch, useSelector, useStore } from 'react-redux';
import Dom from 'vj/components/react/DomComponent'; import Dom from 'vj/components/react/DomComponent';
@@ -38,9 +38,9 @@ class ScratchpadService extends Service {
load: Promise<void>; load: Promise<void>;
loadCallback: () => void; loadCallback: () => void;
editor: monaco.editor.IStandaloneCodeEditor; 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.editor = editor;
this.monaco = monaco; this.monaco = monaco;
this.loadCallback(); this.loadCallback();
+1 -2
View File
@@ -71,9 +71,8 @@
"md-editor-rt": "^7.1.0", "md-editor-rt": "^7.1.0",
"mini-css-extract-plugin": "^2.10.2", "mini-css-extract-plugin": "^2.10.2",
"moment": "^2.31.0", "moment": "^2.31.0",
"monaco-editor": "0.52.2", "monaco-editor": "0.57.0",
"monaco-editor-nls": "^3.1.0", "monaco-editor-nls": "^3.1.0",
"monaco-editor-webpack-plugin": "^7.1.1",
"monaco-themes": "0.4.7", "monaco-themes": "0.4.7",
"monaco-yaml": "5.4.1", "monaco-yaml": "5.4.1",
"nanoid": "^6.0.1", "nanoid": "^6.0.1",
+16
View File
@@ -18,3 +18,19 @@ declare module '*.scss' {
const content: string; const content: string;
export default content; 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<any>;
}
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;
}