ES Modules防坑指南核心是直击高频报错场景:路径必须合法(如./开头)、import/export须在顶层、default与具名导出勿混用、警惕CommonJS与ESM互操作陷阱、循环依赖需拆解共享模块规避undefined。

ES Modules 防坑指南的核心不是罗列语法,而是聚焦“哪些地方一写错就当场报错、打包失败或运行时取不到值”。它得从开发者真实出错的瞬间出发——比如 Uncaught TypeError: Cannot resolve module、undefined is not a function、export 'xxx' was not found 这类高频错误背后,往往藏着对模块机制的误解。下面按开发流程梳理关键防坑点,直击要害。
路径和标识符必须合法,浏览器不认“想当然”的写法
在原生 ESM 环境(如 <script type="module"></script>)中,模块路径不是由构建工具接管的,浏览器会严格校验:
-
不能直接写
import { createApp } from 'vue'—— 浏览器不认识 bare specifier,除非配了<script type="importmap"></script> -
相对路径必须带
./或../:写import utils from 'utils.js'会失败,必须是import utils from './utils.js' -
绝对路径以
/开头,指向站点根目录,不是项目根目录;误写成import x from '/src/x.js'而实际部署在子路径下,就会 404
import/export 必须写在顶层,动态逻辑里不能藏
ESM 是静态分析的,所有依赖关系必须在代码解析阶段就能确定:
-
禁止在 if、for、函数内写 import:比如
if (env === 'dev') { import('./debug.js') }会直接报语法错误 -
需要条件加载?用动态 import():
const { debug } = await import('./debug.js'),返回 Promise,且只能用于模块路径字符串字面量 -
export 也不能嵌套:不能在 if 块里写
export const flag = true,否则编译阶段就中断
default 与具名导出别混用,尤其跨 CommonJS 时
CommonJS 的 module.exports = xxx 在 ESM 中被包装为 default,但行为容易误判:
-
CommonJS 模块(如老版 lodash)没有 default 导出,却有具名属性:
import _ from 'lodash'实际拿到的是整个对象,而import { debounce } from 'lodash'会失败(除非用支持的插件或 esm 版本) - 自己写模块时,避免同时用 export default 和 export const:别人 import 时容易搞混写法,也增加 tree-shaking 不稳定风险
-
检查第三方包是否真正支持 ESM:看它的
package.json是否有"type": "module"或导出字段"exports"明确指向.mjs或es/目录
循环依赖不报错但值可能是 undefined,得靠设计规避
ESM 允许循环导入,但模块初始化顺序决定了变量是否已赋值:
- A.js 导出一个对象,B.js 导入它并修改其属性;A.js 同时又导入 B.js 的某个函数 —— 若 B.js 尚未执行完,A.js 可能拿到空对象或未初始化的值
- 解法不是加 loading 判断,而是拆出共享状态层:把共用数据或工具抽到第三个模块 C.js,A 和 B 都只 import C,不互相引用
- 用 const 声明 + Object.freeze() 锁定导出对象,可提前暴露稳定接口,减少运行时不确定性











