CommonJS模块加载器需用函数包装模块代码、维护缓存、解析路径并注入exports/require/module/__filename/__dirname。核心是通过vm.runInThisContext编译执行,同步读取文件,缓存模块输出,仅支持相对路径和.js文件。

CommonJS 模块系统本身不是 JavaScript 语言原生特性,而是 Node.js 在早期实现的一套同步模块规范。要“手动实现一个简单的 CommonJS 模块加载器”,本质是模拟 require、module、exports 和 global.__dirname/__filename 的行为,核心在于:**用函数包装模块代码、维护缓存、处理路径解析、注入上下文对象**。
1. 基础原理:模块被包装成函数执行
Node.js 实际把每个 .js 文件内容包裹进一个函数里,类似:
这样就能安全地提供局部变量(如 exports)并隔离作用域。我们实现加载器时,也要做这件事。
2. 核心步骤:手写 require 函数
下面是一个极简但可运行的 CommonJS 风格加载器(仅支持相对路径和 .js 扩展名,不处理循环依赖):
立即学习“Java免费学习笔记(深入)”;
关键点说明:
- 用
Map缓存已加载模块,避免重复执行 - 用
fs.readFileSync同步读取文件(符合 CommonJS 同步语义) - 用
vm.runInThisContext或Function构造器编译模块字符串为函数 - 传入
exports、require、module、__filename、__dirname五个参数 - 模块内部的
module.exports即为返回值
3. 一个可运行的最小实现(Node.js 环境)
保存为 my-require.js:
const fs = require('fs');
const path = require('path');
const vm = require('vm');
<p>const cache = new Map();</p><p>function myRequire(id, baseDir = process.cwd()) {
// 解析绝对路径(只支持 ./ 和 ../ 开头)
const absPath = path.resolve(baseDir, id);
const ext = path.extname(absPath) || '.js';
const fullPath = path.join(path.dirname(absPath), path.basename(absPath, ext) + ext);</p><p>if (cache.has(fullPath)) {
return cache.get(fullPath).exports;
}</p><p>const code = fs.readFileSync(fullPath, 'utf8');</p><p>// 包装成 CommonJS 函数
const wrapper = <code>(function(exports, require, module, __filename, __dirname) { ${code} })</code>;
const fn = vm.runInThisContext(wrapper, { filename: fullPath });</p><p>const exports = {};
const module = { exports };
const dirname = path.dirname(fullPath);
const filename = fullPath;</p><p>// 执行模块函数
fn(exports, (id) => myRequire(id, dirname), module, filename, dirname);</p><p>cache.set(fullPath, module);
return module.exports;
}</p><p>// 导出供外部使用
module.exports = myRequire;使用示例:
// math.js
module.exports.add = (a, b) => a + b;
<p>// main.js
const myRequire = require('./my-require');
const math = myRequire('./math.js');
console.log(math.add(2, 3)); // 54. 注意事项和限制
这个简易加载器适合理解原理,但生产中不能替代 Node.js 原生 require:
- 不支持
node_modules查找逻辑(比如从./node_modules逐级向上找) - 不处理 JSON、C++ 插件等其他模块类型
- 没有错误堆栈映射(原始行号会丢失)
- 不支持 ES 模块混用(
import/export) - 同步读取在浏览器环境不可用(需配合打包或 fetch + eval)


















