time-analytics-webpack-plugin
Version:
analytize the time of loaders and plugins
169 lines • 9.06 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable prefer-rest-params */
/* eslint-disable @typescript-eslint/no-shadow */
const crypto_1 = require("crypto");
const path_1 = __importDefault(require("path"));
const analyzer_1 = require("./analyzer");
const const_1 = require("./const");
const utils_1 = require("./utils");
// This is not the same as `require` function, but we hack `require` through override it.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const originModuleRequire = require('module').prototype.require;
// TODO: hack for mjs module, this is how webpack import module
// if(url === undefined) url = require("url");
// var loaderUrl = url.pathToFileURL(loader.path);
// var modulePromise = eval("import(" + JSON.stringify(loaderUrl.toString()) + ")");
function hackWrapLoaderModule(loaderPaths, wrapLoaderModuleCallback) {
const wrapRequire = (requireMethod) => {
const wrappedRequire = function (...args) {
(0, utils_1.assert)(originModuleRequire === requireMethod);
// although `require` should only accept one parameter and `this` binding should be undefined, we do want to make less surprise.
const originExport = requireMethod.apply(this, args);
// `id` is the input of `require`, like `require(id)`
const id = args[0];
const isOriginExportAWebpackLoader = loaderPaths.includes(id);
if (isOriginExportAWebpackLoader) {
(0, utils_1.assert)(path_1.default.isAbsolute(id), 'Webpack should convert the loader path to absolute path. Although we not use this info.');
// console.log(`Wrap Require: it should be webpack which requires loader, the loader is ${id}`);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const isHackLoader = originExport === loader;
if (isHackLoader) {
// console.log(`Wrap Require: Hack require should not work for ${PACKAGE_LOADER_PATH}`);
return originExport;
}
// if (originExport.__smpHacked) return originExport;
// originExport.__smpHacked = true;
return wrapLoaderModuleCallback(originExport, id);
}
return originExport;
};
wrappedRequire.resolve = requireMethod.resolve;
wrappedRequire.extensions = requireMethod.extensions;
wrappedRequire.cache = requireMethod.cache;
wrappedRequire.main = requireMethod.main;
return wrappedRequire;
};
// @ts-ignore
if (typeof System === 'object' && typeof System.import === 'function') {
// @ts-ignore
System.import = wrapReq(System.import);
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Module = require('module');
// TODO: check whether thread-loader works for this case or not
Module.prototype.require = wrapRequire(originModuleRequire);
}
const loader = function timeAnalyticHackLoader(source) {
// console.log('Time analytics plugin: normal loader is executed');
return source;
};
/**
* Override `require` method, so that we could return a wrapped loader function.
*
* Each time the wrapped function is called, we could do some extra work.
*/
loader.pitch = function () {
// console.log('Time analytics plugin: pitch loader is executed, take over the "require" function');
const analyzerInstance = analyzer_1.analyzer;
const resourcePath = this.resourcePath;
const loaderPaths = this.loaders
.map((l) => l.path)
.filter((l) => !l.includes(const_1.PACKAGE_NAME));
// Hack ourselves to overwrite the `require` method so we can override the
// loadLoaders
// `loaderModule` means the cjs or mjs module
hackWrapLoaderModule(loaderPaths, function wrapLoaderModuleCallback(loaderModule, path) {
const wrapLoaderFunc = (originLoader, loaderType) => {
// return originLoader;
const uuid = (0, crypto_1.randomUUID)();
// const loaderTypeText = loaderType === LoaderType.pitch ? 'pitch' : 'normal';
const wrappedLoader = function wrappedLoaderFunc() {
// const tmp = loaderName;
// console.log(`Wrapped loader: ${tmp}'s ${loaderTypeText} function is executed.`);
// console.log('origin loader is ', originLoader);
let isSync = true;
const almostThis = Object.assign({}, this, {
async: () => {
isSync = false;
const originCallback = this.async(arguments);
return function () {
analyzerInstance.collectLoaderInfo({
callId: uuid,
kind: analyzer_1.AnalyzeInfoKind.loader,
eventType: analyzer_1.LoaderEventType.end,
loaderType,
loaderPath: path,
resourcePath,
time: (0, utils_1.now)(),
isAsync: !isSync,
});
// const asyncResult = arguments[1];
// console.log(`Origin loader: ${tmp}'s ${loaderTypeText} loader, async result is \n ${chalk.red(asyncResult)} `);
originCallback.apply(this, arguments);
};
},
});
analyzerInstance.collectLoaderInfo({
callId: uuid,
kind: analyzer_1.AnalyzeInfoKind.loader,
eventType: analyzer_1.LoaderEventType.start,
loaderType,
loaderPath: path,
resourcePath,
time: (0, utils_1.now)(),
isAsync: !isSync,
});
// @ts-ignore, normal loader and pitch loader is kind of different, but we do not care.
const ret = originLoader.apply(almostThis, arguments);
// if it's an async loader, we return `undefined`, as webpack request
// however, it feels not really matters
if (!isSync) {
// console.log(`Origin loader: ${tmp}'s ${loaderTypeText} loader, an async loader, return undefined`);
return undefined;
}
else {
// console.log(`Origin loader: ${tmp}'s ${loaderTypeText} loader, not an async loader, result is ${chalk.redBright(ret)}`);
}
analyzerInstance.collectLoaderInfo({
callId: uuid,
kind: analyzer_1.AnalyzeInfoKind.loader,
eventType: analyzer_1.LoaderEventType.end,
loaderType,
loaderPath: path,
resourcePath,
time: (0, utils_1.now)(),
isAsync: !isSync,
});
return ret;
};
// wrappedLoader.__origional_loader = originLoader;
// wrappedLoader.__origional_loader_type = loaderTypeText;
return wrappedLoader;
};
return wrapLoaderModule(loaderModule);
function wrapLoaderModule(module) {
// do the same check as webpack itself
if (typeof module !== 'function' && typeof module !== 'object') {
throw new Error('Time analytics plugin tries to use the same checek as webpack, you see this error because Time analytics plugin think it should be a bug which should also be existed in webpack.');
}
// get normal loader function according to module is mjs or cjs
const originNormalLoaderFunc = typeof module === 'function' ? module : module.default;
const originPitchLoaderFunc = module.pitch;
const wrappedNormalLoaderFunc = originNormalLoaderFunc ? wrapLoaderFunc(originNormalLoaderFunc, analyzer_1.LoaderType.normal) : undefined;
const wrappedPitchLoaderFunc = originPitchLoaderFunc ? wrapLoaderFunc(originPitchLoaderFunc, analyzer_1.LoaderType.pitch) : undefined;
// convert the module from either cjs or mjs to a mocked mjs module.
const mockModule = {
...module,
default: wrappedNormalLoaderFunc,
pitch: wrappedPitchLoaderFunc,
};
return mockModule;
}
});
};
module.exports = loader;
//# sourceMappingURL=loader.js.map