Skip to content

Commit

Permalink
Merge pull request stephencookdev#31 from stephencookdev/feature/webp…
Browse files Browse the repository at this point in the history
…ack-4#9

Add webpack v4 support
  • Loading branch information
stephencookdev committed Mar 27, 2018
2 parents e78ea2e + 3c012af commit 3240695
Show file tree
Hide file tree
Showing 22 changed files with 34,418 additions and 691 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ SMP follows [semver](https://semver.org/). If upgrading a major version, you can

## Requirements

SMP requires at least Node v6.
SMP requires at least **Node v6**. But otherwise, accepts **all webpack** versions (1, 2, 3, and 4).

## Usage

Expand Down
210 changes: 170 additions & 40 deletions WrappedPlugin/index.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,159 @@
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 = (...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 ret = func.apply(
this,
args.map(a => wrap(a, pluginName, smp, addEndEvent))
);
addEndEvent();
const wrappedFunc = genWrappedFunc({
func,
smp,
context: this,
timeEventName,
pluginName,
endType: "wrapDone",
});
return orig.plugin(method, wrappedFunc);
};

return ret;
};
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);
};

return orig.plugin(method, 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",
Expand Down Expand Up @@ -67,18 +186,21 @@ const wrap = (orig, pluginName, smp, addEndEvent) => {

if (!shouldWrap && !shouldSoftWrap) {
const vanillaFunc = orig.name === "next";
wrappedReturn = vanillaFunc
? function() {
// do this before calling the callback, since the callback can start
// the next plugin step
addEndEvent();
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;
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,
Expand All @@ -87,16 +209,24 @@ const wrap = (orig, pluginName, smp, addEndEvent) => {
getOrigConstrucName(target)
).bind(proxy);

if (typeof target[property] === "function") {
const ret = target[property].bind(proxy);
if (shouldWrap && property === "hooks")
return wrapHooks(
target,
pluginName,
smp,
getOrigConstrucName(target)
);

if (typeof raw === "function") {
const ret = raw.bind(proxy);
if (property === "constructor")
Object.defineProperty(ret, "name", {
value: target.constructor.name,
value: raw.name,
});
return ret;
}

return target[property];
return raw;
},
set: (target, property, value) => {
return Reflect.set(target, property, value);
Expand Down
3 changes: 3 additions & 0 deletions __tests__/common/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
testEnvironment: "node"
};
1 change: 1 addition & 0 deletions __tests__/setups/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ app.js
constants.js
styles.css
smp.test.js
jest.config.js
dist
Loading

0 comments on commit 3240695

Please sign in to comment.