You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
7.9 KiB
293 lines
7.9 KiB
let idInc = 0;
|
|
|
|
const genWrappedFunc = ({
|
|
func,
|
|
smp,
|
|
context,
|
|
timeEventName,
|
|
pluginName,
|
|
endType,
|
|
}) => (...args) => {
|
|
const id = idInc++;
|
|
// we don't know if there's going to be a callback applied to a particular
|
|
// call, so we just set it multiple times, letting each one override the last
|
|
const addEndEvent = () =>
|
|
smp.addTimeEvent("plugins", timeEventName, "end", {
|
|
id,
|
|
// we need to allow failure, since webpack can finish compilation and
|
|
// cause our callbacks to fall on deaf ears
|
|
allowFailure: true,
|
|
});
|
|
|
|
smp.addTimeEvent("plugins", timeEventName, "start", {
|
|
id,
|
|
name: pluginName,
|
|
});
|
|
// invoke an end event immediately in case the callback here causes webpack
|
|
// to complete compilation. If this gets invoked and not the subsequent
|
|
// call, then our data will be inaccurate, sadly
|
|
addEndEvent();
|
|
const normalArgMap = a => wrap(a, pluginName, smp);
|
|
let ret;
|
|
if (endType === "wrapDone")
|
|
ret = func.apply(
|
|
context,
|
|
args.map(a => wrap(a, pluginName, smp, addEndEvent))
|
|
);
|
|
else if (endType === "async") {
|
|
const argsButLast = args.slice(0, args.length - 1);
|
|
const callback = args[args.length - 1];
|
|
ret = func.apply(
|
|
context,
|
|
argsButLast.map(normalArgMap).concat((...callbackArgs) => {
|
|
addEndEvent();
|
|
callback(...callbackArgs);
|
|
})
|
|
);
|
|
} else if (endType === "promise")
|
|
ret = func.apply(context, args.map(normalArgMap)).then(promiseArg => {
|
|
addEndEvent();
|
|
return promiseArg;
|
|
});
|
|
else ret = func.apply(context, args.map(normalArgMap));
|
|
addEndEvent();
|
|
|
|
return ret;
|
|
};
|
|
|
|
const genPluginMethod = (orig, pluginName, smp, type) =>
|
|
function(method, func) {
|
|
const timeEventName = pluginName + "/" + type + "/" + method;
|
|
const wrappedFunc = genWrappedFunc({
|
|
func,
|
|
smp,
|
|
context: this,
|
|
timeEventName,
|
|
pluginName,
|
|
endType: "wrapDone",
|
|
});
|
|
return orig.plugin(method, wrappedFunc);
|
|
};
|
|
|
|
const wrapTap = (tap, pluginName, smp, type, method) =>
|
|
function(id, func) {
|
|
const timeEventName = pluginName + "/" + type + "/" + method;
|
|
const wrappedFunc = genWrappedFunc({
|
|
func,
|
|
smp,
|
|
context: this,
|
|
timeEventName,
|
|
pluginName,
|
|
});
|
|
return tap.call(this, id, wrappedFunc);
|
|
};
|
|
|
|
const wrapTapAsync = (tapAsync, pluginName, smp, type, method) =>
|
|
function(id, func) {
|
|
const timeEventName = pluginName + "/" + type + "/" + method;
|
|
const wrappedFunc = genWrappedFunc({
|
|
func,
|
|
smp,
|
|
context: this,
|
|
timeEventName,
|
|
pluginName,
|
|
endType: "async",
|
|
});
|
|
return tapAsync.call(this, id, wrappedFunc);
|
|
};
|
|
|
|
const wrapTapPromise = (tapPromise, pluginName, smp, type, method) =>
|
|
function(id, func) {
|
|
const timeEventName = pluginName + "/" + type + "/" + method;
|
|
const wrappedFunc = genWrappedFunc({
|
|
func,
|
|
smp,
|
|
context: this,
|
|
timeEventName,
|
|
pluginName,
|
|
endType: "promise",
|
|
});
|
|
return tapPromise.call(this, id, wrappedFunc);
|
|
};
|
|
|
|
const wrappedHooks = [];
|
|
const wrapHooks = (orig, pluginName, smp, type) => {
|
|
const hooks = orig.hooks;
|
|
if (!hooks) return hooks;
|
|
const prevWrapped = wrappedHooks.find(
|
|
w =>
|
|
w.pluginName === pluginName && (w.orig === hooks || w.wrapped === hooks)
|
|
);
|
|
if (prevWrapped) return prevWrapped.wrapped;
|
|
|
|
const genProxy = method => {
|
|
const proxy = new Proxy(hooks[method], {
|
|
get: (target, property) => {
|
|
const raw = Reflect.get(target, property);
|
|
|
|
if (property === "tap" && typeof raw === "function")
|
|
return wrapTap(raw, pluginName, smp, type, method).bind(proxy);
|
|
if (property === "tapAsync" && typeof raw === "function")
|
|
return wrapTapAsync(raw, pluginName, smp, type, method).bind(proxy);
|
|
if (property === "tapPromise" && typeof raw === "function")
|
|
return wrapTapPromise(raw, pluginName, smp, type, method).bind(proxy);
|
|
|
|
return raw;
|
|
},
|
|
set: (target, property, value) => {
|
|
return Reflect.set(target, property, value);
|
|
},
|
|
deleteProperty: (target, property) => {
|
|
return Reflect.deleteProperty(target, property);
|
|
},
|
|
});
|
|
return proxy;
|
|
};
|
|
|
|
const wrapped = Object.keys(hooks).reduce((acc, method) => {
|
|
acc[method] = genProxy(method);
|
|
return acc;
|
|
}, {});
|
|
|
|
wrappedHooks.push({ orig: hooks, wrapped, pluginName });
|
|
|
|
return wrapped;
|
|
};
|
|
|
|
const construcNamesToWrap = [
|
|
"Compiler",
|
|
"Compilation",
|
|
"MainTemplate",
|
|
"Parser",
|
|
"NormalModuleFactory",
|
|
"ContextModuleFactory",
|
|
];
|
|
|
|
const wrappedObjs = [];
|
|
const findWrappedObj = (orig, pluginName) => {
|
|
const prevWrapped = wrappedObjs.find(
|
|
w => w.pluginName === pluginName && (w.orig === orig || w.wrapped === orig)
|
|
);
|
|
if (prevWrapped) return prevWrapped.wrapped;
|
|
};
|
|
const wrap = (orig, pluginName, smp, addEndEvent) => {
|
|
if (!orig) return orig;
|
|
const prevWrapped = findWrappedObj(orig, pluginName);
|
|
if (prevWrapped) return prevWrapped;
|
|
|
|
const getOrigConstrucName = target =>
|
|
target && target.constructor && target.constructor.name;
|
|
const getShouldWrap = target => {
|
|
const origConstrucName = getOrigConstrucName(target);
|
|
return construcNamesToWrap.includes(origConstrucName);
|
|
};
|
|
const shouldWrap = getShouldWrap(orig);
|
|
const shouldSoftWrap = Object.keys(orig)
|
|
.map(k => orig[k])
|
|
.some(getShouldWrap);
|
|
|
|
let wrappedReturn;
|
|
|
|
if (!shouldWrap && !shouldSoftWrap) {
|
|
const vanillaFunc = orig.name === "next";
|
|
wrappedReturn =
|
|
vanillaFunc && addEndEvent
|
|
? function() {
|
|
// do this before calling the callback, since the callback can start
|
|
// the next plugin step
|
|
addEndEvent();
|
|
|
|
return orig.apply(this, arguments);
|
|
}
|
|
: orig;
|
|
} else {
|
|
const proxy = new Proxy(orig, {
|
|
get: (target, property) => {
|
|
const raw = Reflect.get(target, property);
|
|
|
|
if (shouldWrap && property === "plugin")
|
|
return genPluginMethod(
|
|
target,
|
|
pluginName,
|
|
smp,
|
|
getOrigConstrucName(target)
|
|
).bind(proxy);
|
|
|
|
if (shouldWrap && property === "hooks")
|
|
return wrapHooks(
|
|
target,
|
|
pluginName,
|
|
smp,
|
|
getOrigConstrucName(target)
|
|
);
|
|
|
|
if (shouldWrap && property === "compiler") {
|
|
const prevWrapped = findWrappedObj(raw, pluginName);
|
|
if (prevWrapped) {
|
|
return prevWrapped;
|
|
}
|
|
}
|
|
|
|
if (typeof raw === "function") {
|
|
const ret = raw.bind(proxy);
|
|
if (property === "constructor")
|
|
Object.defineProperty(ret, "name", {
|
|
value: raw.name,
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
return raw;
|
|
},
|
|
set: (target, property, value) => {
|
|
return Reflect.set(target, property, value);
|
|
},
|
|
deleteProperty: (target, property) => {
|
|
return Reflect.deleteProperty(target, property);
|
|
},
|
|
});
|
|
|
|
wrappedReturn = proxy;
|
|
}
|
|
|
|
wrappedObjs.push({ pluginName, orig, wrapped: wrappedReturn });
|
|
return wrappedReturn;
|
|
};
|
|
|
|
module.exports.clear = () => {
|
|
wrappedObjs.length = 0;
|
|
wrappedHooks.length = 0;
|
|
};
|
|
|
|
module.exports.WrappedPlugin = class WrappedPlugin {
|
|
constructor(plugin, pluginName, smp) {
|
|
this._smp_plugin = plugin;
|
|
this._smp_pluginName = pluginName;
|
|
this._smp = smp;
|
|
|
|
this.apply = this.apply.bind(this);
|
|
|
|
const wp = this;
|
|
return new Proxy(plugin, {
|
|
get(target, property) {
|
|
if (property === "apply") {
|
|
return wp.apply;
|
|
}
|
|
return target[property];
|
|
},
|
|
set: (target, property, value) => {
|
|
return Reflect.set(target, property, value);
|
|
},
|
|
deleteProperty: (target, property) => {
|
|
return Reflect.deleteProperty(target, property);
|
|
},
|
|
});
|
|
}
|
|
|
|
apply(compiler) {
|
|
return this._smp_plugin.apply(
|
|
wrap(compiler, this._smp_pluginName, this._smp)
|
|
);
|
|
}
|
|
};
|