**本文摘要:**本文记录了在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.js、config_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 传统项目 | 新项目、全栈开发 |
| 推荐度 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
结语:
随着这篇博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容。