require 函数

Node.js 应用由模块组成,每个文件就是一个模块。对于 CommonJS 模块规范来说,我们通过 require 函数来导入模块
require时会调用load来加载模块,加载过程如下:

  1. Module.prototype.load = function(filename) {
  2. this.filename = filename;
  3. this.paths = Module._nodeModulePaths(path.dirname(filename));
  4. // 获取文件扩展名,默认为js
  5. const extension = findLongestRegisteredExtension(filename);
  6. if (StringPrototypeEndsWith(filename, '.mjs') && !Module._extensions['.mjs'])
  7. throw new ERR_REQUIRE_ESM(filename, true);
  8. // Module._extensions查找对应的匹配的加载器
  9. Module._extensions[extension](this, filename);
  10. this.loaded = true;
  11. const esmLoader = asyncESM.esmLoader;
  12. const exports = this.exports;
  13. if ((module?.module === undefined ||
  14. module.module.getStatus() < kEvaluated) &&
  15. !esmLoader.cjsCache.has(this))
  16. esmLoader.cjsCache.set(this, exports);
  17. };

node中内置了三种加载器:js、json、node

js加载器

步骤:

  • 判断是否存在缓存,存在则取出使用,不存在则读取文件源码
  • 使用 module._compile 方法编译已加载的 js 代码

    1. Module._extensions['.js'] = function(module, filename) {
    2. const cached = cjsParseCache.get(module);
    3. let content;
    4. if (cached?.source) {
    5. content = cached.source;
    6. cached.source = undefined;
    7. } else {
    8. content = fs.readFileSync(filename, 'utf8');
    9. }
    10. module._compile(content, filename);
    11. };

    json加载器

    步骤:

  • 读取文件源码

  • 通过jsonparse去转换成JSON对象

    1. Module._extensions['.json'] = function(module, filename) {
    2. const content = fs.readFileSync(filename, 'utf8');
    3. try {
    4. module.exports = JSONParse(stripBOM(content));
    5. } catch (err) {
    6. err.message = filename + ': ' + err.message;
    7. throw err;
    8. }
    9. };

    node加载器

    步骤:

  • 通过rocess.dlopen()方法动态加载共享对象,主要用于require()加载 C++ 插件,不应直接使用

  • https://nodejs.org/api/process.html#processdlopenmodule-filename-flags
    1. Module._extensions['.node'] = function(module, filename) {
    2. return process.dlopen(module, path.toNamespacedPath(filename));
    3. };

    自定义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); };

  1. <a name="c8k9c"></a>
  2. ## Node模块预加载机制:
  3. 利用 -r, --require 命令行配置项,我们就可以预加载指定的模块
  4. ```bash
  5. node -r ./register.js index.ts

pirates是什么

pirates 底层是利用 Node.js 内置 module 模块提供的扩展机制来实现 Hook 功能,当使用 require 函数来加载模块时,Node.js 会根据文件的后缀名来匹配对应的加载器,

addHooks

  1. export function addHook(hook, opts = {}) {
  2. let reverted = false;
  3. const loaders = [];
  4. const oldLoaders = [];
  5. let exts;
  6. // js加载器
  7. const originalJSLoader = Module._extensions['.js'];
  8. // 文件过滤
  9. const matcher = opts.matcher || null;
  10. const ignoreNodeModules = opts.ignoreNodeModules !== false;
  11. // 若没有自定义扩展名则默认为js加载器
  12. exts = opts.extensions || opts.exts || opts.extension || opts.ext || ['.js'];
  13. if (!Array.isArray(exts)) {
  14. exts = [exts];
  15. }
  16. exts.forEach((ext) => {
  17. if (typeof ext !== 'string') {
  18. throw new TypeError(`Invalid Extension: ${ext}`);
  19. }
  20. // 获取旧的加载器若没有则默认为JS加载器
  21. const oldLoader = Module._extensions[ext] || originalJSLoader;
  22. oldLoaders[ext] = Module._extensions[ext];
  23. // 定义新的加载器
  24. loaders[ext] = Module._extensions[ext] = function newLoader(mod, filename) {
  25. let compile;
  26. if (!reverted) {
  27. // 对文件名以及扩展名进行校验
  28. if (shouldCompile(filename, exts, matcher, ignoreNodeModules)) {
  29. // 保存原有的mod._compile方法
  30. compile = mod._compile;
  31. // 定义新的mod.compile方法用于拦截原有的compile方法
  32. mod._compile = function _compile(code) {
  33. // 还原原有的mod._compile方法
  34. mod._compile = compile;
  35. // 执行拦截需要做的相关操作
  36. const newCode = hook(code, filename);
  37. if (typeof newCode !== 'string') {
  38. throw new Error(HOOK_RETURNED_NOTHING_ERROR_MESSAGE);
  39. }
  40. // 执行原有的compile方法进行编译
  41. return mod._compile(newCode, filename);
  42. };
  43. }
  44. }
  45. // 执行原有的加载器
  46. oldLoader(mod, filename);
  47. };
  48. });
  49. return function revert() {
  50. if (reverted) return;
  51. reverted = true;
  52. exts.forEach((ext) => {
  53. if (Module._extensions[ext] === loaders[ext]) {
  54. if (!oldLoaders[ext]) {
  55. delete Module._extensions[ext];
  56. } else {
  57. Module._extensions[ext] = oldLoaders[ext];
  58. }
  59. }
  60. });
  61. };
  62. }