Node.js模块系统:CommonJS与ESM怎么选

Node.js支持CommonJS和ECMAScript Modules两套模块体系。`require`与`import`不能只按个人偏好混用,文件扩展名、`package.json`中的`type`字段、第三方包导出方式和Node.js版本会共同决定解析结果。

模块格式判断用扩展名和package.json统一require或import。

CommonJS

使用require和module.exports。

ESM

使用import和export。

项目标记

扩展名和type字段决定解析方式。

先看两套模块的基本写法

下面提供双示例;后续的格式对比表与判断流程用来决定自己的项目应采用哪一套。

如果你在运行脚本时遇到require或import相关错误,目标是让项目使用一致且可运行的模块格式。准备Node.js项目、package.json和错误原文后,依次查看扩展名、检查package.json type、确认依赖导出格式、选择CommonJS或ESM并运行最小示例。

CommonJS使用require()加载模块,通过module.exportsexports导出;ESM使用静态importexport语法。两者都能拆分代码,但加载语义、文件标记和部分运行时能力不同。

// math.cjs
exports.add = (a, b) => a + b;

// app.cjs
const { add } = require('./math.cjs');
console.log(add(2, 3));
// math.mjs
export const add = (a, b) => a + b;

// app.mjs
import { add } from './math.mjs';
console.log(add(2, 3));

两个示例分别使用明确扩展名,适合用来验证环境。不要把require和顶层import直接放进同一个文件后期待Node.js自动猜测意图。

Node.js如何判断.js文件的格式

.mjs始终按ESM解释,.cjs始终按CommonJS解释。普通.js文件通常受最近一层package.json"type"字段影响:"type": "module".js按ESM处理;"type": "commonjs"或未声明时通常按CommonJS处理。

判断线索 CommonJS ESM
明确扩展名.cjs.mjs
package.jsontype为commonjs或无module声明type为module
导入语法require()import
导出语法module.exports / exportsexport
常见文件位置能力__filename、__dirname通过import.meta相关能力获取

项目应在根目录明确约定,并让测试、构建工具和发布文件遵守同一规则。子目录出现另一个package.json时,解析边界可能变化,排错时要查找离目标文件最近的配置。

新项目应该选择哪一种

新项目可以优先评估ESM,因为它与JavaScript标准模块语法一致,现代工具和包生态支持逐步完善。但选择前仍要确认框架、测试工具和关键依赖的支持方式。维护大量CommonJS代码的旧项目,没有必要为了形式统一一次性重写全部模块。

如果项目是发布给他人使用的库,还需设计exports字段和兼容策略;如果只是内部应用,重点是团队统一、工具可用和部署可复现。任何选择都应通过最小示例、测试和实际启动命令验证,而不是只修改type字段。

常见错误如何判断

同文件混用、扩展名覆盖type、第三方包导出差异和旧版本支持是常见失败点。完成调整后,成功标准是脚本无模块解析错误且导入值正确。

require is not defined in ES module scope表示当前文件被当作ESM,但代码仍使用CommonJS的require。可以改用import,或在确实需要CommonJS时改用.cjs并调整项目约定。

Cannot use import statement outside a module通常表示文件被按CommonJS解释,却写了import。检查扩展名和最近的package.json,不要只在命令后附加随机参数。ERR_REQUIRE_ESM常见于CommonJS尝试require一个只提供ESM的包,应查看该包文档与导出配置。

模块找不到则先区分“格式错误”和“路径或依赖缺失”。相对文件导入要检查路径与扩展名;第三方包要确认已在当前项目本地安装;终端目录错误会让你检查错package.jsonnode_modules

从CommonJS迁移到ESM的稳妥步骤

  1. 1.建立测试并记录当前启动命令,确保迁移前有基线。
  2. 2.盘点requiremodule.exports__dirname和动态加载位置。
  3. 3.检查关键依赖是否支持ESM以及导出名称。
  4. 4.先迁移一个边界清晰的小模块,选择.mjs或明确type
  5. 5.修改导入导出与文件路径,运行测试和真实启动流程。
  6. 6.逐步扩大范围,不要在未验证时同时升级Node.js和全部依赖。

本站软件记录为Node.js 16.17.0.0且该版本已EOL。模块功能的具体支持细节会随版本变化,维护旧项目时应查看对应版本文档;开始新项目则使用受支持版本并按当前文档验证。版本选择方法见LTS指南

与npm和项目配置配合

发布包还应检查exports字段是否只暴露预期入口,并分别验证CommonJS和ESM消费者的使用方式。应用项目则不必为了兼容所有外部场景制造复杂配置,保持单一模块约定和明确启动脚本通常更容易维护。

迁移期间可建立一个最小集成测试,分别加载关键依赖、读取配置并启动服务。只有模块解析通过但业务入口失败时,应继续检查导出名称、默认导出和异步初始化,而不是反复切换扩展名。

对于测试工具、构建工具和代码检查工具,也要确认它们读取同一套模块设置。主程序能运行而测试失败,常见原因是工具自身配置仍按另一种格式加载。把运行命令写入npm scripts,可减少不同成员手工参数不一致。

本页回答根据项目标记选择require或import,并修复常见模块格式错误。下一步进入/npm/index.html检查依赖与scripts是否遵守同一模块约定。

模块格式属于项目约定,package.json同时还记录依赖和scripts。先通过npm项目包管理建立清晰项目根目录,再设置模块格式。运行脚本前用npm ls确认依赖,用node --version记录运行时。

若仍未完成基础脚本,返回Node.js入门;出现命令路径问题进入问题排查;需要获取运行时则进入Node.js下载页

常见问题

CommonJS和ESM哪个性能更好?

不能仅凭格式下结论。选择应优先考虑生态兼容、工具链、加载行为和维护成本,并通过真实项目测量。

package.json没有type字段时.js是什么格式?

常见情况下按CommonJS处理,但还要考虑Node.js版本、启动参数和上层配置。用明确扩展名可减少歧义。

可以在CommonJS中加载ESM吗?

存在动态导入等互操作方式,但同步语义和导出形式有差异。应根据目标Node版本和依赖文档设计,不要假设完全等价。

为什么同样的app.js在另一个目录会报错?

不同目录可能受不同package.jsontype字段影响,也可能缺少对应node_modules。先确认项目根目录和最近配置。

改成type module后__dirname为什么没了?

__dirname是CommonJS提供的能力,ESM需要使用import.meta相关方式构造文件路径。迁移时要逐项替换这类运行时差异。

继续浏览

下一步怎么走

本页只解决一个主要问题,后续操作可按需求进入对应页面。

获取Node.js Windows安装器

还没有可用Node.js运行时,先完成安装与基础命令检查,再验证模块示例。

前往Node.js下载页