Node.js 模块系统选择-学习 CommonJS 和 ESM

**本文摘要:**本文记录了在Node.js项目中从CommonJS迁移到ES Modules时遇到的配置加载问题及解决方案。作者在将原有配置系统改造为ESM格式后,遇到了模块导出错误、语法兼容性问题以及WebSocket地址解析错误。通过分析发现,问题的根源在于Node版本过低(v14)不支持import assertions特性,以及CommonJS与ESM模块混用导致的兼容性问题。最终通过统一使用ESM语法、去除不兼容的import参数,成功解决了配置加载问题。文章还详细对比了CommonJS和ESM两种模块系统的使用方式、语法差异和适用场景,为开发者提供了模块系统选择的参考标准。案例表明,在新项目中推荐使用ESM标准,而在旧项目迁移时需特别注意版本兼容性和语法统一性。

目录

一、问题背景

二、遇到的问题

三、分析过程

[1. 确认配置加载机制](#1. 确认配置加载机制)

[2. 确认当前 Node.js 模块类型](#2. 确认当前 Node.js 模块类型)

[3. 初步修正:统一为 ESM 写法](#3. 初步修正:统一为 ESM 写法)

[4. 运行时报语法错误 "Unexpected token ','"](#4. 运行时报语法错误 “Unexpected token ','”)

[5. 最终运行成功](#5. 最终运行成功)

[CommonJS 和 ESM](#CommonJS 和 ESM)

[1. 什么是模块系统?](#1. 什么是模块系统?)

[2. CommonJS 模块系统](#2. CommonJS 模块系统)

[2.1 基本概念](#2.1 基本概念)

[2.2 如何使用 CommonJS](#2.2 如何使用 CommonJS)

导出模块(把东西放进盒子)

导入模块(从盒子里拿东西)

[2.3 CommonJS 特点](#2.3 CommonJS 特点)

[3. ESM (ECMAScript Modules) 模块系统](#3. ESM (ECMAScript Modules) 模块系统)

[3.1 基本概念](#3.1 基本概念)

[3.2 如何使用 ESM](#3.2 如何使用 ESM)

[第一步:告诉 Node.js 使用 ESM](#第一步:告诉 Node.js 使用 ESM)

导出模块

导入模块

[4. 两种系统的详细对比](#4. 两种系统的详细对比)

[4.1 语法对比表](#4.1 语法对比表)

[4.2 实际例子对比](#4.2 实际例子对比)

[5. 常见问题与解决方案](#5. 常见问题与解决方案)

[5.1 如何选择使用哪种?](#5.1 如何选择使用哪种?)

[5.2 文件扩展名说明](#5.2 文件扩展名说明)

[5.3 特殊变量处理](#5.3 特殊变量处理)

[6. 练习项目](#6. 练习项目)

项目:简单的计算器

[7. 总结](#7. 总结)


一、问题背景

在我最近维护的 某平台 中,

Node环境确定、不能混用ESM和CommonJS、修改代码要注意环境版本

项目中原始的配置管理逻辑(main.js + 多个 config_xxx.js)基于 CommonJS(require/module.exports)。

为了适配新版运行环境(Node.js 14+)和平台的自动配置覆盖机制,我将:

  • 原来 main.js 中的 if/else 配置判断逻辑抽取到独立文件;

  • 引入了一个新的模块:config_loader.js

  • 同时,平台层支持通过指定的 config_test.jsconfig_pre.js 等文件覆盖 config.js 的内容。

看似简单的结构优化,却引发了一连串配置加载与运行错误。


二、遇到的问题

在改造完成后运行:

bash 复制代码
node main.js

我收到了如下长长的错误堆栈👇

复制代码
SyntaxError: The requested module './config_loader.js' does not provide an export named 'getPlatformPathConfig' 

后续又出现了:在修改代码的时候要注意自己的环境,比如说我这里,就需要注意Node的版本

复制代码
SyntaxError: Unexpected token ',' 

以及在浏览器端出现的实际问题:

复制代码
WebSocket connection to '某网址' failed 

而我明明在配置文件中写的是:

复制代码
signal_ws: 'test.xxx.com/ws' 

却被错误地解析成了 /test-ws/


三、分析过程

1. 确认配置加载机制

平台机制明确指出:

永远加载 config.js,但允许平台层动态覆盖指定环境文件(如 config_test.js)。

因此问题并非出在加载顺序,而是:

  • 我抽取出来的 config_loader.js 没有正确识别覆盖后的文件结构

  • 或者 Node.js 运行时的模块系统混用了 CommonJS 与 ES Module


2. 确认当前 Node.js 模块类型

我检查 package.json

复制代码
{ "type": "module" } 

说明该项目处于 ES Module 模式

但旧代码还在使用 CommonJS:

复制代码
const { getPlatformPathConfig } = require('./config_loader'); module.exports = { platform_config: {...} } 

→ 这两种模块机制是 不兼容的 ,导致 SyntaxError: export not found


3. 初步修正:统一为 ESM 写法

我将所有配置文件改为 ESM:

复制代码
- module.exports = { xxx_config: {...} } + export default { xxx_config: {...} } 

并调整加载器:

复制代码
- const path = require('path'); - module.exports = { getxxxPathConfig } + import path from 'path'; + export function getxxxPathConfig() { ... } 

以及 main.js 顶部:

复制代码
- const { getxxxPathConfig } = require('./config_loader'); + import { getxxxPathConfig } from './config_loader.js'; 

4. 运行时报语法错误 "Unexpected token ','"

出现于:

复制代码
const { default: configModule } = await import(configPath, { assert: { type: 'javascript' } }) 

这说明 Node.js 版本过低,不支持 import assertions(v17+ 才引入)。

而运行环境是 Node v14.19.3

解决方法:去掉 { assert: ... } 参数,改为:

复制代码
const configModule = await import(configPath); 

5. 最终运行成功

✅ 配置覆盖机制成功;

✅ WebSocket 地址正确(从 test-ws 修复为 ws);

✅ RabbitMQ、Media、Signal 等模块正常。

CommonJS 和 ESM

1. 什么是模块系统?

简单理解:就像整理房间一样,模块系统帮助我们把代码分成不同的"盒子",每个盒子有特定功能,需要时拿出来用。

为什么需要模块?

  • 避免所有代码写在一个文件里

  • 提高代码可维护性

  • 方便代码复用

  • 团队协作更高效

2. CommonJS 模块系统

2.1 基本概念

CommonJS 是 Node.js 最早使用的模块系统,设计用于服务器端。

2.2 如何使用 CommonJS

导出模块(把东西放进盒子)

math.js

javascript 复制代码
// math.js 文件 - 创建一个数学工具模块

// 方法1:导出单个值
module.exports = function add(a, b) {
  return a + b;
};

// 方法2:导出多个值
module.exports = {
  add: function(a, b) { return a + b; },
  subtract: function(a, b) { return a - b; },
  PI: 3.14159
};

// 方法3:使用 exports(与上面等效)
exports.add = function(a, b) { return a + b; };
exports.subtract = function(a, b) { return a - b; };
导入模块(从盒子里拿东西)

app.js

javascript 复制代码
// app.js 文件 - 使用数学工具模块

// 导入整个模块
const math = require('./math.js');
console.log(math.add(2, 3)); // 5
console.log(math.PI); // 3.14159

// 只导入需要的部分(解构赋值)
const { add, PI } = require('./math.js');
console.log(add(5, 3)); // 8

2.3 CommonJS 特点

  • 同步加载:像排队买票,一个接一个

  • 运行时加载:代码执行时才加载模块

  • 值拷贝:导出的是值的副本

3. ESM (ECMAScript Modules) 模块系统

3.1 基本概念

ESM 是 JavaScript 的官方标准模块系统,现代浏览器和 Node.js 都支持。

3.2 如何使用 ESM

第一步:告诉 Node.js 使用 ESM

package.json 文件中添加:

javascript 复制代码
{
  "type": "module"
}
导出模块
javascript 复制代码
// math.js 文件

// 命名导出 - 可以导出多个
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

export const PI = 3.14159;

// 默认导出 - 只能有一个
export default function multiply(a, b) {
  return a * b;
}
导入模块
javascript 复制代码
// app.js 文件

// 导入命名导出
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5

// 导入默认导出
import multiply from './math.js';
console.log(multiply(2, 3)); // 6

// 导入所有内容
import * as math from './math.js';
console.log(math.add(1, 2)); // 3

4. 两种系统的详细对比

4.1 语法对比表

功能 CommonJS ESM
导出 module.exports = {} export {}
导入 const module = require() import module from ''
文件扩展名 .js.cjs .js.mjs

4.2 实际例子对比

CommonJS 例子:

javascript 复制代码
// user.js - 导出用户相关功能
const users = ['小明', '小红', '小刚'];

function getUser(id) {
  return users[id] || '用户不存在';
}

function addUser(name) {
  users.push(name);
  return `用户 ${name} 添加成功`;
}

module.exports = {
  getUser,
  addUser,
  userCount: users.length
};
javascript 复制代码
// app.js - 使用用户功能
const userModule = require('./user.js');

console.log(userModule.getUser(0)); // 小明
console.log(userModule.addUser('小李')); // 用户 小李 添加成功

ESM 例子:

javascript 复制代码
// user.js - 导出用户相关功能
const users = ['小明', '小红', '小刚'];

export function getUser(id) {
  return users[id] || '用户不存在';
}

export function addUser(name) {
  users.push(name);
  return `用户 ${name} 添加成功`;
}

export const userCount = users.length;
javascript 复制代码
// app.js - 使用用户功能
import { getUser, addUser, userCount } from './user.js';

console.log(getUser(0)); // 小明
console.log(addUser('小李')); // 用户 小李 添加成功

5. 常见问题与解决方案

5.1 如何选择使用哪种?

  • 新项目:推荐使用 ESM(现代标准)

  • 旧项目:继续用 CommonJS 或逐步迁移

  • 不确定时:先用 CommonJS,更容易理解

5.2 文件扩展名说明

// .js 文件 - 根据 package.json 决定

// 如果 package.json 有 "type": "module" → 按 ESM 处理

// 如果没有 → 按 CommonJS 处理

// .cjs 文件 - 强制按 CommonJS 处理

// .mjs 文件 - 强制按 ESM 处理

5.3 特殊变量处理

javascript 复制代码
// CommonJS 中有这些变量
console.log(__dirname); // 当前文件所在目录
console.log(__filename); // 当前文件名

// ESM 中没有,需要这样获取
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

6. 练习项目

项目:简单的计算器

分别用 CommonJS 和 ESM 实现:

CommonJS 版本:

javascript 复制代码
// calculator.js
module.exports = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => b !== 0 ? a / b : '不能除以0'
};
javascript 复制代码
// app.js
const calc = require('./calculator.js');
console.log('5 + 3 =', calc.add(5, 3));
console.log('10 / 2 =', calc.divide(10, 2));

ESM 版本:

javascript 复制代码
// calculator.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { 
  return b !== 0 ? a / b : '不能除以0';
}
javascript 复制代码
// app.js
import { add, divide } from './calculator.js';
console.log('5 + 3 =', add(5, 3));
console.log('10 / 2 =', divide(10, 2));

7. 总结

特性 CommonJS ESM
学习难度 简单 中等
现代性 传统 现代标准
使用场景 Node.js 传统项目 新项目、全栈开发
推荐度 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

结语:

随着这篇博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。

你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容。

相关推荐
陈陈小白2 小时前
npm run dev报错Error: listen EADDRINUSE: address already in use :::8090
前端·npm·node.js·vue
杂鱼豆腐人2 小时前
pnpm环境下防止误使用npm的方法
前端·git·npm·node.js·git bash
Q_Q5110082854 小时前
python+uniapp基于微信小程序的垃圾分类信息系统
spring boot·python·微信小程序·django·flask·uni-app·node.js
郏国上8 小时前
查询数据库上所有表用到图片和视频的数据,并记录到excel表
mongodb·node.js·excel
Q_Q5110082859 小时前
python+django/flask+vue的书城图书阅读器系统,亮点含目录章节pycharm
spring boot·python·django·flask·node.js·php
Q_Q51100828512 小时前
python+django/flask的图书馆管理系统vue
spring boot·python·django·flask·node.js·php
Q_Q51100828513 小时前
python+django/flask的美食交流宣传系统vue
spring boot·python·pycharm·django·flask·node.js·php
Q_Q51100828513 小时前
python+django/flask+vue的基层智能化人员调度系统pycharm-计算机毕业设计
spring boot·python·pycharm·django·flask·node.js