Jest: 解决 “cannot use import statement outside a module” 错误 – wiki基地


解读与应对 Jest 中的 “cannot use import statement outside a module” 错误

在使用 Jest 编写和运行 JavaScript 或 TypeScript 测试时,你可能遇到过一个令人头疼的错误:SyntaxError: Cannot use import statement outside a module。这个错误通常在 Jest 尝试加载你的测试文件或被测试代码时抛出,指示 Node.js 环境遇到了 ES Modules (import/export) 语法,但它当前运行在 CommonJS 环境下。

本文将深入探讨这个错误的根本原因,解释为何它在使用 Jest 时频繁出现,并为你提供多种行之有效的解决方案,帮助你顺利构建和运行现代化 JavaScript 项目的测试。

1. 错误溯源:CommonJS 与 ES Modules 的分野

要理解 Cannot use import statement outside a module 错误,首先需要了解 JavaScript 模块系统的历史和现状。

在很长一段时间里,JavaScript 在浏览器端没有官方的模块系统。开发者依赖于全局变量或 IIFE(Immediately Invoked Function Expression)来组织代码。在服务器端,Node.js 的出现引入了 CommonJS 模块系统,它使用 require() 导入模块和 module.exports 导出模块。这是 Node.js 的标准,至今仍广泛应用于许多项目中。

CommonJS 模块的特点:
* 使用 require() 函数同步加载模块。
* 使用 module.exportsexports 导出模块。
* 模块加载路径相对于当前文件。
* 运行时加载。

示例 CommonJS 代码:

“`javascript
// utils.js
const add = (a, b) => a + b;
module.exports = { add };

// main.js
const { add } = require(‘./utils’);
console.log(add(1, 2)); // 3
“`

随着 JavaScript 语言标准(ECMAScript)的发展,ES2015 (ES6) 引入了官方的 ES Modules (ESM) 模块系统。它设计之初就考虑了浏览器和服务器端的通用性。

ES Modules 的特点:
* 使用 import 语句导入模块。
* 使用 export 语句导出模块。
* 支持静态分析,可以在编译时确定模块依赖关系。
* 默认异步加载(尽管在 Node.js 中同步加载)。

示例 ES Modules 代码:

“`javascript
// utils.js
export const add = (a, b) => a + b;

// main.js
import { add } from ‘./utils’;
console.log(add(1, 2)); // 3
“`

Node.js 对这两种模块系统的支持演进:

Node.js 起初只支持 CommonJS。为了兼容 ES Modules,Node.js 采取了一种策略来区分文件是 CommonJS 还是 ES Modules:

  1. 文件扩展名: .js 文件默认被视为 CommonJS。.mjs 文件被视为 ES Modules。.cjs 文件被强制视为 CommonJS。
  2. package.json 中的 type 字段: 在项目的 package.json 文件中设置 "type": "module",会使得该项目目录及其子目录下的 .js 文件默认被视为 ES Modules(除非是 .cjs 文件)。如果设置为 "type": "commonjs" (或者不设置,因为这是默认值),则 .js 文件默认被视为 CommonJS。
  3. 内部逻辑: Node.js 在加载时会检查文件扩展名和 package.jsontype 字段来确定模块类型。

当 Node.js 尝试加载一个 .js 文件(或在 type: "commonjs" 环境下的任何文件),并且在该文件中遇到了 importexport 语法时,它会认为“这是一个 ES Modules 文件,但我被指示要把它当作 CommonJS 文件来处理”,于是就抛出了 SyntaxError: Cannot use import statement outside a module 错误。

2. 为什么 Jest 会遇到这个错误?

Jest 是一个 JavaScript 测试框架,它默认在 Node.js 环境中运行你的测试代码。当你运行 jest 命令时,Jest 会启动一个或多个 Node.js 进程,加载你的测试文件(通常是 .test.js, .spec.js, .ts 等),并执行其中的代码。

