require 函数
Node.js 应用由模块组成,每个文件就是一个模块。对于 CommonJS 模块规范来说,我们通过 require 函数来导入模块
require时会调用load来加载模块,加载过程如下:
Module.prototype.load = function(filename) {
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
// 获取文件扩展名,默认为js
const extension = findLongestRegisteredExtension(filename);
if (StringPrototypeEndsWith(filename, '.mjs') && !Module._extensions['.mjs'])
throw new ERR_REQUIRE_ESM(filename, true);
// Module._extensions查找对应的匹配的加载器
Module._extensions[extension](this, filename);
this.loaded = true;
const esmLoader = asyncESM.esmLoader;
const exports = this.exports;
if ((module?.module === undefined ||
module.module.getStatus() < kEvaluated) &&
!esmLoader.cjsCache.has(this))
esmLoader.cjsCache.set(this, exports);
};
js加载器
步骤:
- 判断是否存在缓存,存在则取出使用,不存在则读取文件源码
使用 module._compile 方法编译已加载的 js 代码
Module._extensions['.js'] = function(module, filename) {
const cached = cjsParseCache.get(module);
let content;
if (cached?.source) {
content = cached.source;
cached.source = undefined;
} else {
content = fs.readFileSync(filename, 'utf8');
}
module._compile(content, filename);
};
json加载器
步骤:
读取文件源码
通过jsonparse去转换成JSON对象
Module._extensions['.json'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');
try {
module.exports = JSONParse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
node加载器
步骤:
通过rocess.dlopen()方法动态加载共享对象,主要用于require()加载 C++ 插件,不应直接使用
- https://nodejs.org/api/process.html#processdlopenmodule-filename-flags
Module._extensions['.node'] = function(module, filename) {
return process.dlopen(module, path.toNamespacedPath(filename));
};
自定义TS加载器
```typescript const fs = require(“fs”); const Module = require(“module”); const { transformSync } = require(“esbuild”);
Module._extensions[“.ts”] = function (module, filename) { const content = fs.readFileSync(filename, “utf8”); // 通过esbuild转换 ts->cjs const { code } = transformSync(content, { sourcefile: filename, sourcemap: “both”, loader: “ts”, format: “cjs”, }); module._compile(code, filename); };
<a name="c8k9c"></a>
## Node模块预加载机制:
利用 -r, --require 命令行配置项,我们就可以预加载指定的模块
```bash
node -r ./register.js index.ts
pirates是什么
pirates 底层是利用 Node.js 内置 module 模块提供的扩展机制来实现 Hook 功能,当使用 require 函数来加载模块时,Node.js 会根据文件的后缀名来匹配对应的加载器,
addHooks
export function addHook(hook, opts = {}) {
let reverted = false;
const loaders = [];
const oldLoaders = [];
let exts;
// js加载器
const originalJSLoader = Module._extensions['.js'];
// 文件过滤
const matcher = opts.matcher || null;
const ignoreNodeModules = opts.ignoreNodeModules !== false;
// 若没有自定义扩展名则默认为js加载器
exts = opts.extensions || opts.exts || opts.extension || opts.ext || ['.js'];
if (!Array.isArray(exts)) {
exts = [exts];
}
exts.forEach((ext) => {
if (typeof ext !== 'string') {
throw new TypeError(`Invalid Extension: ${ext}`);
}
// 获取旧的加载器若没有则默认为JS加载器
const oldLoader = Module._extensions[ext] || originalJSLoader;
oldLoaders[ext] = Module._extensions[ext];
// 定义新的加载器
loaders[ext] = Module._extensions[ext] = function newLoader(mod, filename) {
let compile;
if (!reverted) {
// 对文件名以及扩展名进行校验
if (shouldCompile(filename, exts, matcher, ignoreNodeModules)) {
// 保存原有的mod._compile方法
compile = mod._compile;
// 定义新的mod.compile方法用于拦截原有的compile方法
mod._compile = function _compile(code) {
// 还原原有的mod._compile方法
mod._compile = compile;
// 执行拦截需要做的相关操作
const newCode = hook(code, filename);
if (typeof newCode !== 'string') {
throw new Error(HOOK_RETURNED_NOTHING_ERROR_MESSAGE);
}
// 执行原有的compile方法进行编译
return mod._compile(newCode, filename);
};
}
}
// 执行原有的加载器
oldLoader(mod, filename);
};
});
return function revert() {
if (reverted) return;
reverted = true;
exts.forEach((ext) => {
if (Module._extensions[ext] === loaders[ext]) {
if (!oldLoaders[ext]) {
delete Module._extensions[ext];
} else {
Module._extensions[ext] = oldLoaders[ext];
}
}
});
};
}