node.js模块系统通过module._extensions、require.resolve钩子、esm加载器和require.cache四大机制实现深度可扩展;前者支持自定义文件加载,后者分别实现路径重写、esm全流程控制及热重载。

Node.js 的模块系统(CommonJS)表面看是简单的 require 和 module.exports,但其底层具备高度可扩展性——通过内置钩子(如 Module._extensions、require.resolve 钩子、ESM 的 register 和自定义加载器),你可以深度干预模块解析、加载、编译甚至缓存行为。
修改 Module._extensions 实现自定义文件类型加载
Module._extensions 是一个对象,键为文件后缀(如 '.js'),值为接收 module 和 filename 的函数。它只对 CommonJS 生效,且在模块首次加载时触发。
例如,让 .txt 文件被 require 时自动读取内容并作为字符串导出:
const fs = require('fs');
require.extensions['.txt'] = function(module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
};
// 后续即可:const content = require('./readme.txt');
⚠️ 注意:require.extensions 在 Node.js 12+ 已被标记为废弃(不推荐用于生产),但它仍是理解模块加载流程的关键入口。实际项目中建议优先使用 ESM 加载器(--loader)替代。
拦截 require.resolve 实现路径重写与别名映射
require.resolve 负责将模块标识符(如 'lodash' 或 '../utils')解析为绝对路径。你可以通过 monkey patch 它来实现路径别名、环境条件路由或虚拟模块注入:
- 覆盖原生方法前先缓存:
const originalResolve = require.resolve; - 在新函数中判断请求路径,匹配别名(如
'@src/config'→/project/src/config/index.js) - 调用原生
originalResolve(request, options)回退兜底,避免破坏默认逻辑
该方式轻量、兼容所有 CommonJS 场景,常用于工具库(如 ts-node、babel-register)中支持路径别名配置。
ESM 自定义加载器(--loader)接管整个导入链
Node.js 12+ 支持通过 --loader ./my-loader.mjs 启动参数注入 ESM 加载器,它能完全控制从 import 解析、获取源码、转换到实例化的全过程。核心钩子包括:
-
resolve(specifier, context, defaultResolve):决定模块位置(支持返回{ format: 'module', shortCircuit: true }跳过后续步骤) -
getFormat(url, context):指定模块格式('module'、'commonjs'、'json'等) -
transformSource(source, context, defaultTransform):对源码做运行前转换(如 Babel 编译、宏替换、代码注入)
例如,在 transformSource 中自动为每个 ESM 模块注入调试信息:
export async function transformSource(source, context, defaultTransform) {
const transformed = `console.log('[Loaded]', ${JSON.stringify(context.url)});\n${source}`;
return { source: transformed };
}
操作 require.cache 实现热重载与模块隔离
require.cache 是一个以绝对路径为键的普通对象,存储已加载的 Module 实例。删除其中条目即可强制下次 require 重新加载文件(常用于开发时热更新):
- 清空单个模块:
delete require.cache[require.resolve('./config.js')]; - 清空所有匹配路径的模块(如全部
.js):Object.keys(require.cache).forEach(key => key.endsWith('.js') && delete require.cache[key]); - 注意:若模块被多个地方依赖,需同步清除其依赖者缓存,否则旧引用仍存在(可用
module.parent反向追踪)
此机制也是 Jest、Mocha 等测试框架实现 jest.resetModules() 的基础。
不复杂但容易忽略——这些钩子不是“黑魔法”,而是 Node.js 将模块系统设计为可组合、可替换的体现。真正掌握它们,才能写出像 esbuild-node-loader、vitest 或自研构建管道这类深度集成工具。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











