Skip to content

Commit

Permalink
patch: improve enforcement performance on legacy browsers (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush authored Nov 19, 2020
1 parent b304602 commit c3949bd
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 74 deletions.
3 changes: 3 additions & 0 deletions jsconfig.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions packages/n4s/src/enforce/bindLazyRule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { RUN_RULE } from 'enforceKeywords';
import genRuleProxy from 'genRuleProxy';
import isFunction from 'isFunction';
import runtimeRules from 'runtimeRules';

// Initiates a chain of functions directly from the `enforce`
// function - that's even though we do not have any closure
// there to store that data.
export default function bindLazyRule(ruleName) {
const registeredRules = [];

const addFn = fnName => (...args) => {
registeredRules.push(
Object.defineProperty(
value => runtimeRules[fnName](value, ...args),
'name',
{ value: fnName }
)
);

const returnvalue = genRuleProxy({}, addFn);

return Object.assign(returnvalue, {
[RUN_RULE]: getValue => {
return registeredRules.every(fn => {
// This inversion of control when getting the value is
// required in order to pass the function over to `shape`
// so it can make the decision which args to pass to `optional`
return fn(isFunction(getValue) ? getValue(fn.name) : getValue);
});
},
});
};

return addFn(ruleName);
}
77 changes: 6 additions & 71 deletions packages/n4s/src/enforce/enforce.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import compounds from 'compounds';
import { RUN_RULE } from 'enforceKeywords';
import bindLazyRule from 'bindLazyRule';
import runner from 'enforceRunner';
import isFunction from 'isFunction';
import isRule from 'isRule';
import genRuleProxy from 'genRuleProxy';
import proxySupported from 'proxySupported';
import rules from 'rules';

const rulesObject = Object.assign(rules(), compounds);

let rulesList = proxySupported() ? null : Object.keys(rulesObject);
import runtimeRules from 'runtimeRules';

const Enforce = value => {
const proxy = genRuleProxy(enforce, ruleName => (...args) => {
runner(rulesObject[ruleName], value, args);
const proxy = genRuleProxy({}, ruleName => (...args) => {
runner(runtimeRules[ruleName], value, args);
return proxy;
});
return proxy;
Expand All @@ -21,72 +15,13 @@ const Enforce = value => {
const enforce = genRuleProxy(Enforce, bindLazyRule);

enforce.extend = customRules => {
Object.assign(rulesObject, customRules);
Object.assign(runtimeRules, customRules);

if (!proxySupported()) {
rulesList = Object.keys(rulesObject);
genRuleProxy(Enforce, bindLazyRule);
}

return enforce;
};

export default enforce;

// Creates a proxy object that has access to all the rules
function genRuleProxy(target, output) {
if (proxySupported()) {
return new Proxy(target, {
get: (target, fnName) => {
if (isRule(rulesObject, fnName)) {
return output(fnName);
}

return target[fnName];
},
});
} else {
/**
* This method is REALLY not recommended as it is slow and iterates over
* all the rules for each direct enforce reference. We only use it as a
* lightweight alternative for the much faster proxy interface
*/
return rulesList.reduce((target, fnName) => {
return Object.defineProperties(target, {
[fnName]: { get: () => output(fnName), configurable: true },
});
}, target);
}
}

// Initiates a chain of functions directly from the `enforce`
// function - that's even though we do not have any closure
// there to store that data.
function bindLazyRule(ruleName) {
const registeredRules = [];

const addFn = fnName => (...args) => {
registeredRules.push(
Object.defineProperty(
value => rulesObject[fnName](value, ...args),
'name',
{ value: fnName }
)
);

const returnvalue = genRuleProxy({}, addFn);

return Object.assign(returnvalue, {
[RUN_RULE]: getValue => {
return registeredRules.every(fn => {
// This inversion of control when getting the value is
// required in order to pass the function over to `shape`
// so it can make the decision which args to pass to `optional`
return fn(isFunction(getValue) ? getValue(fn.name) : getValue);
});
},
});
};

return addFn(ruleName);
}
33 changes: 33 additions & 0 deletions packages/n4s/src/enforce/genRuleProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import isFunction from 'isFunction';
import isRule from 'isRule';
import proxySupported from 'proxySupported';
import runtimeRules from 'runtimeRules';

// Creates a proxy object that has access to all the rules
export default function genRuleProxy(target, output) {
if (proxySupported()) {
return new Proxy(target, {
get: (target, fnName) => {
if (isRule(fnName)) {
return output(fnName);
}

return target[fnName];
},
});
} else {
/**
* This method is REALLY not recommended as it is slow and iterates over
* all the rules for each direct enforce reference. We only use it as a
* lightweight alternative for the much faster proxy interface
*/
for (const fnName in runtimeRules) {
if (!isFunction(target[fnName])) {
Object.defineProperties(target, {
[fnName]: { get: () => output(fnName) },
});
}
}
return target;
}
}
4 changes: 4 additions & 0 deletions packages/n4s/src/enforce/runtimeRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import compounds from 'compounds';
import rules from 'rules';

export default Object.assign(rules(), compounds);
5 changes: 2 additions & 3 deletions packages/n4s/src/lib/isRule.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import isFunction from 'isFunction';
import runtimeRules from 'runtimeRules';

const isRule = (rulesObject, name) => {
return rulesObject.hasOwnProperty(name) && isFunction(rulesObject[name]);
};
const isRule = name => isFunction(runtimeRules[name]);

export default isRule;

0 comments on commit c3949bd

Please sign in to comment.