这个错误在使用 Jest 时频繁出现的原因在于:

  1. 现代 JavaScript 开发趋势: 越来越多的项目采用 ES Modules 来组织代码,无论是应用程序代码还是库。
  2. Jest 的默认行为: 默认情况下,Jest 运行的 Node.js 环境可能并未配置为原生支持 ES Modules。Jest 会加载你的 .js 测试文件以及这些测试文件 import 的应用程序代码或第三方库。
  3. 未经处理的 ES Modules 语法: 如果你的测试文件或它们依赖的代码使用了 import/export 语法,而 Jest 运行的 Node.js 环境将其识别为 CommonJS 文件(因为是 .js 文件且没有 type: "module" 设置),就会触发这个错误。

简而言之,Jest 在加载包含 import/export 语法的代码时,其运行环境未能正确地将其识别或处理为 ES Modules。

这个问题可能出现在:
* 你的测试文件本身使用了 import/export
* 你的应用程序代码(被测试的代码)使用了 import/export
* 你使用的第三方库在 node_modules 中发布了使用 import/export 语法的代码,并且没有被正确地转换为 CommonJS 或被 Jest 处理。

理解了错误原因,我们就可以针对性地提出解决方案。核心思想是:让 Jest(或其运行的 Node.js 环境)能够理解并正确执行包含 import/export 语法的代码。

3. 解决方案一:使用转译器 (Transpilers)

这是最常见、最推荐,也是最健壮的解决方案。转译器(如 Babel 或 swc)可以将现代 JavaScript 语法(包括 ES Modules 的 import/export)转换为旧版本 JavaScript 或 CommonJS 语法,使其能够在各种环境中运行,包括不支持原生 ES Modules 的 Node.js 环境。

Jest 提供了 transform 配置选项,允许你在运行测试之前,使用指定的转译器处理你的源代码。常用的转译器集成有 babel-jest (基于 Babel) 和 swc-jest (基于 swc)。

方案 1.1: 使用 Babel (通过 babel-jest)

Babel 是一个广泛使用的 JavaScript 转译器,可以将 ES Modules 转换为 CommonJS。babel-jest 是 Jest 与 Babel 集成的官方包。

工作原理:
当你配置 Jest 使用 babel-jest 时,Jest 在加载文件之前,会先通过 Babel 处理这些文件。Babel 根据你的配置(例如 @babel/preset-env)会将 import/export 语句转换为 require()/module.exports。然后 Jest 就可以在默认的 CommonJS 环境下执行这些已经被转换的代码了。

实现步骤:

  1. 安装必要的依赖:

    “`bash
    npm install –save-dev jest babel-jest @babel/core @babel/preset-env

    或者使用 yarn

    yarn add –dev jest babel-jest @babel/core @babel/preset-env
    ``
    *
    jest: Jest 测试框架本身。
    *
    babel-jest: Jest 和 Babel 的桥梁。
    *
    @babel/core: Babel 的核心库。
    *
    @babel/preset-env`: Babel 的预设,包含了一系列插件,可以根据你指定的环境自动转换最新的 JavaScript 语法,包括 ES Modules 到 CommonJS。

  2. 配置 Babel:
    在项目根目录创建一个 Babel 配置文件。常见的配置文件名有 .babelrc.js, .babelrc, babel.config.js,或者在 package.json 中添加 "babel" 字段。推荐使用 babel.config.js,因为它支持更多高级配置选项。

    创建 babel.config.js 文件:

    javascript
    // babel.config.js
    module.exports = {
    presets: [
    [
    '@babel/preset-env',
    {
    // target node environment for Jest
    targets: {
    node: 'current', // Transpile for the current Node.js version Jest is running on
    },
    // Optional: configure how modules are handled.
    // 'auto' lets @babel/preset-env decide (usually converts to CommonJS for Node targets).
    // 'commonjs' forces conversion to CommonJS.
    // false keeps ES modules (only if you handle it otherwise, e.g., Node native ESM or bundler)
    modules: 'auto',
    },
    ],
    ],
    // If you are using React or TypeScript, you might add more presets:
    // presets: ['@babel/preset-react', '@babel/preset-typescript', /* ... */ '@babel/preset-env'],
    };

    * @babel/preset-env: 这个预设会根据 targets.node: 'current' 将代码转换为当前 Node.js 版本支持的语法。关键在于,当目标环境是 Node.js 且 modules 设置不是 false 时,@babel/preset-env 默认会将 ES Modules (import/export) 转换为 CommonJS (require/module.exports)。这正是解决 Cannot use import statement outside a module 错误的关键。

  3. 配置 Jest:
    Jest 默认会查找 babel-jest 如果它被安装了。你通常不需要在 jest.config.js 中显式配置 transform。然而,明确配置可以增加可读性和控制性。

    创建 jest.config.js 文件(如果还没有):

    javascript
    // jest.config.js
    module.exports = {
    // Automatically transform files using babel-jest if installed
    // transform: {
    // '^.+\\.[jt]sx?$': 'babel-jest',
    // },
    // By default, transformIgnorePatterns ignores node_modules.
    // If a library in node_modules uses ES modules and is not transpiled by the author,
    // you might need to adjust this (see Solution 3).
    // transformIgnorePatterns: ['/node_modules/'],
    // ... other Jest configurations
    };

    * Jest 默认的 transform 设置通常已经包含了一个规则(如 '\\.[jt]sx?$': 'babel-jest''\\.[jt]s$': 'babel-jest'),当它检测到 babel-jest 安装时,会使用它来处理 .js, .jsx, .ts, .tsx 文件。所以很多时候你甚至不需要显式写出 transform 配置。

  4. 运行测试:

    “`bash
    npm test

    或者 yarn test

    “`

