diff --git a/lib/index.js b/lib/index.js index 80c5a4f..311ca71 100644 --- a/lib/index.js +++ b/lib/index.js @@ -41,6 +41,10 @@ function optimizeJs (jsString, opts) { return false } + function isNumeric (str) { + return /^[0-9]+$/.test(str) + } + function isCallExpression (node) { return node && node.type === 'CallExpression' } @@ -49,6 +53,11 @@ function optimizeJs (jsString, opts) { return node && node.type === 'ArrayExpression' } + function isElementOfArray (node) { + return isArrayExpression(node.parent()) && + node.parent().elements.indexOf(node) !== -1 + } + // returns true iff node is an argument to a function call expression. function isArgumentToFunctionCall (node) { return isCallExpression(node.parent()) && @@ -56,30 +65,50 @@ function optimizeJs (jsString, opts) { node.parent().arguments.indexOf(node) !== -1 } - // returns true iff node is an element of an array literal which is in turn - // an argument to a function call expression. - function isElementOfArrayArgumentToFunctionCall (node) { - return isArrayExpression(node.parent()) && - node.parent().elements.indexOf(node) !== -1 && - isArgumentToFunctionCall(node.parent()) + function isValueOfObjectLiteralWithNumericName (node) { + return node && + node.parent() && + node.parent().type === 'Property' && + node.parent().key && + node.parent().key.type === 'Literal' && + node.parent().key.raw && + isNumeric(node.parent().key.raw) && + node.parent().value === node && + node.parent().parent() && + node.parent().parent().type === 'ObjectExpression' } // returns true iff node is an IIFE. function isIIFE (node) { - return isCallExpression(node.parent()) && + return node && + node.type === 'FunctionExpression' && + isCallExpression(node.parent()) && node.parent().callee === node } + // returns true iff this is an IIFE call expression + function isIIFECall (node) { + return node && + isCallExpression(node) && + node.callee && + node.callee.type === 'FunctionExpression' + } + // tries to divine if this function is a webpack module wrapper. // returns true iff node is an element of an array literal which is in turn // an argument to a function call expression, and that function call // expression is an IIFE. function isProbablyWebpackModule (node) { - return isElementOfArrayArgumentToFunctionCall(node) && - node.parent() && // array literal - node.parent().parent() && // CallExpression - node.parent().parent().callee && // function that is being called - node.parent().parent().callee.type === 'FunctionExpression' + return isElementOfArray(node) && + isArgumentToFunctionCall(node.parent()) && + isIIFECall(node.parent().parent()) + } + + function isProbablyBrowserifyModule (node) { + return isElementOfArray(node) && + isValueOfObjectLiteralWithNumericName(node.parent()) && + isArgumentToFunctionCall(node.parent().parent().parent()) && + isIIFECall(node.parent().parent().parent().parent()) } if (node.type === 'FunctionExpression') { @@ -88,7 +117,9 @@ function optimizeJs (jsString, opts) { var postChar = jsString.charAt(node.end) var postPostChar = jsString.charAt(node.end + 1) - if (isArgumentToFunctionCall(node) || isProbablyWebpackModule(node)) { + if (isArgumentToFunctionCall(node) || + isProbablyWebpackModule(node) || + isProbablyBrowserifyModule(node)) { // function passed in to another function, either as an argument, or as an element // of an array argument. these are almost _always_ executed, e.g. webpack bundles, // UMD bundles, Browserify bundles, Node-style errbacks, Promise then()s and catch()s, etc. diff --git a/test/cases/browserify-object-literal-in-global-scope/input.js b/test/cases/browserify-object-literal-in-global-scope/input.js new file mode 100644 index 0000000..d623627 --- /dev/null +++ b/test/cases/browserify-object-literal-in-global-scope/input.js @@ -0,0 +1,9 @@ +// a browserify-style object literal that isn't a parameter shouldn't be optimized. +var a = { + 1:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] +}; diff --git a/test/cases/browserify-object-literal-in-global-scope/output.js b/test/cases/browserify-object-literal-in-global-scope/output.js new file mode 100644 index 0000000..d623627 --- /dev/null +++ b/test/cases/browserify-object-literal-in-global-scope/output.js @@ -0,0 +1,9 @@ +// a browserify-style object literal that isn't a parameter shouldn't be optimized. +var a = { + 1:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] +}; diff --git a/test/cases/browserify-style-multi-element/input.js b/test/cases/browserify-style-multi-element/input.js new file mode 100644 index 0000000..32f8e47 --- /dev/null +++ b/test/cases/browserify-style-multi-element/input.js @@ -0,0 +1,24 @@ +!function(o){ + return o[0](); +}( + { + 1:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ], + 2:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ], + 3:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] + } +); diff --git a/test/cases/browserify-style-multi-element/output.js b/test/cases/browserify-style-multi-element/output.js new file mode 100644 index 0000000..63cdf13 --- /dev/null +++ b/test/cases/browserify-style-multi-element/output.js @@ -0,0 +1,24 @@ +!(function(o){ + return o[0](); +})( + { + 1:[ + (function(o,r,t){ + console.log("browserify style!"); + }), + {} + ], + 2:[ + (function(o,r,t){ + console.log("browserify style!"); + }), + {} + ], + 3:[ + (function(o,r,t){ + console.log("browserify style!"); + }), + {} + ] + } +); diff --git a/test/cases/browserify-style-non-iife/input.js b/test/cases/browserify-style-non-iife/input.js new file mode 100644 index 0000000..29227ed --- /dev/null +++ b/test/cases/browserify-style-non-iife/input.js @@ -0,0 +1,10 @@ +foo( + { + 1:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] + } +); diff --git a/test/cases/browserify-style-non-iife/output.js b/test/cases/browserify-style-non-iife/output.js new file mode 100644 index 0000000..29227ed --- /dev/null +++ b/test/cases/browserify-style-non-iife/output.js @@ -0,0 +1,10 @@ +foo( + { + 1:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] + } +); diff --git a/test/cases/browserify-style-one-element/input.js b/test/cases/browserify-style-one-element/input.js new file mode 100644 index 0000000..71f9e66 --- /dev/null +++ b/test/cases/browserify-style-one-element/input.js @@ -0,0 +1,12 @@ +!function(o){ + return o[0](); +}( + { + 1:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] + } +); diff --git a/test/cases/browserify-style-one-element/output.js b/test/cases/browserify-style-one-element/output.js new file mode 100644 index 0000000..6741ca5 --- /dev/null +++ b/test/cases/browserify-style-one-element/output.js @@ -0,0 +1,12 @@ +!(function(o){ + return o[0](); +})( + { + 1:[ + (function(o,r,t){ + console.log("browserify style!"); + }), + {} + ] + } +); diff --git a/test/cases/browserify-style-with-non-numeric-propname/input.js b/test/cases/browserify-style-with-non-numeric-propname/input.js new file mode 100644 index 0000000..8feba69 --- /dev/null +++ b/test/cases/browserify-style-with-non-numeric-propname/input.js @@ -0,0 +1,14 @@ +// a browserify-style function call that has non-numeric object indices shouldn't +// be optimized. +!function(o){ + return o[0](); +}( + { + a:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] + } +); diff --git a/test/cases/browserify-style-with-non-numeric-propname/output.js b/test/cases/browserify-style-with-non-numeric-propname/output.js new file mode 100644 index 0000000..6a4954c --- /dev/null +++ b/test/cases/browserify-style-with-non-numeric-propname/output.js @@ -0,0 +1,14 @@ +// a browserify-style function call that has non-numeric object indices shouldn't +// be optimized. +!(function(o){ + return o[0](); +})( + { + a:[ + function(o,r,t){ + console.log("browserify style!"); + }, + {} + ] + } +);