Skip to content

Commit

Permalink
Extract OptionsManager to a separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
boopathi committed Sep 29, 2016
1 parent 8fe830c commit d05e7e7
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 87 deletions.
3 changes: 1 addition & 2 deletions packages/babel-preset-babili/__tests__/options-tests.js
Expand Up @@ -21,8 +21,7 @@ const mocks = [
];

mocks.forEach((mockName) => {
// it's called mockName for jest workaround
// specifically babel-jest-plugin
// it's called mockName for jest(babel-jest-plugin) workaround
jest.mock(mockName, () => mockName);
});

Expand Down
115 changes: 30 additions & 85 deletions packages/babel-preset-babili/src/index.js
@@ -1,4 +1,5 @@
const isPlainObject = require("lodash.isplainobject");
const OptionsManager = require("./options-manager");

// the flat plugin map
// This is to prevent dynamic requires - require('babel-plugin-' + name);
Expand Down Expand Up @@ -28,101 +29,45 @@ module.exports = preset;
function preset(_opts = {}) {
const opts = isPlainObject(_opts) ? _opts : {};

const plugins = [];
// Proxies are options passed to multiple plugins
const proxies = {
keepFnames: ["mangle", "deadcode"]
};

const delegations = invertMap({
keepFnames: [ "mangle", "deadcode" ],
});
const plugins = new OptionsManager(proxies, opts)
.addOption("evaluate", PLUGINS["minify-constant-folding"], true)
.addOption("deadcode", PLUGINS["minify-dead-code-elimination"], true)

option(opts, "evaluate", "minify-constant-folding", true);
option(opts, "deadcode", "minify-dead-code-elimination", true);
.addGroup("unsafe", (optionsManager) => {
optionsManager
.addOption("flip", PLUGINS["minify-flip-comparisons"], true)
.addOption("simplify", PLUGINS["transform-simplify-comparison-operators"], true)
.addOption("guards", PLUGINS["minify-guarded-expressions"], true)
.addOption("typeConstructors", PLUGINS["minify-type-constructors"], true);
})

optionGroup(opts, "unsafe", (opts) => [
option(opts, "flip", "minify-flip-comparisons", true),
option(opts, "simplify", "transform-simplify-comparison-operators", true),
option(opts, "guards", "minify-guarded-expressions", true),
option(opts, "typeConstructors", "minify-type-constructors", true)
]);
.addOption("infinity", PLUGINS["minify-infinity"], true)
.addOption("mangle", PLUGINS["minify-mangle-names"], true)
.addOption("replace", PLUGINS["minify-replace"], true)
.addOption("simplify", PLUGINS["minify-simplify"], true)

option(opts, "infinity", "minify-infinity", true);
option(opts, "mangle", "minify-mangle-names", true);
option(opts, "replace", "minify-replace", true);
option(opts, "simplify", "minify-simplify", true);
.addGroup("properties", (optionsManager) => {
optionsManager
.addOption("memberExpressions", PLUGINS["transform-member-expression-literals"], true)
.addOption("propertyLiterals", PLUGINS["transform-property-literals"], true);
})

optionGroup(opts, "properties", (opts) => [
option(opts, "memberExpressions", "transform-member-expression-literals", true),
option(opts, "propertyLiterals", "transform-property-literals", true)
]);
.addOption("mergeVars", PLUGINS["transform-merge-sibling-variables"], true)
.addOption("booleans", PLUGINS["transform-minify-booleans"], true)

option(opts, "mergeVars", "transform-merge-sibling-variables", true);
option(opts, "booleans", "transform-minify-booleans", true);
.addOption("undefinedToVoid", PLUGINS["transform-undefined-to-void"], true)
.addOption("removeDebugger", PLUGINS["transform-remove-debugger"], false)
.addOption("removeConsole", PLUGINS["transform-remove-console"], false)

option(opts, "undefinedToVoid", "transform-undefined-to-void", true);
option(opts, "removeDebugger", "transform-remove-debugger", false);
option(opts, "removeConsole", "transform-remove-console", false);
.result;

return {
minified: true,
plugins,
};

function option(opts, name, plugin, defaultValue) {
if (typeof opts === "undefined") {
if (defaultValue) {
plugins.push(getPlugin(name, plugin));
}
} else if (isPlainObject(opts) && hop(opts, name)) {
if (isPlainObject(opts[name])) {
plugins.push(getPlugin(name, plugin, opts[name]));
} else if (opts[name]) {
plugins.push(getPlugin(name, plugin));
}
} else if (defaultValue) {
plugins.push(getPlugin(name, plugin));
}
}

function optionGroup(opts, name, fn) {
if (isPlainObject(opts) && (!hop(opts, name) || opts[name])) {
fn(opts[name]);
}
}

function getPlugin(name, plugin, pluginOpts) {
const pluginFn = PLUGINS[plugin];
if (hop(delegations, name)) {
const delegatedOpts = {};
delegations[name].forEach((d) => {
if (hop(opts, d)) {
Object.assign(delegatedOpts, {
[d]: opts[d]
});
}
});
if (isPlainObject(pluginOpts)) {
Object.assign(delegatedOpts, pluginOpts);
}
if (Object.keys(delegatedOpts).length > 0) {
return [pluginFn, delegatedOpts];
}
}
return pluginFn;
}
}