现在,Jest 在运行测试前会通过 Babel 处理你的代码,将 import/export 转换为 CommonJS,从而避免了 SyntaxError

优点:
* 兼容性强: 几乎兼容所有 Node.js 版本和复杂的 JavaScript 语法。
* 功能强大: 除了模块转换,还能处理 JSX, TypeScript, 其他新的 ES 语法等。
* 生态成熟: Babel 生态系统庞大,插件和预设丰富。

缺点:
* 配置略显复杂: 需要安装多个包并配置 Babel。
* 测试速度: 转译会增加测试的启动和执行时间,尤其是在大型项目中(但通常是可接受的)。

方案 1.2: 使用 swc (通过 swc-jest)

swc 是一个用 Rust 编写的超快速的 JavaScript/TypeScript 转译器,通常比 Babel 快很多。swc-jest 是 Jest 与 swc 的集成。

工作原理:
类似于 babel-jestswc-jest 也是 Jest 的一个 transform。它利用 swc 来处理文件,将 ES Modules 转换为 CommonJS。因为 swc 是用 Rust 编写的原生代码,其转译速度通常远超基于 JavaScript 的 Babel。

实现步骤:

  1. 安装必要的依赖:

    “`bash
    npm install –save-dev jest @swc/core swc-jest

    或者 yarn

    yarn add –dev jest @swc/core swc-jest
    ``
    *
    @swc/core: swc 的核心库。
    *
    swc-jest`: Jest 和 swc 的桥梁。

  2. 配置 swc:
    在项目根目录创建 .swcrc 文件。

    创建 .swcrc 文件:

    json
    // .swcrc
    {
    "jsc": {
    "parser": {
    "syntax": "ecmascript", // or "typescript" if using TS
    "jsx": true // Set to true if using React JSX
    },
    "transform": {
    "react": {
    "runtime": "automatic" // Optional: Set to "automatic" for new JSX transform
    }
    },
    "target": "es2017", // Or a Node version like "node14"
    "loose": false,
    "externalHelpers": false,
    // Key setting: convert ES modules to CommonJS
    "paths": {},
    "baseUrl": "."
    },
    "module": {
    "type": "commonjs" // This tells swc to output CommonJS modules
    },
    // If using TypeScript, you might configure sourceType and other options
    // "sourceType": "module"
    }

    * "module": { "type": "commonjs" }: 这是将 ES Modules 转换为 CommonJS 的关键配置。
    * "jsc.parser.syntax": 根据你的代码类型设置为 "ecmascript""typescript"
    * "jsc.parser.jsx": 如果使用 JSX 则设置为 true

  3. 配置 Jest:
    jest.config.js 中配置 transform 使用 swc-jest

    创建或修改 jest.config.js 文件:

    javascript
    // jest.config.js
    module.exports = {
    transform: {
    // Adjust regex based on your file types (js, jsx, ts, tsx)
    '^.+\\.(t|j)sx?$': [
    'swc-jest',
    {
    // SWC config options can be placed here or in .swcrc
    // The module.type: 'commonjs' is the most important for this error
    // For example, if you don't use .swcrc:
    // jsc: {
    // parser: { syntax: 'typescript', jsx: true },
    // transform: { react: { runtime: 'automatic' } },
    // target: 'es2017',
    // },
    // module: { type: 'commonjs' },
    },
    ],
    },
    // Default transformIgnorePatterns ignores node_modules.
    // If a library in node_modules uses ES modules and is not transpiled,
    // you might need to adjust this (see Solution 3).
    // transformIgnorePatterns: ['/node_modules/'],
    // ... other Jest configurations
    };

    * 确保 transform 正则表达式匹配你的文件类型(.js, .jsx, .ts, .tsx)。
    * 你可以在 .swcrc 文件中集中配置 swc,或者在 jest.config.jstransform 配置中为 swc-jest 提供配置对象。使用 .swcrc 更常见,因为它可以在其他地方(如构建工具)复用。

  4. 运行测试:

    “`bash
    npm test

    或者 yarn test

    “`

优点:
* 速度极快: 测试运行时间显著减少。
* 功能齐全: 支持现代 JavaScript 语法、JSX、TypeScript。
* 配置简洁: .swcrc 配置相对简单。

缺点:
* 相对较新: 尽管已经很稳定,但生态和社区支持不如 Babel 历史悠久。

总结: 使用转译器是解决 Jest 中 Cannot use import statement outside a module 错误的首选方法。对于大多数项目,它提供了最稳定和全面的解决方案,特别是当你已经在项目中使用了 Babel 或 swc 来处理其他现代语法时。选择 Babel 还是 swc 主要取决于你对构建速度的需求和个人偏好。

4. 解决方案二:使用 Node.js 原生 ES Modules 支持

Node.js v12+ 版本(以及 v13.2+ 中功能更完善)开始正式支持 ES Modules。可以通过在 package.json 中设置 "type": "module" 来启用。

工作原理:
当你在项目的 package.json 中设置 "type": "module" 时,Node.js(以及在其上运行的 Jest)会将项目目录下的 .js 文件默认视为 ES Modules。这意味着 Node.js 将直接理解并执行 import/export 语法,而无需转译为 CommonJS。

实现步骤:

  1. 确保 Node.js 版本: 你的 Node.js 版本必须支持原生 ESM。推荐使用 Node.js v14 或更高版本以获得更稳定的支持。
  2. 修改 package.json 在项目根目录的 package.json 文件中添加 type 字段。

    json
    {
    "name": "your-project",
    "version": "1.0.0",
    "type": "module", // Add this line
    // ... other fields
    }

  3. 调整文件扩展名(如果需要):

    • 如果你的 Jest 配置文件 (jest.config.js) 或 setup 文件使用了 CommonJS 语法 (require/module.exports),并且位于设置了 "type": "module" 的目录下,你需要将文件扩展名更改为 .cjs。例如,将 jest.config.js 重命名为 jest.config.cjs
    • 如果你的测试文件或应用程序代码仍然使用 CommonJS 语法,你需要将它们的扩展名改为 .cjs
    • 在 ES Modules 环境下,导入本地模块时通常需要指定完整的文件扩展名(例如 import { add } from './utils.js'; 而不是 import { add } from './utils';),除非依赖于 bundler 的解析能力。Jest 在某些情况下可以处理这种无扩展名导入,但这取决于具体配置和版本。
  4. 运行测试:

    “`bash
    npm test

    或者 yarn test

    “`

Jest 应该能够直接运行在启用了原生 ESM 的 Node.js 环境中。

优点:
* 无需额外转译器: 对于纯粹的 ES Modules 语法,不需要 Babel 或 swc。
* 符合未来趋势: 与 Node.js 和 JavaScript 语言的演进方向一致。
* 启动速度可能更快: 避免了转译步骤(尽管大型项目可能仍然需要转译处理第三方库或其他语法)。

