如何全局修改module.exports,实现所有模块导出自动应用日志代理
全局日志代理自动应用方案咨询
我想做一个日志工具,在所有方法调用前记录信息(比如性能追踪)。目前已经用JS装饰器实现了类及类方法的日志功能:
logger.ts
// 给类所有方法添加日志的类装饰器 export async function logger(something): Promise<void> { // 类装饰器逻辑 } // 方法装饰器 export async function logMethod(something): Promise<void> { console.log("方法调用前日志"); something.apply(this, args); }
类文件中的使用方式:
someclass.ts
@logger export default class SomeClass extends BaseClass { async somemethod(message: string): Promise<boolean> { return true; } }
但非类方法没法用装饰器,所以我写了一个logProxy代理对象实现相同功能:
const logProxy = (obj) => { return new Proxy(obj, { get(target, prop, receiver) { console.log(`访问属性/方法: ${String(prop)}`); // 日志/性能追踪逻辑 return Reflect.get(target, prop, receiver); } }); };
非类文件中的使用方式:
somefile.ts
export async function dosomething(): Promise<void> { const endPoint = `${url}`; await get(endPoint); } module.exports = logProxy(module.exports);
现在要给大量文件手动加这段代码,太麻烦了。有没有全局方案能让所有模块导出自动应用这个代理?或者重写module.exports让它自动执行module.exports = logProxy(module.exports)?
方案1:Node.js 模块加载钩子(--experimental-loader)
Node.js 16+支持自定义模块加载器,可在模块加载完成后自动包装导出内容。
- 创建加载器文件
log-loader.js:
import { readFile } from 'fs/promises'; import { logProxy } from './logger.js'; // 导入你的logProxy export async function load(url, context, defaultLoad) { const result = await defaultLoad(url, context, defaultLoad); // 仅处理JS/TS模块,跳过node_modules if ((url.endsWith('.js') || url.endsWith('.ts')) && !url.includes('node_modules')) { const originalExports = await result.module; // 包装默认导出和所有函数类型的具名导出 result.module = { ...originalExports, default: logProxy(originalExports.default || originalExports), ...Object.fromEntries( Object.entries(originalExports).map(([key, value]) => [key, typeof value === 'function' ? logProxy(value) : value] ) ) }; } return result; }
- 启动项目时指定加载器:
node --experimental-loader ./log-loader.js your-entry-file.js
注意:TS项目需先编译为JS,或结合ts-node的加载器一起使用。
方案2:Webpack/Babel 插件(前端/TS构建项目)
如果用Webpack或Babel构建,可写插件自动在每个模块末尾添加代理包装代码。
Babel插件示例:
const t = require('@babel/types'); module.exports = function() { return { visitor: { Program(path) { // 在模块最后插入代理包装代码 path.pushContainer('body', [ t.expressionStatement( t.assignmentExpression( '=', t.memberExpression(t.identifier('module'), t.identifier('exports')), t.callExpression( t.identifier('logProxy'), [t.memberExpression(t.identifier('module'), t.identifier('exports'))] ) ) ) ]); } } }; };
将该插件加入Babel配置,即可自动给所有模块添加代理。
方案3:重写Module._compile(Node.js)
在Node.js中通过猴子补丁重写Module._compile,在模块编译完成后自动包装导出:
const Module = require('module'); const originalCompile = Module._compile; const { logProxy } = require('./logger'); Module._compile = function(content, filename) { // 执行原编译逻辑 originalCompile.call(this, content, filename); // 跳过node_modules文件 if (!filename.includes('node_modules')) { this.exports = logProxy(this.exports); } };
把这段代码放在项目入口文件最顶部,即可全局生效。注意这种方式可能和其他模块加载逻辑冲突,需测试验证。
方案4:TypeScript自定义转换器(纯TS项目)
针对纯TS项目,可写TS转换器在编译阶段修改AST,自动给所有导出添加代理:
import * as ts from 'typescript'; export function logTransformer(program: ts.Program): ts.TransformerFactory<ts.SourceFile> { return (context) => { return (sourceFile) => { function visit(node: ts.Node): ts.Node { // 包装默认导出 if (ts.isExportAssignment(node)) { return ts.factory.createExportAssignment( undefined, undefined, ts.factory.createCallExpression( ts.factory.createIdentifier('logProxy'), undefined, [node.expression] ) ); } // 处理具名导出(可按需扩展) if (ts.isExportDeclaration(node) && node.exportClause) { const updatedElements = node.exportClause.elements.map(element => { if (ts.isIdentifier(element.name)) { return ts.factory.createExportSpecifier( undefined, element.name, ts.factory.createCallExpression( ts.factory.createIdentifier('logProxy'), undefined, [element.name] ) ); } return element; }); return ts.factory.updateExportDeclaration( node, node.modifiers, ts.factory.createNamedExports(updatedElements), node.moduleSpecifier ); } return ts.visitEachChild(node, visit, context); } return ts.visitNode(sourceFile, visit); }; }; }
在tsconfig.json中配置该转换器,编译时自动处理所有TS文件。
内容的提问来源于stack exchange,提问作者PDHide
相关产品推荐
相关产品推荐

