使用 yarn 的时候,遇到 Error [ERR_REQUIRE_ESM]: require() of ES Module 怎么解决?
'# 使用 yarn 的时候,遇到 Error [ERR_REQUIRE_ESM]: require() of ES Module 怎么解决?
一、背景与问题
在使用 Yarn 进行项目依赖管理时,开发者可能会遇到如下错误:
Error [ERR_REQUIRE_ESM]: require() of ES Module 'xxx' from 'yyy' is deprecated.这个错误通常出现在以下场景中:
- 项目中同时使用了 ES 模块(ESM)和 CommonJS 模块
- 依赖的第三方库使用了 ESM 但项目配置为 CommonJS
- Node.js 版本升级后(v12+)对 ESM 的严格校验
- 使用了动态 require() 调用 ESM 模块
这个错误的本质是 Node.js 对 ESM 和 CommonJS 模块系统的严格区分。从 Node.js v12 开始,require() 只能加载 CommonJS 模块,而 ESM 模块必须使用 import 或 require() 时必须配合 type: module 配置。
二、基本原理
1. 模块系统差异
Node.js 从 v12 开始区分了两种模块系统:
- CommonJS(CJS):传统 Node.js 模块系统,使用
require()和module.exports - ES 模块(ESM):基于 ES6 的模块系统,使用
import/export,需要通过type: module配置
2. ESM 的工作原理
ESM 的核心机制包括:
- 文件扩展名强制要求
.mjs或package.json中type: module - 模块加载使用
import/export语法 - 允许使用动态导入
import()和require()的 ES 模块
3. 错误触发条件
当出现以下情况时会触发 ERR_REQUIRE_ESM 错误:
// 错误示例:尝试用 require() 加载 ESM 模块
const fs = require('fs');// package.json 配置错误
{
"type": "commonjs" // 未正确设置为 module
}三、环境准备
确保开发环境符合以下条件:
- Node.js v12+(推荐 v16+)
- Yarn v1.22+(支持 ESM 配置)
- 项目结构示例:
my-project/
├── package.json
├── src/
│ ├── index.js
│ └── utils.js
└── node_modules/四、核心实现
1. 修改 package.json 配置
这是最直接的解决方案:
{
"type": "module"
}关键代码解释:
type: module告诉 Node.js 该项目使用 ESM 系统- 所有
.js文件都会被当作 ESM 处理 - 需要将 CommonJS 代码转换为 ESM 语法
// 原始 CommonJS 代码
const fs = require('fs');
// 转换为 ESM 代码
import fs from 'fs';2. 使用 TypeScript 配置
对于 TS 项目,需要配置 tsconfig.json:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"target": "ES2017"
}
}关键代码解释:
esModuleInterop: true允许 CommonJS 模块与 ESM 兼容module: ESNext使用最新 ESM 特性- 可以直接使用
import导入 CommonJS 模块
3. 使用动态 require() 的特殊处理
对于必须使用 require() 的场景,可以使用 import.meta.url 动态加载:
// 动态加载 ESM 模块
import fs from 'fs/promises';
async function loadModule(path) {
const modulePath = new URL(path, import.meta.url);
const module = await import(modulePath.href);
return module;
}关键代码解释:
- 使用
URL构造函数创建模块路径 import()支持动态加载 ESM 模块- 需要确保模块路径是绝对路径
五、完整案例
1. 项目结构示例
my-project/
├── package.json
├── src/
│ ├── index.js
│ └── utils.js
└── node_modules/2. package.json 配置
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/index.js"
},
"dependencies": {
"lodash": "^4.17.21"
}
}3. src/index.js
// 使用 ESM 语法
import { debounce } from 'lodash';
// 定义函数
const myFunction = debounce(() => {
console.log('Debounced function called');
}, 1000);
// 调用函数
myFunction();4. src/utils.js
// ESM 模块
export function formatDate(date) {
return date.toLocaleString();
}运行流程:
- 安装依赖:
yarn install - 运行项目:
yarn start - 输出结果:
Debounced function called(约1秒后)
六、源码解析
1. Node.js 模块加载机制
// 模块加载核心代码(简化版)
function loadModule(modulePath) {
if (isESM(modulePath)) {
return import(modulePath);
} else {
return require(modulePath);
}
}关键点:
isESM()判断模块是否为 ESM- 使用
import()加载 ESM 模块 - 使用
require()加载 CJS 模块
2. ESM 的文件处理
// ESM 文件处理逻辑(简化版)
function handleESMFile(filePath) {
// 检查文件扩展名
if (filePath.endsWith('.mjs') ||
(filePath.endsWith('.js') && isESMEnabled())) {
return loadESM(filePath);
}
return loadCJS(filePath);
}关键点:
.mjs文件自动识别为 ESM.js文件需要type: module配置- 未配置时默认使用 CJS
七、进阶使用
1. 混合使用 ESM 和 CJS 的场景
// ESM 文件中使用 CJS 模块
import fs from 'fs/promises';
import { createReadStream } from 'fs';
async function readFileSync(filePath) {
const buffer = await fs.readFile(filePath);
return buffer.toString();
}2. 使用 TypeScript 的高级特性
// TypeScript 中的 ESM 支持
import { createReadStream } from 'fs';
type FileContent = string;
async function readFile(filePath: string): Promise<FileContent> {
const stream = createReadStream(filePath);
return new Promise((resolve, reject) => {
let data = '';
stream.on('data', (chunk) => data += chunk);
stream.on('end', () => resolve(data));
stream.on('error', (err) => reject(err));
});
}3. 使用构建工具进行转换
// package.json 构建配置
{
"scripts": {
"build": "tsc --module ESNext --outDir dist"
}
}关键点:
- 使用 TypeScript 编译器进行转换
- 输出目录为 ESM 兼容格式
- 可以保持源码为 CJS 但输出为 ESM
八、性能与工程实践
1. 性能优化
- 使用
import()动态加载减少初始加载时间 - 使用
require()加载核心模块提高性能 - 对于高频调用的模块,使用缓存机制
// 缓存机制示例
const moduleCache = new Map();
function loadModule(path) {
if (moduleCache.has(path)) {
return moduleCache.get(path);
}
const module = require(path);
moduleCache.set(path, module);
return module;
}2. 异常处理
try {
const module = import('some-module');
console.log(module);
} catch (err) {
console.error('Failed to load module:', err.message);
}3. 安全风险
- 动态 require() 可能导致路径遍历漏洞
- ESM 的动态导入可能引发安全问题
- 需要严格校验模块路径
九、常见问题与踩坑
1. 常见错误
| 错误场景 | 解决方案 |
|---|---|
忘记添加 type: module | 在 package.json 中添加配置 |
| 依赖包未支持 ESM | 升级依赖包版本或寻找替代方案 |
使用 require() 加载 ESM | 改用 import() 或调整配置 |
2. 典型错误示例
// 错误示例:混合使用 require 和 import
import fs from 'fs';
const path = require('path');错误原因: 未统一模块系统
3. 常见问题分析
- 性能问题: ESM 的模块加载机制可能导致启动时间增加
- 兼容性问题: 旧版本 Node.js 不支持 ESM
- 配置问题: 未正确配置
type字段导致模块识别错误
十、最佳实践
1. 推荐方案
- 对新项目统一使用 ESM 系统
- 对旧项目逐步迁移为 ESM
- 对必须使用 CJS 的场景使用
esModuleInterop: true - 使用 TypeScript 提供类型安全和模块兼容性
2. 推荐配置
{
"type": "module",
"scripts": {
"start": "node src/index.js"
},
"eslintConfig": {
"rules": {
"no-require": "warn"
}
}
}3. 推荐实践
- 使用
import.meta.url处理动态模块路径 - 对关键模块使用
import()动态加载 - 使用
require()加载非 ESM 模块
十一、总结
Error [ERR_REQUIRE_ESM]: require() of ES Module 是 Node.js 在 v12+ 版本中对 ESM 系统的严格校验导致的错误。解决这个问题需要理解 Node.js 的模块系统差异,并根据项目需求选择合适的解决方案。
通过合理配置 package.json 的 type 字段、使用 TypeScript 配置、动态加载 ESM 模块等方法,可以有效解决这个错误。同时需要根据项目实际情况选择合适的技术方案,平衡性能、兼容性和安全性。
在实际开发中,建议遵循以下原则:
- 新项目优先使用 ESM 系统
- 旧项目逐步迁移为 ESM
- 对必须使用 CJS 的场景使用
esModuleInterop - 使用构建工具进行代码转换
- 始终保持对模块系统的兼容性考虑
通过深入理解模块系统差异和合理配置,可以有效避免 ERR_REQUIRE_ESM 错误,提升项目的稳定性和可维护性。
评论已关闭