缺点:
* 兼容性问题: 需要较新版本的 Node.js。
* CommonJS 模块兼容性: 虽然 Node.js 提供了 require() 的部分支持(例如 import pkg from 'commonjs-package';),但在 ES Modules 环境下使用 CommonJS 模块可能仍然会遇到一些边缘情况或性能问题。特别是一些 CommonJS 模块可能无法正确地通过 import 导入 default export。
* 文件路径和扩展名: 在原生 ESM 中,相对路径导入通常需要包含文件扩展名,这与 CommonJS 或使用 bundler 的习惯不同,可能需要调整代码。
* 影响整个项目: "type": "module" 设置影响项目中的所有 .js 文件,可能需要对现有 CommonJS 代码进行迁移或重命名。
* Jest 配置复杂性: Jest 本身的配置(如 jest.config.js)如果需要使用 CommonJS,必须重命名为 .cjs

总结: 使用 Node.js 原生 ES Modules 支持是一种更现代化的方法,对于全新项目或愿意全面迁移的项目来说是一个不错的选择。但对于现有大型项目或依赖大量 CommonJS 库的项目,可能会引入新的兼容性问题,并且需要对项目结构和代码风格进行调整。在这种情况下,使用转译器通常是更平滑的过渡方案。

5. 解决方案三:调整 transformIgnorePatterns

有时,Cannot use import statement outside a module 错误不是因为你自己的代码使用了 ES Modules,而是因为你安装的某个第三方库在 node_modules 中发布了未经转译的 ES Modules 代码。

Jest 默认有一个 transformIgnorePatterns 配置选项,其默认值通常是 /node_modules/。这意味着 Jest 不会 使用 transform 配置(如 Babel 或 swc)处理 node_modules 目录下的文件。这是为了提高测试运行速度,因为大多数 npm 包应该已经发布为 CommonJS 或预转译的代码。

然而,有些现代库为了优化打包或利用原生 ESM 特性,可能会直接在 node_modules 中发布 ESM 格式的代码。当 Jest 试图加载这些未被转译的 ESM 库时,就会抛出错误。

工作原理:
要解决这个问题,你需要修改 transformIgnorePatterns,使其 不要 忽略那些发布了 ESM 代码的特定第三方库。这样,Jest 就会使用你的转译器(如果配置了 transform,如 babel-jestswc-jest)来处理这些库的代码,将其转换为 CommonJS。

实现步骤:

  1. 识别问题库: 查看 Jest 错误堆栈跟踪,找出是哪个文件(通常在 node_modules 路径下)导致了错误。错误信息会指示具体的 importexport 语句所在的行和文件。
  2. 修改 transformIgnorePatterns 在你的 jest.config.js 文件或 package.jsonjest 字段中,修改 transformIgnorePatterns 配置。你需要使用正则表达式来匹配除了问题库之外的所有 node_modules 路径。

    假设问题库是 problematic-esm-library,其名称在 node_modules 目录下的路径是 node_modules/problematic-esm-library/

    修改 jest.config.js

    javascript
    // jest.config.js
    module.exports = {
    // Make sure you have a transform configured, e.g., using babel-jest or swc-jest
    // transform: {
    // '^.+\\.[jt]sx?$': 'babel-jest', // or 'swc-jest'
    // },
    transformIgnorePatterns: [
    // Ignore all node_modules except for 'problematic-esm-library'
    '/node_modules/(?!problematic-esm-library/).+\\.js$',
    // If the library uses JSX or TS, adjust the regex:
    // '/node_modules/(?!problematic-esm-library/).+\\.(js|jsx|ts|tsx)$',
    ],
    // ... other Jest configurations
    };

    * /node_modules/: 匹配 node_modules 目录。
    * ?!problematic-esm-library/: 这是一个负向先行断言(negative lookahead)。它表示“后面的内容不能是 problematic-esm-library/”。
    * /.+\\.js$: 匹配以 .js 结尾的文件(如果库文件是 .mjs 或其他类型,可能需要调整)。
    * 整个正则表达式 /node_modules/(?!problematic-esm-library/).+\\.js$/ 的意思是:“匹配位于 node_modules 目录下,但不包含 problematic-esm-library/ 子路径的 .js 文件”。通过将这个正则表达式设置为 transformIgnorePatterns 的值,我们告诉 Jest 忽略所有这些文件(即不转译它们),从而 不忽略 那些包含 problematic-esm-library 的路径下的文件(即要转译它们)。

    如果你的项目使用了 React 等框架,你的 transformIgnorePatterns 可能更复杂,例如,它可能需要同时不忽略 problematic-esm-libraryreact-native 等库:

    javascript
    // jest.config.js
    module.exports = {
    // ... transform config ...
    transformIgnorePatterns: [
    '/node_modules/(?!react-native|problematic-esm-library)/',
    // Often, the regex is simplified to just exclude the specific packages from the ignore list
    // '/node_modules/(?!react-native|problematic-esm-library).+'
    ],
    // ... other Jest configurations
    };

    * /node_modules/(?!react-native|problematic-esm-library)/: 忽略所有 node_modules 目录下的文件,除了路径中包含 react-native/problematic-esm-library/ 的文件。

  3. 运行测试:

    “`bash
    npm test

    或者 yarn test

    “`

