冷知识。🤔 何为 AMD 匿名模块冲突???

Uncaught Error: Can only have one anonymous define call per script file at c.enqueueDefineAnonymousModule (loader.js:1340:23)

最近踩了一个 amd 匿名模块的坑。

关于 amd

啥是 amd?amd 是 web 前端上古时代的模块化规范。 amd(Asynchronous Module Definition)是​​面向浏览器端的异步模块加载规范​​,核心解决依赖管理与异步加载问题。典型实现库为RequireJS。

虽然现在遍地都是 esm + cjs,但 amd 也应用于许多运行时的插件系统(如 vscode 编辑器)。

基本语法

js 复制代码
    // 模块注册
    define([id?], [dependencies?], factory)
    // 模块导入使用
    require(id, (mod) => {})

amd 的匿名/具名模块

匿名模块指的是 define 时,没有传入模块 id 的模块,反之则是具名模块,如下

js 复制代码
// 匿名模块 a.js
define([], () => {
    return { name: 'a' }
})
// 具名模块 b.js
define('b', [], () => {
    return { name: 'b' }
})

大部分情况下,模块开发者都不需关注模块的 id,使用匿名模块进行 define 符合开发直觉。

在引入匿名模块时,通过 require 函数指定模块的 id 即可,id 也可以是一个 uri。

html 复制代码
<script src='amd_loader.js'></script>
<script>
  // 引入模块
  require(['a'], function(a) {
  });
</script>
<!-- 然后加载器会请求 a.js 并执行,该文件包含: -->
<script src="a.js">
  // a.js 内容
  define([], function() {
    return { name: 'a' }; // 这个匿名模块会被关联到 'a'
  });
</script>

具名模块则用于模块的预定义,即提前声明,后面再通过 require 引入使用,如

html 复制代码
<script src='amd_loader.js'></script>
<script src="b.js">
  // 声明一个 id 为 b 的模块
  define('b', [], function() {
    return { name: 'b' }; // 这个匿名模块会被关联到 'a'
  });
</script>
<script>
  // require 该模块
  require(['b'], function(b) {
    // 使用模块
  });
</script>

提问:假设我引入了一个「声明了两个匿名模块」的模块,会怎样?如下,最终真正引入的是 a1 还是 a2 ???

html 复制代码
<script src='amd_loader.js'></script>
<script>
  require(['a.js'], function(a) {
     console.log(a.name);
  });
</script>
<script src="a.js">
  // a.js 内容
  define([], function() {
    return { name: 'a1' };
  });
  define([], function() {
    return { name: 'a2' }; // 这个匿名模块会被关联到 'a'
  });
</script>

答案是跟 amd 的实现有关系,在 vscode 提供的 amd loader 中,同一文件声明了两个匿名模块会直接抛出错误。

如果是高级版本的 requirejs,则会进行兼容,至少保证不会白屏。

下图为 vscode amd loader 的匿名模块导入检测逻辑。

匿名模块冲突

💩⛰️ 项目中使用到了 monaco editor(一个基于 vscode 的 web 端编辑器),以及在运行时也用到了 js-beautify,prettier 这两个包,进行代码格式化。

在某些页面打开时出现一些奇怪的控制台报错 Can only have one anonymous define call per script file

分析思路

这个报错来自于 vscode 的 loader 文件,用于初始化环境,导入导出插件等用途,是 vscode 十分关键的文件。

vscode 的 loader 文件中包含一段默认逻辑,若当前浏览器不是 amd 环境,vscode 会将初始化一个 amd 环境,即声明 window.define, window.define.amd, window.require 等全局变量,这也会导致当前的浏览器环境直接变成 amd 环境,影响到后续异步模块的初始化行为。

于是打了几个断点,如下,在源码中可以看到 js-beautify 的入口文件 define 了一个匿名模块。也就是只要引入了 js-beautify 就会马上声明一个匿名模块。

编译后的代码(js-beautify)

源代码(js-beautify)

接着,在 prettier 的入口文件中,也有同样的行为,声明了一个匿名模块。

编译后的代码(prettier)

源代码(prettier)

因此,原因就是 define 了两个匿名模块,导致 amd loader 抛出错误。

以上可以等价这段伪代码

html 复制代码
<script src='vscode_amd_loader.js'>
    // 初始化 vscode 的 amd loader...
</script>
<script src="async_js-beautify.js">
    // js-beautify.js 内容,定义匿名模块
    define([
        "./lib/beautify",
        "./lib/beautify-css",
        "./lib/beautify-html"
    ], function(js_beautify, css_beautify, html_beautify) {
        return get_beautify(js_beautify, css_beautify, html_beautify);
    });
</script>
<script src="async_prettier.js">
    // prettier.js 内容,再次定义匿名模块
    define([], factory);
</script>

提前 define 了两个匿名模块,此时会被 vscode amd loader 检测到,最终导致白屏。

修复方式

以下两种方式皆可

  • 不用 vscode 的 amd loader,用 requirejs 先把 amd 环境初始化好

  • 修改 js-beautify 和 prettier 包的匿名模块初始化行为。

完结,不撒花🌸。

相关推荐
—Qeyser11 分钟前
用 Deepseek 写的uniapp血型遗传查询工具
前端·javascript·ai·chatgpt·uni-app·deepseek
codingandsleeping12 分钟前
HTTP1.0、1.1、2.0 的区别
前端·网络协议·http
小满blue14 分钟前
uniapp实现目录树效果,异步加载数据
前端·uni-app
喜樂的CC2 小时前
[react]Next.js之自适应布局和高清屏幕适配解决方案
javascript·react.js·postcss
天天扭码2 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫2 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
烛阴3 小时前
手把手教你搭建 Express 日志系统,告别线上事故!
javascript·后端·express
拉不动的猪3 小时前
设计模式之------策略模式
前端·javascript·面试
旭久3 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc3 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf