optimize-js
Advanced tools
Comparing version 1.0.2 to 1.0.3
121
lib/index.js
@@ -14,6 +14,16 @@ 'use strict' | ||
enter: function (node, parent) { | ||
// assuming this node is an argument to a function, return true if it itself | ||
// is already padded with parentheses | ||
// estree-walker does not automatically add a parent node pointer to nodes, | ||
// which we need for some of our context checks below. | ||
// normally I would just write "node.parentNode = parent" here, but that makes | ||
// estree-walker think that parentNode is a child node of the node, which leads to | ||
// infinite loops as it walks a circular tree. if we make parent a function, though, | ||
// estree-walker does not follow the link. | ||
node.parent = function () { | ||
return parent | ||
} | ||
// assuming this node is an argument to a function or an element in an array, | ||
// return true if it itself is already padded with parentheses | ||
function isPaddedArgument (node) { | ||
var idx = parent.arguments.indexOf(node) | ||
var parentArray = node.parent().arguments ? node.parent().arguments : node.parent().elements | ||
var idx = parentArray.indexOf(node) | ||
if (idx === 0) { // first arg | ||
@@ -23,3 +33,3 @@ if (prePreChar === '(' && preChar === '(' && postChar === ')') { // already padded | ||
} | ||
} else if (idx === parent.arguments.length - 1) { // last arg | ||
} else if (idx === parentArray.length - 1) { // last arg | ||
if (preChar === '(' && postChar === ')' && postPostChar === ')') { // already padded | ||
@@ -36,2 +46,72 @@ return true | ||
function isNumeric (str) { | ||
return /^[0-9]+$/.test(str) | ||
} | ||
function isCallExpression (node) { | ||
return node && node.type === 'CallExpression' | ||
} | ||
function isArrayExpression (node) { | ||
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()) && | ||
node.parent().arguments.length && | ||
node.parent().arguments.indexOf(node) !== -1 | ||
} | ||
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 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 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') { | ||
@@ -43,21 +123,18 @@ var prePreChar = jsString.charAt(node.start - 2) | ||
if (parent && parent.type === 'CallExpression') { | ||
// this function is getting called itself or | ||
// it is getting passed in to another call expression | ||
// the else statement is strictly never hit, but I think the code is easier to read this way | ||
/* istanbul ignore else */ | ||
if (parent.arguments && parent.arguments.indexOf(node) !== -1) { | ||
// function passed in to another function. these are almost _always_ executed, e.g. | ||
// UMD bundles, Browserify bundles, Node-style errbacks, Promise then()s and catch()s, etc. | ||
if (!isPaddedArgument(node)) { // don't double-pad | ||
magicString = magicString.insertLeft(node.start, '(') | ||
.insertRight(node.end, ')') | ||
} | ||
} else if (parent.callee === node) { | ||
// this function is getting immediately invoked, e.g. function(){}() | ||
if (preChar !== '(') { | ||
magicString.insertLeft(node.start, '(') | ||
.insertRight(node.end, ')') | ||
} | ||
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. | ||
if (!isPaddedArgument(node)) { // don't double-pad | ||
magicString = magicString.insertLeft(node.start, '(') | ||
.insertRight(node.end, ')') | ||
} | ||
} else if (isIIFE(node)) { | ||
// this function is getting immediately invoked, e.g. function(){}() | ||
if (preChar !== '(') { | ||
magicString.insertLeft(node.start, '(') | ||
.insertRight(node.end, ')') | ||
} | ||
} | ||
@@ -64,0 +141,0 @@ } |
{ | ||
"name": "optimize-js", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "Optimize initial JavaScript execution/parsing by wrapping eager functions", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"test": "standard && mocha test/test.js", | ||
"test": "standard && mocha --timeout 60000 test/test.js", | ||
"benchmark": "npm run build-benchmark && hs -p 9090 benchmarks", | ||
@@ -9,0 +9,0 @@ "build-benchmark": "sh bin/build-benchmark.sh", |
142
README.md
@@ -6,2 +6,4 @@ optimize-js [![Build Status](https://travis-ci.org/nolanlawson/optimize-js.svg?branch=master)](https://travis-ci.org/nolanlawson/optimize-js) [![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) | ||
See the [changelog](#changelog) for recent changes. | ||
Install | ||
@@ -36,10 +38,10 @@ --- | ||
| Browser | Typical speed boost using `optimize-js` | | ||
| Browser | Typical speed boost/regression using optimize-js | | ||
| ---- | ----- | | ||
| Chrome 52 | 57.09% | | ||
| Edge 14 | 28.88% | | ||
| Firefox 48 | 12.49% | | ||
| Safari 10 | 6.54% | | ||
| Chrome 55 | 20.63% | | ||
| Edge 14 | 13.52% | | ||
| Firefox 50 | 8.26% | | ||
| Safari 10 | -1.04% | | ||
For benchmark details, see [benchmarks](#benchmarks). | ||
These numbers are based on a benchmark of common JS libraries. For benchmark details, see [benchmarks](#benchmarks). | ||
@@ -135,16 +137,16 @@ CLI | ||
| Script | Size (bytes) | Difference (bytes) | ||
| Script | Size (bytes) | Difference (bytes) | | ||
| ---- | --- | --- | | ||
| benchmarks/ember.min.js | 126957 | | | ||
| benchmarks/ember.min.optimized.js | 127134 | +177 | | ||
| benchmarks/immutable.min.js | 15782 | | | ||
| benchmarks/immutable.min.optimized.js | 15809 | +27 | | ||
| benchmarks/jquery.min.js | 30450 | | | ||
| benchmarks/jquery.min.optimized.js | 30524 | +74 | | ||
| benchmarks/lodash.min.js | 24528 | | | ||
| benchmarks/lodash.min.optimized.js | 24577 | +49 | | ||
| benchmarks/pouchdb.min.js | 45298 | | | ||
| benchmarks/pouchdb.min.optimized.js | 45426 | +128 | | ||
| benchmarks/three.min.js | 125070 | | | ||
| benchmarks/three.min.optimized.js | 125129 | +59 | | ||
| benchmarks/create-react-app.min.js | 160387 || | ||
| benchmarks/create-react-app.min.optimized.js | 160824 |+ 437 | | ||
| benchmarks/immutable.min.js | 56738 || | ||
| benchmarks/immutable.min.optimized.js | 56933 |+ 195 | | ||
| benchmarks/jquery.min.js | 86808 || | ||
| benchmarks/jquery.min.optimized.js | 87109 |+ 301 | | ||
| benchmarks/lodash.min.js | 71381 || | ||
| benchmarks/lodash.min.optimized.js | 71644 |+ 263 | | ||
| benchmarks/pouchdb.min.js | 140332 || | ||
| benchmarks/pouchdb.min.optimized.js | 141231 |+ 899 | | ||
| benchmarks/three.min.js | 486996 || | ||
| benchmarks/three.min.optimized.js | 487279 |+ 283 | | ||
@@ -158,9 +160,20 @@ ### Is `optimize-js` intended for library authors? | ||
```js | ||
```html | ||
<script> | ||
var start = performance.now(); | ||
// your code goes here... | ||
</script> | ||
<script src="myscript.js"></script> | ||
<script> | ||
var end = performance.now(); | ||
console.log('took ' + (end - start) + 'ms'); | ||
</script> | ||
``` | ||
Note that the script boundaries are actually recommended, in order to truly measure the full parse/compile time. | ||
If you'd like to avoid measuring the network overhead, you can see how we do it in [our benchmarks](https://github.com/nolanlawson/optimize-js/tree/master/benchmarks). | ||
You may also want to check out [marky](http://github.com/nolanlawson/marky), | ||
which allows you to easily set mark/measure points that you can visually inspect in the Dev Tools timeline to ensure that the full | ||
compile time is being measured. | ||
Also, be sure to test in multiple browsers! If you need an Edge VM, check out [edge.ms](http://edge.ms). | ||
@@ -180,3 +193,3 @@ | ||
Based on my tests, this optimization seems to work best for V8 (Chrome), followed by Chakra (Edge), followed by SpiderMonkey (Firefox). For JavaScriptCore (Safari) it seems to be basically a wash, although it's hard to tell because JSCore is so fast already that the numbers are tiny. | ||
Based on my tests, this optimization seems to work best for V8 (Chrome), followed by Chakra (Edge), followed by SpiderMonkey (Firefox). For JavaScriptCore (Safari) it seems to be basically a wash, and may actually be a slight regression overall depending on your codebase. (Again, this is why it's important to actually measure on your own codebase, on the browsers you actually target!) | ||
@@ -191,17 +204,16 @@ In the case of Chakra, [Uglify-style IIFEs are actually already optimized](https://github.com/mishoo/UglifyJS2/issues/640#issuecomment-247792319), but using `optimize-js` doesn't hurt because a | ||
You can also try [a live version of the benchmark](https://nolanlawson.github.io/optimize-js/) (note: very slow, running locally is recommended). | ||
You can also try [a live version of the benchmark](https://nolanlawson.github.io/optimize-js/). | ||
### Chrome 52, macOS Sierra, 2013 MacBook Pro i5 | ||
### Chrome 55, Windows 10 RS1, Surfacebook i5 | ||
| Script | Original | Optimized | Improvement | Minified | Min+Optimized | Improvement | | ||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | | ||
| ImmutableJS | 12.76ms | 1.75ms | **86.29%** | 10.47ms | 1.68ms | **83.95%** | | ||
| jQuery | 26.73ms | 8.72ms | **67.38%** | 23.02ms | 9ms | **60.90%** | | ||
| Lodash | 29.13ms | 28.03ms | **3.78%** | 20.7ms | 24.45ms | **-18.12%** | | ||
| Ember | 1.48ms | 1.33ms | **10.14%** | 70.93ms | 1.24ms | **98.25%** | | ||
| PouchDB | 60.98ms | 31.59ms | **48.20%** | 40.63ms | 32.02ms | **21.19%** | | ||
| ThreeJS | 10.6ms | 10.16ms | **4.15%** | 66.18ms | 10.33ms | **84.39%** | | ||
| Create React App | 55.39ms | 51.71ms | **6.64%** | 26.12ms | 21.09ms | **19.26%** | | ||
| ImmutableJS | 11.61ms | 7.95ms | **31.50%** | 8.50ms | 5.99ms | **29.55%** | | ||
| jQuery | 22.51ms | 16.62ms | **26.18%** | 19.35ms | 16.10ms | **16.80%** | | ||
| Lodash | 20.88ms | 19.30ms | **7.57%** | 20.47ms | 19.86ms | **3.00%** | | ||
| PouchDB | 43.75ms | 20.36ms | **53.45%** | 36.40ms | 18.78ms | **48.43%** | | ||
| ThreeJS | 71.04ms | 72.98ms | **-2.73%** | 54.99ms | 39.59ms | **28.00%** | | ||
Overall improvement: **20.63%** | ||
Overall improvement: **57.09%** | ||
### Edge 14, Windows 10 RS1, SurfaceBook i5 | ||
@@ -211,24 +223,22 @@ | ||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | | ||
| ImmutableJS | 5.11ms | 1ms | **80.43%** | 4.61ms | 1ms | **78.31%** | | ||
| jQuery | 21.62ms | 12.31ms | **43.06%** | 21.27ms | 12.69ms | **40.34%** | | ||
| Lodash | 22.1ms | 21.81ms | **1.31%** | 19.97ms | 19.05ms | **4.61%** | | ||
| Ember | 0.33ms | 0.32ms | **3.03%** | 0.33ms | 0.32ms | **3.03%** | | ||
| PouchDB | 37.54ms | 28.44ms | **24.24%** | 47.81ms | 65.23ms | **-36.44%** | | ||
| ThreeJS | 30.9ms | 19.08ms | **38.25%** | 68.94ms | 18.26ms | **73.51%** | | ||
| Create React App | 32.46ms | 24.85ms | **23.44%** | 26.49ms | 20.39ms | **23.03%** | | ||
| ImmutableJS | 8.94ms | 6.19ms | **30.74%** | 7.79ms | 5.41ms | **30.55%** | | ||
| jQuery | 22.56ms | 14.45ms | **35.94%** | 16.62ms | 12.99ms | **21.81%** | | ||
| Lodash | 22.16ms | 21.48ms | **3.05%** | 15.77ms | 15.46ms | **1.96%** | | ||
| PouchDB | 24.07ms | 21.22ms | **11.84%** | 39.76ms | 52.86ms | **-32.98%** | | ||
| ThreeJS | 43.77ms | 39.99ms | **8.65%** | 54.00ms | 36.57ms | **32.28%** | | ||
Overall improvement: **13.52%** | ||
Overall improvement: **28.88%** | ||
### Firefox 50, Windows 10 RS1, Surfacebook i5 | ||
### Firefox 48, macOS Sierra, 2013 MacBook Pro i5 | ||
| Script | Original | Optimized | Improvement | Minified | Min+Optimized | Improvement | | ||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | | ||
| ImmutableJS | 4.92ms | 1.74ms | **64.63%** | 2.87ms | 1.31ms | **54.36%** | | ||
| jQuery | 15.73ms | 12.64ms | **19.64%** | 14.88ms | 12.43ms | **16.47%** | | ||
| Lodash | 13.8ms | 14.6ms | **-5.80%** | 9.27ms | 9.33ms | **-0.65%** | | ||
| Ember | 3.22ms | 5.55ms | **-72.36%** | 9.42ms | 5.27ms | **44.06%** | | ||
| PouchDB | 15.05ms | 16.65ms | **-10.63%** | 11.96ms | 11.6ms | **3.01%** | | ||
| ThreeJS | 21.09ms | 17.63ms | **16.41%** | 26.83ms | 21.68ms | **19.19%** | | ||
| Create React App | 33.56ms | 28.02ms | **16.50%** | 24.71ms | 22.05ms | **10.76%** | | ||
| ImmutableJS | 6.52ms | 5.75ms | **11.80%** | 4.96ms | 4.58ms | **7.47%** | | ||
| jQuery | 15.77ms | 13.97ms | **11.41%** | 12.90ms | 12.15ms | **5.85%** | | ||
| Lodash | 17.08ms | 16.63ms | **2.64%** | 13.11ms | 13.22ms | **-0.80%** | | ||
| PouchDB | 19.23ms | 16.77ms | **12.82%** | 13.77ms | 12.89ms | **6.42%** | | ||
| ThreeJS | 38.33ms | 37.36ms | **2.53%** | 33.01ms | 30.32ms | **8.16%** | | ||
Overall improvement: **8.26%** | ||
Overall improvement: **12.49%** | ||
### Safari 10, macOS Sierra, 2013 MacBook Pro i5 | ||
@@ -238,13 +248,12 @@ | ||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | | ||
| ImmutableJS | 1.23ms | 1.39ms | **-13.01%** | 1.22ms | 1.12ms | **8.20%** | | ||
| jQuery | 4.16ms | 3.91ms | **6.01%** | 3.83ms | 3.94ms | **-2.87%** | | ||
| Lodash | 3.95ms | 3.9ms | **1.27%** | 3.38ms | 3.36ms | **0.59%** | | ||
| Ember | 0.55ms | 0.5ms | **9.09%** | 0.44ms | 0.4ms | **9.09%** | | ||
| PouchDB | 2.31ms | 2.15ms | **6.93%** | 2.32ms | 2.21ms | **4.74%** | | ||
| ThreeJS | 8.51ms | 7.62ms | **10.46%** | 8.03ms | 6.82ms | **15.07%** | | ||
| Create React App | 31.60ms | 31.60ms | **0.00%** | 23.10ms | 23.50ms | **-1.73%** | | ||
| ImmutableJS | 5.70ms | 5.60ms | **1.75%** | 4.50ms | 4.50ms | **0.00%** | | ||
| jQuery | 12.40ms | 12.60ms | **-1.61%** | 10.80ms | 10.90ms | **-0.93%** | | ||
| Lodash | 14.70ms | 14.50ms | **1.36%** | 11.10ms | 11.30ms | **-1.80%** | | ||
| PouchDB | 14.00ms | 14.20ms | **-1.43%** | 11.70ms | 12.10ms | **-3.42%** | | ||
| ThreeJS | 35.60ms | 36.30ms | **-1.97%** | 27.50ms | 27.70ms | **-0.73%** | | ||
Overall improvement: **-1.04%** | ||
Overall improvement: **6.54%** | ||
Note that these results may vary based on your machine, how taxed your CPU is, gremlins, etc. I ran the full suite a few times on all browsers and found these numbers to be roughly representative. In our test suite, we use a median of 151 runs to reduce variability. | ||
Note that these results may vary based on your machine, how taxed your CPU is, gremlins, etc. I ran the full suite a few times on all browsers and found these numbers to be roughly representative. However, the final "overall improvement" may vary by as much as 5%, and individual libraries can swing a bit too. | ||
Plugins | ||
@@ -269,2 +278,5 @@ --- | ||
Thanks to [Sasha Aickin](https://github.com/aickin) for generous contributions to improving this library (especially in v1.0.3) | ||
and prodding me to improve the accuracy of the benchmarks. | ||
Contributing | ||
@@ -291,1 +303,15 @@ ----- | ||
``` | ||
Changelog | ||
---- | ||
- v1.0.3 | ||
- Much more accurate benchmark (#37) | ||
- Browserify-specific fixes (#29, #36, #39) | ||
- Webpack-specific fixes (#7, #34) | ||
- v1.0.2 | ||
- Use estree-walker to properly parse ES6 (#31) | ||
- v1.0.1: | ||
- Don't call process.exit(0) on success (#11) | ||
- v1.0.0 | ||
- Initial release |
32958
163
308