现在,Jest 应该会使用你的转译器处理 problematic-esm-library 中的 ES Modules 代码,使其可以在 Jest 环境中执行。

优点:
* 针对性强: 只处理需要转译的特定第三方库,不会影响其他正常工作的库。
* 保持默认行为: 大部分 node_modules 仍然被忽略转译,保持了较快的测试速度。

缺点:
* 需要识别问题库: 你需要通过错误信息找到是哪个库导致的问题。
* 配置可能复杂: 正则表达式的编写可能有些棘手,尤其是需要处理多个库或复杂的路径时。
* 依赖库的稳定性: 如果问题库更新版本并改变了其内部结构或发布方式,你可能需要再次调整配置。
* 仍然需要转译器: 这个方案本身不提供转译能力,它依赖于你已经配置好了 transform(方案一)。

总结: 调整 transformIgnorePatterns 是解决特定第三方库导致 ESM 问题的有效方法。它通常与方案一(使用转译器)结合使用。如果错误源确定是 node_modules 中的某个库,这通常是优先考虑的解决方案,因为它对 Jest 的性能影响最小。

6. 总结与选择最佳方案

“cannot use import statement outside a module” 错误在使用 Jest 测试现代 JavaScript 代码时是一个常见的问题,其根源在于 Node.js 在 CommonJS 环境下不理解 ES Modules 语法。Jest 默认在 Node.js 环境中运行,因此会受此影响。

我们讨论了三种主要的解决方案:

  1. 使用转译器 (Babel/swc): 这是最推荐和最健壮的方法。通过配置 Jest 的 transform,在运行测试前将你的代码和受影响的第三方库从 ES Modules 转译为 CommonJS。
    • 优点: 兼容性强,处理复杂语法,生态成熟(Babel)或速度快(swc)。
    • 缺点: 引入转译步骤,配置相对复杂。
  2. 使用 Node.js 原生 ES Modules 支持 (type: "module"): 通过设置 package.json,让整个项目以 ES Modules 模式运行。
    • 优点: 符合 Node.js 和语言未来趋势,无需额外转译器处理基本 ESM 语法。
    • 缺点: 需要较新 Node.js 版本,可能引入 CommonJS 兼容性问题,影响整个项目结构,需要调整文件扩展名和导入路径。
  3. 调整 transformIgnorePatterns 当错误仅由 node_modules 中的特定第三方库引起时,修改 Jest 配置,让转译器处理这些特定的库。
    • 优点: 针对性强,对性能影响小。
    • 缺点: 需要识别问题库,配置可能复杂,依赖于转译器的存在。

