第一次接触Node.js时,我被require()和module.exports的魔法般的数据传递所震撼。这种将代码分割成独立单元的能力,就像乐高积木一样让复杂系统变得可组合。十年前刚入行时,前端开发还处于全局变量满天飞的"刀耕火种"时代,直到CommonJS和ES Modules的出现才真正实现了工程化开发。
模块化的核心是关注点分离。想象你正在组装一台电脑:电源模块只负责供电,主板模块处理信号传输,CPU专注运算。当某个部件需要升级时,你只需要更换对应模块,而不必重构整个机箱。代码模块化也是同样的道理——每个文件都是一个独立的功能单元,通过明确定义的接口与其他模块通信。
Node.js的模块系统基于CommonJS规范,其核心原理可以拆解为:
javascript复制(function(exports, require, module, __filename, __dirname) {
// 你的模块代码
});
这种包装方式保证了模块内部的变量不会污染全局作用域。
加载流程:
循环引用处理:
javascript复制// a.js
console.log('a starting');
exports.done = false;
const b = require('./b');
console.log('in a, b.done =', b.done);
exports.done = true;
// b.js
console.log('b starting');
exports.done = false;
const a = require('./a');
console.log('in b, a.done =', a.done);
exports.done = true;
Node.js通过未完成副本机制解决循环依赖:当a.js加载b.js时,b.js会获取a.js的未完成导出对象,这种部分加载的状态保证了模块系统不会陷入死锁。
Node.js内置模块如fs、http等通过以下方式优化:
通过process.binding()方法可以将C++模块绑定到JavaScript层,这也是fs等模块能调用系统API的关键。
实际项目中常用到这些技巧:
javascript复制// 目录模块加载策略
require('./utils') // 依次尝试加载:
// utils.js → utils.json → utils.node
// utils/index.js → utils/index.json → utils/index.node
// 自定义扩展名处理
require.extensions['.vue'] = function(module, filename) {
const content = compileVueFile(fs.readFileSync(filename));
module._compile(content, filename);
};
当require('express')时,Node.js会:
可以通过module.paths查看当前模块的查找路径列表。
| 特性 | CommonJS | ES Modules |
|---|---|---|
| 加载方式 | 动态加载(运行时) | 静态解析(编译时) |
| 导出类型 | 值拷贝 | 实时绑定 |
| 顶层作用域 | 函数作用域 | 模块作用域 |
| 循环引用处理 | 部分加载 | 引用追踪 |
| 动态导入 | require() | import() |
| 文件扩展名 | .js/.json/.node | .mjs/.js(with type) |
典型互操作场景:
javascript复制// 在ESM中引入CJS
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjsModule = require('./legacy.cjs');
// 在CJS中动态加载ESM
(async () => {
const esm = await import('./modern.mjs');
})();
package.json中可配置多环境入口:
json复制{
"exports": {
".": {
"require": "./lib/node.cjs",
"import": "./lib/node.mjs",
"default": "./lib/node.js"
},
"./feature": {
"browser": "./feature-browser.js",
"default": "./feature-node.js"
}
}
}
开发工具链常用方案:
javascript复制// webpack-dev-server示例
if (module.hot) {
module.hot.accept('./module', () => {
const newModule = require('./module');
// 执行更新逻辑
});
}
微前端场景下的模块共享:
javascript复制// app1/webpack.config.js
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button'
}
});
// app2/webpack.config.js
new ModuleFederationPlugin({
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js'
}
});
javascript复制// 查看模块缓存
console.log(require.cache);
// 危险操作:删除缓存可能导致内存泄漏
delete require.cache[require.resolve('module')];
javascript复制// --require预加载模块
node --require dotenv/config server.js
// 利用worker_threads并行加载
const { Worker } = require('worker_threads');
new Worker(`
const heavyModule = require('heavy-module');
parentPort.postMessage(heavyModule.init());
`, { eval: true });
bash复制# 生成依赖图
npx madge --image graph.svg app.js
# 分析包大小
npx source-map-explorer bundle.js
javascript复制// 安全的对象导出
module.exports = Object.freeze({
safeMethod() {},
data: deepClone(unsafeData)
});
// 防止篡改
const secure = require('secure-module');
Object.defineProperty(secure, 'dangerous', {
writable: false
});
bash复制# 审计依赖
npm audit
# 锁定文件校验
npm ci --audit
javascript复制const vm = require('vm');
const context = vm.createContext({ console });
vm.runInContext('console.log("Safe execution")', context);
案例1:模块未找到
bash复制Error: Cannot find module 'lodash'
解决方案链:
案例2:ESM-CJS互操作问题
bash复制Warning: To load an ES module...
处理步骤:
bash复制node --loader ./custom-loader.js app.js
javascript复制const originalRequire = require;
require = function(path) {
console.time('require:' + path);
const result = originalRequire(path);
console.timeEnd('require:' + path);
return result;
};
javascript复制const { heapSnapshot } = require('v8');
setInterval(() => {
const snapshot = heapSnapshot();
fs.writeFileSync(`heap-${Date.now()}.heapsnapshot`, snapshot);
}, 60000);
javascript复制// core.js
class Core {
constructor() {
this.plugins = new Map();
}
register(name, plugin) {
this.plugins.set(name, plugin.init(this));
}
}
// plugin.js
module.exports = {
init(core) {
return {
execute: () => console.log('Plugin running')
};
}
};
javascript复制class Container {
constructor() {
this.services = new Map();
}
register(name, creator) {
this.services.set(name, creator(this));
}
get(name) {
return this.services.get(name);
}
}
javascript复制const fs = require('fs');
const { WASI } = require('wasi');
const wasi = new WASI();
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
WebAssembly.instantiate(fs.readFileSync('module.wasm'), importObject)
.then((obj) => {
wasi.start(obj.instance);
});
typescript复制// 类型声明合并
declare module 'legacy-module' {
export function deprecatedMethod(): void;
export const newField: string;
}
javascript复制const { SharedArrayBuffer } = require('worker_threads');
const sharedBuffer = new SharedArrayBuffer(1024);
module.exports = { buffer: sharedBuffer };