动态加载Monaco Editor脚本失败,直接加载正常,求排查原因
动态加载Monaco Editor脚本报错问题
我尝试在不阻塞页面加载的情况下按顺序加载JS脚本,编写了一个返回Promise的加载函数,按URL顺序调用后脚本可依次加载,但出现以下错误:
VM4599 monacoManipulate.js:1 Uncaught ReferenceError: monaco is not defined at loader.js:657 GET https://example.com/vs/language/typescript/tsMode.js net::ERR_ABORTED 404 Refused to execute script from 'https://example.com/vs/language/typescript/tsMode.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
将相同脚本按相同顺序直接在head标签中加载时代码可正常运行,请问动态加载时我哪里出错了?
可正常运行的代码
<head> <link rel="stylesheet" data-name="vs/editor/editor.main" href="/monaco-editor/min/vs/editor/editor.main.css" /> <script defer> var require = { paths: { vs: '/monaco-editor/min/vs' } }; </script> <script src="/monaco-editor/min/vs/loader.js"></script> <script src="/monaco-editor/min/vs/editor/editor.main.nls.js"></script> <script src="/monaco-editor/min/vs/editor/editor.main.js"></script> </head>
无法正常运行的代码
function loadResource(url, callback) { return new Promise(function(resolve, reject) { var element; if (url.endsWith('.js')) { element = document.createElement("script"); element.type = "text/javascript"; element.src = url; } else if (url.endsWith('.css')) { element = document.createElement("link"); element.rel = "stylesheet"; element.href = url; element.type = "text/css"; if (url === '/monaco-editor/min/vs/editor/editor.main.css') { element.dataset.name = "vs/editor/editor.main"; } } else { reject(new Error('Unsupported file type ' + url)); return; } element.onload = () => { if (typeof callback === 'function') { resolve(callback()); } else { resolve(); } }; element.onerror = reject; document.getElementsByTagName("head")[0].appendChild(element); }) .catch(function(error) { console.error("The resource failed to load:", error); }); } const monaco_css = "/monaco-editor/min/vs/editor/editor.main.css"; const monaco_script = "/monaco-editor/min/vs/loader.js"; const monaco_i18n_script = "/monaco-editor/min/vs/editor/editor.main.nls.js"; const monaco_main_script = "/monaco-editor/min/vs/editor/editor.main.js"; const monaco_manipulate = "/js/users/monacoManipulate.js" loadResource(monaco_css) .then(() => loadResource(monaco_script)) .then(() => loadResource(monaco_i18n_script)) .then(() => loadResource(monaco_main_script)) .then(() => loadResource(monaco_manipulate)) .then(() => { require.config({ paths: { 'vs': '/monaco-editor/min/vs' } }); require(['vs/editor/editor.main'], function() { console.log("Monaco loaded") }); }) .catch((error) => { console.error("Some resources failed to load:", error); });
问题分析
- require配置时机错误:静态加载时,
require配置在loader.js加载前就已全局定义,而动态加载时你把require.config()放在所有脚本加载完成后,导致loader.js初始化时没有正确的路径配置,它会默认从根路径请求vs资源,才会出现https://example.com/vs/...的错误请求(返回404后实际是HTML页面,MIME类型不匹配)。 - 操作脚本加载时机错误:
monacoManipulate.js依赖monaco全局对象,但你在editor.main.js加载完成后立刻加载它,而Monaco的初始化是异步的,此时monaco还未生成,因此抛出ReferenceError。
修复方案
调整配置时机和脚本加载顺序,和静态加载逻辑对齐:
function loadResource(url, callback) { return new Promise(function(resolve, reject) { var element; if (url.endsWith('.js')) { element = document.createElement("script"); element.type = "text/javascript"; element.src = url; } else if (url.endsWith('.css')) { element = document.createElement("link"); element.rel = "stylesheet"; element.href = url; element.type = "text/css"; if (url === '/monaco-editor/min/vs/editor/editor.main.css') { element.dataset.name = "vs/editor/editor.main"; } } else { reject(new Error('Unsupported file type ' + url)); return; } element.onload = () => { if (typeof callback === 'function') { resolve(callback()); } else { resolve(); } }; element.onerror = reject; document.getElementsByTagName("head")[0].appendChild(element); }) .catch(function(error) { console.error("The resource failed to load:", error); }); } // 提前全局定义require配置,和静态加载逻辑一致 var require = { paths: { vs: '/monaco-editor/min/vs' } }; const monaco_css = "/monaco-editor/min/vs/editor/editor.main.css"; const monaco_script = "/monaco-editor/min/vs/loader.js"; const monaco_i18n_script = "/monaco-editor/min/vs/editor/editor.main.nls.js"; const monaco_main_script = "/monaco-editor/min/vs/editor/editor.main.js"; const monaco_manipulate = "/js/users/monacoManipulate.js" loadResource(monaco_css) .then(() => loadResource(monaco_script)) .then(() => loadResource(monaco_i18n_script)) .then(() => loadResource(monaco_main_script)) .then(() => { // 直接通过require加载editor.main,无需重复配置 require(['vs/editor/editor.main'], function() { console.log("Monaco loaded"); // 确认Monaco初始化完成后,再加载依赖它的操作脚本 loadResource(monaco_manipulate); }); }) .catch((error) => { console.error("Some resources failed to load:", error); });
关键说明
- 静态加载中,
defer脚本会在DOM解析完成后执行,但它定义的require全局对象会在loader.js加载前就存在,因此loader.js能正确读取路径配置。 - 动态加载必须保证
require配置在loader.js加载前就全局可用,否则loader.js会使用默认路径,导致资源请求错误。 editor.main.js加载完成不代表monaco对象立刻可用,必须通过require(['vs/editor/editor.main'], callback)的回调确认初始化完成,此时再加载依赖脚本才不会报错。
内容的提问来源于stack exchange,提问作者seriously
相关产品推荐
相关产品推荐

