如何修复 'Cannot use import statement outside a module' 错误
你写了一个简洁的 import 语句,运行代码后立即遇到这个错误:
SyntaxError: Cannot use import statement outside a module
这个 JavaScript ES 模块错误是开发者在使用 Node.js、浏览器和测试环境时最常遇到的困惑之一。修复方法并不总是相同——应用错误的修复方法会让情况变得更糟。以下是如何诊断你的情况并正确解决它的方法。
核心要点
- 这个错误是模块系统不匹配,而不是语法错误——你的运行时期望 CommonJS 但遇到了 ES 模块的
import语法。 - 在 Node.js 中,在
package.json中设置"type": "module"或使用.mjs文件扩展名来启用 ESM。 - 在浏览器中,在
<script>标签中添加type="module",这样引擎会将文件视为 ES 模块。 - 在 Jest 中,配置 Babel 将 ESM 转换为 CommonJS,并调整
transformIgnorePatterns以处理仅支持 ESM 的第三方包。 - 在应用修复方法之前,始终先诊断你的环境——Node.js、浏览器还是测试运行器。
为什么会出现这个错误
这个错误意味着你的运行时遇到了 ES 模块语法(import),但期望的是其他内容——通常是 CommonJS(require)。这是一个模块系统不匹配,而不是代码中的语法错误。
JavaScript 有两种模块系统:
| 系统 | 语法 | 默认使用环境 |
|---|---|---|
| CommonJS (CJS) | require() | Node.js(传统方式) |
| ES Modules (ESM) | import | 浏览器、现代 Node.js |
运行时根据配置决定使用哪个系统——而不是代码本身。这就是为什么同样的 import 语句在一个项目中有效,在另一个项目中却失败的原因。
如何在 Node.js 中修复模块外导入语句错误
Node.js 将 .js 文件视为 CommonJS,除非在 package.json 中设置了 "type": "module"。要使用 import,你需要显式启用 ESM。
方法 1:在 package.json 中设置 "type": "module"
{
"name": "my-app",
"version": "1.0.0",
"type": "module"
}
这告诉 Node.js 将项目中的所有 .js 文件视为 ES 模块。有关 Node 如何确定模块类型的详细信息,请参阅官方 Node.js ES 模块文档。
方法 2:使用 .mjs 文件扩展名
将文件从 app.js 重命名为 app.mjs。无论 package.json 如何配置,Node.js 始终将 .mjs 文件视为 ESM。
方法 3:对 CommonJS 文件使用 .cjs
如果你的项目使用 "type": "module" 但某个文件需要使用 require(),给它一个 .cjs 扩展名。
重要提示: 不要在没有检查其他文件的情况下就添加
"type": "module"。任何使用require()、module.exports或__dirname的文件在 ESM 下都会出错。
在浏览器 JavaScript 中修复错误
在浏览器中,静态 import 只能在模块脚本中使用。如果你的 <script> 标签没有声明 type="module",浏览器会将其视为经典脚本并拒绝 import 语法。
<!-- 这会抛出错误 -->
<script src="app.js"></script>
<!-- 这样可以工作 -->
<script type="module" src="app.js"></script>
这种行为是现代浏览器实现的标准 JavaScript 模块规范的一部分。
注意,模块脚本是有作用域的——在其中声明的变量不能全局访问。这就是为什么你可能添加了 type="module" 后会看到一个 ReferenceError,提示你期望的全局变量不存在。解决方案是显式导出和导入值,而不是依赖全局作用域。
Discover how at OpenReplay.com.
在 Jest 和其他测试环境中修复错误
Jest 在 Node.js 中运行,传统上默认使用 CommonJS,即使你的源代码使用 ESM。这是测试套件中出现 “cannot use import statement outside a module” 错误的常见原因。
一种方法是配置 Babel 在测试期间将 ESM 转换为 CommonJS:
// babel.config.js
module.exports = {
presets: ['@babel/preset-env'],
}
或者,如果你的项目已经使用 ES 模块,Jest 可以配置为在原生 ESM 模式下运行测试。详情请参阅 Jest ECMAScript 模块文档。
如果第三方包(如 swiper、lodash-es 或类似的仅支持 ESM 的库)导致错误,你需要告诉 Jest 转换它而不是忽略它:
// jest.config.js
module.exports = {
transformIgnorePatterns: [
'/node_modules/(?!(swiper|ssr-window|dom7)/)',
],
}
关键点:transformIgnorePatterns 使用负向前瞻。你是在说”忽略 node_modules 中的所有内容,除了这些包”。将它们列在单独的数组条目中不起作用——它们必须用 | 组合在单个正则表达式模式中。
TypeScript 配置
如果你使用 TypeScript,检查你的 tsconfig.json。module 字段控制 TypeScript 在编译输出中生成什么模块语法:
{
"compilerOptions": {
"module": "ESNext",
"target": "ESNext"
}
}
将 module 设置为 CommonJS 同时编写 import 语句是有效的 TypeScript——编译器会在输出中将 import 转换为 require()。但是,将 module 设置为 ESNext 同时在 CommonJS Node.js 环境中运行输出(没有在 package.json 中设置 "type": "module")会在运行时触发错误。确保你的 tsconfig.json 模块设置与运行时实际期望的匹配。
先诊断再修复
同样的错误在不同环境中出现的原因不同。在应用任何修复方法之前,问自己:
- 错误发生在哪里——Node.js、浏览器还是测试运行器?
- 你的
package.json中的"type"字段是什么? - 你是在使用打包工具,还是直接运行文件?
回答这三个问题每次都能指引你找到正确的解决方案。
常见问题
可以,但你需要使用文件扩展名来区分它们。扩展名为 .mjs 的文件始终被视为 ES 模块,扩展名为 .cjs 的文件始终被视为 CommonJS,无论 package.json 中的 type 字段如何设置。这让你可以在单个项目中并行使用两种系统。
不会。node_modules 中的每个包都有自己的 package.json,所以你的 type 字段只适用于你的项目文件。依赖项独立定义自己的模块系统。错误通常来自你自己的文件,或者在没有适当转换的情况下在 CommonJS 上下文中导入仅支持 ESM 的依赖项。
像 Webpack 和 Vite 这样的打包工具在构建步骤中处理 import 语句并生成单个输出文件。它们在运行时看到之前就解析了模块语法。当你在 Node.js 或浏览器中直接运行文件而不使用打包工具时,运行时本身必须理解模块格式,这就是不匹配导致此错误的地方。
这些 CommonJS 全局变量在 ES 模块中不可用。相反,你可以使用 import.meta.url 重建它们。例如,使用 new URL(import.meta.url).pathname 获取文件路径,或将其与 path 模块和 url 模块中的 fileURLToPath 结合使用来复制 __dirname 行为。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.