前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库require ESM Node.js 22 synchronous ESM graph
NoNode.js模块与工具链

CommonJS 从哪个 Node.js 版本可以 require ESM,为什么仍可能失败?

能不能 require 一个 ESM,既取决于 Node 的具体版本和启动选项,也取决于整个依赖图是否同步。

前端进阶之旅 · 一题精讲更新于 2026.09.06
Node.js#模块与工具链#异步编程
先看核心答案读代码示例
理解线索

按三个条件检查兼容性

  1. 运行能力确认具体 Node 版本和是否关闭了相关特性
  2. 模块格式确保目标确实按照 ESM 解析和加载
  3. 求值条件检查直接与间接依赖是否存在顶层等待

版本节点来自对应版本官方文档;下面示例用于验证当前运行环境,不代表已经在所有历史版本上执行过测试。

核心回答

先记住这个答案

require ESM 在 Node 22.0.0 引入,并回移到 20.17.0,但引入不等于当时默认启用:22.12.0 和 20.19.0 取消了所需实验开关,22.13.0 与 20.19.0 默认不再发出该实验警告。它仍要求被加载模块及其依赖图没有顶层 await,通常返回模块命名空间,默认导出放在 .default。遇到异步模块图应使用 import() 并等待结果,而不是把 await 藏到间接依赖里。发布包时应声明并测试具体运行范围,不能只写“Node 22 都支持”。

  • 引入、默认启用和默认警告是不同版本节点
  • 同步要求覆盖间接导入的整个模块图
  • 普通默认导出通常从返回值的 .default 访问

先核对版本细节而不是只看主版本

本地开发、CI 和生产容器可能使用同一主版本下不同补丁,因此互操作在一处正常并不说明另一处也可用。记录 node --version 与启动参数,才能判断是能力未启用、解析失败还是异步求值限制。

在支持该检测项的版本可以查看 process.features.require_module,但能力检测不能代替包的最低版本声明。还需要确认启动参数没有关闭特性,并让兼容性测试运行实际发布产物,避免只验证转译前源码。

用独立文件观察返回命名空间

下面的 ESM 同时导出默认函数和命名常量,CommonJS 通过 .default 调用默认函数。它没有顶层异步初始化,所以在支持同步 require ESM 的环境里可以直接完成加载。

示例采用明确扩展名以排除 package.json 的影响。若真实依赖用了特殊 module.exports 互操作导出,返回形状可能由作者定制;消费方应查看包的公开契约,不能盲目追加 .default。

format.mjs 与 main.cjsText
// file: format.mjs
export const separator = ' / ';
export default function format(parts) {
  return parts.join(separator);
}

// file: main.cjs
const formatModule = require('./format.mjs');
console.log(formatModule.default(['Node', 'ESM']));
console.log(formatModule.separator === ' / ');

在具备相应能力的 Node 中执行 node main.cjs,输出 Node / ESM 与 true。这里验证命名空间访问方式,函数名和扩展名并不会消除间接依赖里的顶层 await。

异步依赖需要改变调用链的完成契约

假设 format.mjs 静态导入配置模块,而配置在顶层 await 远端结果,即使 format 自己没有 await,同步 require 仍可能因异步模块图抛出 ERR_REQUIRE_ASYNC_MODULE。只搜索入口文件会漏掉这个原因。

改用 import() 后,调用方得到的是 Promise,需要把等待传播到初始化入口,并处理失败与超时等业务条件。若公共接口必须同步,可以预先完成初始化或调整模块边界,而不能把 Promise 伪装成已准备好的导出对象。

回答前,多想一步

容易答错的地方

把默认启用误写成首次支持
这会错误排除带实验开关的旧版本,也可能让早期同主版本用户以为无需配置。应分别说明引入、开关和警告节点,并明确回答针对的是哪一条版本维护线。
捕获加载异常后直接返回空对象
错误被吞掉后,业务会在更远处报方法不存在,定位反而困难。应区分模块找不到、语法不兼容与异步求值限制,保留错误原因并在启动阶段决定如何失败或选择受支持入口。
试着用自己的话回答

面试官还会怎么问?

把顶层 await 包进 async 函数就能解决吗?

只有模块求值本身确实不再等待时,模块图才可能同步,但业务初始化并不会因此自动完成。若函数返回的 Promise 才代表配置可用,消费者仍需要等待它,不能以加载成功代替准备完成。

动态 import 可以在 CommonJS 里直接用吗?

可以,CommonJS 支持 import() 表达式,它返回 Promise,不要求先把整个文件改成 ESM。需要在异步函数或 Promise 链中处理结果,并注意这与同步 require 返回值的完成时机不同。

一个包应该同时提供两份实现吗?

取决于支持的运行环境和消费方式,可以选择兼容构建或明确提高最低版本。双入口也会增加状态实例、导出一致性和测试成本,应验证实际需求,不要只为绕过一个版本错误就复制整套代码。

从一道题,走向一组知识

把知识连起来

模块与工具链

Node.js 怎样判断 .js 文件是 CommonJS 还是 ESM?

先定位扩展名与 package.json 如何决定目标模块格式。

模块与工具链

ESM 导入 CommonJS 时,为什么默认导入可用而命名导入报错?

比较反方向互操作时默认导出与命名导出的差异。

参考资料

  • Node.js 22:require ESM 版本与限制
  • Node.js 20.19:回移记录与异步模块图

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 先核对版本细节而不是只看主版本
  3. 用独立文件观察返回命名空间
  4. 异步依赖需要改变调用链的完成契约
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