function invertMap(map) {
const inverted = {};
Object.keys(map).forEach((key) => {
map[key].forEach((option) => {
if (!hop(inverted, option)) {
inverted[option] = [];
}
inverted[option].push(key);
});
});
return inverted;
}

function hop(o, k) {
return Object.prototype.hasOwnProperty.call(o, k);
}
126 changes: 126 additions & 0 deletions packages/babel-preset-babili/src/options-manager.js
@@ -0,0 +1,126 @@
const isPlainObject = require("lodash.isplainobject");

/**
* Options Manager
*
* Input Options: Object
* Output: Array of plugins enabled with their options
*
* Handles multiple types of input option keys
*
* 1. boolean and object values
* { mangle: true } // should enable mangler
* { mangle: { blacklist: ["foo"] } } // should enabled mangler
* // and pass obj to mangle plugin
*
* 2. group
* { unsafe: true } // should enable all plugins under unsafe
* { unsafe: { flip: false } } // should disable flip-comparisons plugin
* // and other plugins should take their defaults
* { unsafe: { simplify: {multipass: true}}} // should pass obj to simplify
* // other plugins take defaults
*
* 3. same option passed on to multiple plugins
* { keepFnames: false } // should be passed on to mangle & dce
* // without disturbing their own options
*/
module.exports = class OptionsManager {
constructor(proxies, inputOpts) {
this.result = [];
this.inputOpts = inputOpts;

// proxies are passed on to the respective plugins
this.proxies = invertMapping(proxies);

// the current level to track within group calls
this.state = this.inputOpts;
this.previousStates = [];
}

addOption(optionKey, resolvingValue, defaultOptionValue) {
const opts = this.state;
// preset(undefined)
// or { unsafe: undefined } when in a group
if (typeof opts === "undefined") {
if (defaultOptionValue) {
this.result.push(this.resolve(optionKey, resolvingValue));
}
} else
// preset({ [optionKey]: <value> })
if (isPlainObject(opts) && hop(opts, optionKey)) {
// { mangle: { blacklist: ['foo'] }}
if (isPlainObject(opts[optionKey])) {
this.result.push(this.resolve(optionKey, resolvingValue, opts[optionKey]));
} else
// { mangle: true }
// any truthy value enables the plugin
if (opts[optionKey]) {
this.result.push(this.resolve(optionKey, resolvingValue));
}
} else
// { } -> mangle is not a property
if (defaultOptionValue) {
this.result.push(this.resolve(optionKey, resolvingValue));
}
// chain
return this;
}

addGroup(optionKey, fn) {
const opts = this.state;
if (isPlainObject(opts) && (!hop(opts, optionKey) || opts[optionKey])) {
this.pushState(optionKey);
fn.call(null, this);
this.popState();
}
// chain
return this;
}

resolve(optionKey, resolvingValue, resolveOpts) {
const opts = this.state;
if (hop(this.proxies, optionKey)) {
const proxiedOpts = {};
this.proxies[optionKey].forEach((p) => {
if (hop(opts, p)) {
Object.assign(proxiedOpts, {
[p]: opts[p]
});
}
});
if (isPlainObject(resolveOpts)) {
Object.assign(proxiedOpts, resolveOpts);
}
if (Object.keys(proxiedOpts).length > 0) {
return [resolvingValue, proxiedOpts];
}
}
return resolvingValue;
}

pushState(key) {
this.previousStates.push(this.state);
this.state = this.state[key];
}

popState() {
this.state = this.previousStates.pop();
}
};

function invertMapping(map) {
const inverted = {};
Object.keys(map).forEach((key) => {
map[key].forEach((option) => {
if (!hop(inverted, option)) {
inverted[option] = [];
}
inverted[option].push(key);
});
});
return inverted;
}

function hop(o, k) {
return Object.prototype.hasOwnProperty.call(o, k);
}

0 comments on commit d05e7e7

Please sign in to comment.