require 函数
Node.js 应用由模块组成,每个文件就是一个模块。对于 CommonJS 模块规范来说,我们通过 require 函数来导入模块
require时会调用load来加载模块,加载过程如下:
Module.prototype.load = function(filename) {this.filename = filename;this.paths = Module._nodeModulePaths(path.dirname(filename));// 获取文件扩展名,默认为jsconst 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 命令行配置项,我们就可以预加载指定的模块```bashnode -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];}}});};}
