@@ -11,15 +11,25 @@ function optimizeJs (jsString, opts) {
1111
1212 walk ( ast , {
1313 enter : function ( node , parent ) {
14- // assuming this node is an argument to a function, return true if it itself
15- // is already padded with parentheses
14+ // estree-walker does not automatically add a parent node pointer to nodes,
15+ // which we need for some of our context checks below.
16+ // normally I would just write "node.parentNode = parent" here, but that makes
17+ // estree-walker think that parentNode is a child node of the node, which leads to
18+ // infinite loops as it walks a circular tree. if we make parent a function, though,
19+ // estree-walker does not follow the link.
20+ node . parent = function ( ) {
21+ return parent
22+ }
23+ // assuming this node is an argument to a function or an element in an array,
24+ // return true if it itself is already padded with parentheses
1625 function isPaddedArgument ( node ) {
17- var idx = parent . arguments . indexOf ( node )
26+ var parentArray = node . parent ( ) . arguments ? node . parent ( ) . arguments : node . parent ( ) . elements
27+ var idx = parentArray . indexOf ( node )
1828 if ( idx === 0 ) { // first arg
1929 if ( prePreChar === '(' && preChar === '(' && postChar === ')' ) { // already padded
2030 return true
2131 }
22- } else if ( idx === parent . arguments . length - 1 ) { // last arg
32+ } else if ( idx === parentArray . length - 1 ) { // last arg
2333 if ( preChar === '(' && postChar === ')' && postPostChar === ')' ) { // already padded
2434 return true
2535 }
@@ -31,30 +41,66 @@ function optimizeJs (jsString, opts) {
3141 return false
3242 }
3343
44+ function isCallExpression ( node ) {
45+ return node && node . type === 'CallExpression'
46+ }
47+
48+ function isArrayExpression ( node ) {
49+ return node && node . type === 'ArrayExpression'
50+ }
51+
52+ // returns true iff node is an argument to a function call expression.
53+ function isArgumentToFunctionCall ( node ) {
54+ return isCallExpression ( node . parent ( ) ) &&
55+ node . parent ( ) . arguments . length &&
56+ node . parent ( ) . arguments . indexOf ( node ) !== - 1
57+ }
58+
59+ // returns true iff node is an element of an array literal which is in turn
60+ // an argument to a function call expression.
61+ function isElementOfArrayArgumentToFunctionCall ( node ) {
62+ return isArrayExpression ( node . parent ( ) ) &&
63+ node . parent ( ) . elements . indexOf ( node ) !== - 1 &&
64+ isArgumentToFunctionCall ( node . parent ( ) )
65+ }
66+
67+ // returns true iff node is an IIFE.
68+ function isIIFE ( node ) {
69+ return isCallExpression ( node . parent ( ) ) &&
70+ node . parent ( ) . callee === node
71+ }
72+
73+ // tries to divine if this function is a webpack module wrapper.
74+ // returns true iff node is an element of an array literal which is in turn
75+ // an argument to a function call expression, and that function call
76+ // expression is an IIFE.
77+ function isProbablyWebpackModule ( node ) {
78+ return isElementOfArrayArgumentToFunctionCall ( node ) &&
79+ node . parent ( ) && // array literal
80+ node . parent ( ) . parent ( ) && // CallExpression
81+ node . parent ( ) . parent ( ) . callee && // function that is being called
82+ node . parent ( ) . parent ( ) . callee . type === 'FunctionExpression'
83+ }
84+
3485 if ( node . type === 'FunctionExpression' ) {
3586 var prePreChar = jsString . charAt ( node . start - 2 )
3687 var preChar = jsString . charAt ( node . start - 1 )
3788 var postChar = jsString . charAt ( node . end )
3889 var postPostChar = jsString . charAt ( node . end + 1 )
3990
40- if ( parent && parent . type === 'CallExpression' ) {
41- // this function is getting called itself or
42- // it is getting passed in to another call expression
43- // the else statement is strictly never hit, but I think the code is easier to read this way
44- /* istanbul ignore else */
45- if ( parent . arguments && parent . arguments . indexOf ( node ) !== - 1 ) {
46- // function passed in to another function. these are almost _always_ executed, e.g.
47- // UMD bundles, Browserify bundles, Node-style errbacks, Promise then()s and catch()s, etc.
48- if ( ! isPaddedArgument ( node ) ) { // don't double-pad
49- magicString = magicString . insertLeft ( node . start , '(' )
50- . insertRight ( node . end , ')' )
51- }
52- } else if ( parent . callee === node ) {
53- // this function is getting immediately invoked, e.g. function(){}()
54- if ( preChar !== '(' ) {
55- magicString . insertLeft ( node . start , '(' )
56- . insertRight ( node . end , ')' )
57- }
91+ if ( isArgumentToFunctionCall ( node ) || isProbablyWebpackModule ( node ) ) {
92+ // function passed in to another function, either as an argument, or as an element
93+ // of an array argument. these are almost _always_ executed, e.g. webpack bundles,
94+ // UMD bundles, Browserify bundles, Node-style errbacks, Promise then()s and catch()s, etc.
95+ if ( ! isPaddedArgument ( node ) ) { // don't double-pad
96+ magicString = magicString . insertLeft ( node . start , '(' )
97+ . insertRight ( node . end , ')' )
98+ }
99+ } else if ( isIIFE ( node ) ) {
100+ // this function is getting immediately invoked, e.g. function(){}()
101+ if ( preChar !== '(' ) {
102+ magicString . insertLeft ( node . start , '(' )
103+ . insertRight ( node . end , ')' )
58104 }
59105 }
60106 }
0 commit comments