使用 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
}

三、环境准备

确保开发环境符合以下条件:

  1. Node.js v12+(推荐 v16+)
  2. Yarn v1.22+(支持 ESM 配置)
  3. 项目结构示例:
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();
}

运行流程:

  1. 安装依赖:yarn install
  2. 运行项目:yarn start
  3. 输出结果: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. 推荐方案

  1. 对新项目统一使用 ESM 系统
  2. 对旧项目逐步迁移为 ESM
  3. 对必须使用 CJS 的场景使用 esModuleInterop: true
  4. 使用 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 错误,提升项目的稳定性和可维护性。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日