如何选择最佳方案?

  • 如果你已经在使用 Babel 或 swc 进行项目构建 (例如,处理 React/Vue 组件、TypeScript 等),或者你的项目是中大型项目: 方案一(使用转译器)通常是最佳选择。只需确保你的 Babel/swc 配置正确地将 ES Modules 转换为 CommonJS,并且 Jest 被配置为使用该转译器。
  • 如果你的项目是新项目,希望完全拥抱现代 Node.js 生态,且依赖的第三方库对原生 ESM 支持良好: 可以考虑方案二(type: "module")。但要做好处理 CommonJS 兼容性和文件路径的准备。
  • 如果你的项目大部分代码都是 CommonJS,但错误仅仅是由 node_modules 中一两个特定的 ES Modules 库引起的: 方案三(调整 transformIgnorePatterns),配合方案一(需要配置一个转译器),是最高效的解决方案。
  • 如果你正在使用 TypeScript: ts-jest 是另一个 Jest transform,它使用 TypeScript 编译器将 TS 和 ESM 转换为 CommonJS。这是一种集成了方案一和 TypeScript 支持的方法。安装 ts-jest 并配置 Jest 使用它,通常也能解决 ESM 问题。

    bash
    npm install --save-dev jest ts-jest typescript @types/jest

    javascript
    // jest.config.js
    module.exports = {
    preset: 'ts-jest', // Or use transform: { '^.+\\.ts?$': 'ts-jest' }
    testEnvironment: 'node',
    // ... other configs
    };

在实践中,许多复杂的项目会结合使用方案一和方案三:使用 Babel 或 swc 处理自己的代码和大多数现代语法,然后调整 transformIgnorePatterns 来特别处理少数在 node_modules 中发布了 ESM 且未被转译的库。

7. 排除故障 (Troubleshooting)

即使应用了上述解决方案,有时错误可能仍然存在。以下是一些常见的排查步骤:

  • 检查 Jest 配置路径: 确保 Jest 正在加载你修改过的 jest.config.jspackage.json 中的配置。可以通过运行 jest --showConfig 来查看 Jest 实际使用的配置。
  • 检查转译器配置:
    • 如果你使用 Babel (babel-jest),确保 .babelrc.jsbabel.config.js 文件位于 Jest 运行的目录下,并且配置(特别是 @babel/preset-envmodules 选项)正确地将 ESM 转换为 CommonJS。
    • 如果你使用 swc (swc-jest),确保 .swcrc 文件位于正确位置,并且 module.type 设置为 "commonjs"
  • 确认 transform 正则表达式: 确保 jest.config.jstransform 的正则表达式匹配到你的测试文件、应用程序代码以及(如果调整了 transformIgnorePatterns)需要转译的第三方库文件。
  • 确认 transformIgnorePatterns 正则表达式: 如果你修改了 transformIgnorePatterns,仔细检查你的正则表达式是否正确地排除了需要转译的库,同时忽略了其他所有 node_modules。可以在在线 regex 测试工具中测试你的表达式。
  • 清除 Jest 缓存: Jest 会缓存已转译的文件以提高性能。有时旧的缓存会导致问题。尝试运行 jest --clearCache 后再运行测试。
  • 检查 Node.js 版本: 如果你尝试使用原生 ESM (type: "module"),确保你的 Node.js 版本足够新。
  • 查看完整的错误堆栈: 错误信息通常会包含文件路径和行号。这有助于确定是哪个特定的文件导致了 import 语句错误,从而帮助你定位问题是出现在自己的代码、应用程序代码还是第三方库中。

通过系统地检查这些配置和环境因素,你应该能够找到导致 Cannot use import statement outside a module 错误并成功解决它。

结语

解决 Jest 中 Cannot use import statement outside a module 错误是现代化 JavaScript 开发中常见的一环。它不仅要求你了解 Jest 的配置,更深入地触及了 JavaScript 模块系统(CommonJS vs. ES Modules)以及 Node.js 对它们的处理方式。

掌握使用转译器(如 Babel 或 swc)是解决这类问题的最通用和强大的方法。同时,了解 Node.js 的原生 ESM 支持和 Jest 的 transformIgnorePatterns 配置,能帮助你更精确和高效地应对特定场景。

希望本文提供的详细解释和多种解决方案能帮助你理解并克服这个常见的 Jest 错误,让你的测试之旅更加顺畅。祝你编写的测试稳健可靠!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部