• Stars
    star
    3,754
  • Rank 11,389 (Top 0.3 %)
  • Language
    JavaScript
  • License
    Apache License 2.0
  • Created over 7 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Optimize a JS file for faster parsing (UNMAINTAINED)

optimize-js Build Status JavaScript Style Guide

Optimize a JavaScript file for faster initial execution and parsing, by wrapping all immediately-invoked functions or likely-to-be-invoked functions in parentheses.

⚠️ Maintenance note ⚠️ This project is unmaintained. I consider it an interesting experiment, but I have no intention to keep updating the benchmark results with every new browser release, or to add new features. I invite folks to keep using it, but to be aware that they should heavily benchmark their own websites to ensure it's actually a significant performance improvement in their target browsers.

Update The V8 team wrote a blog post about why you probably shouldn't use optimize-js anymore.

Install

npm install -g optimize-js

Usage

optimize-js input.js > output.js

Example input:

!function (){}()
function runIt(fun){ fun() }
runIt(function (){})

Example output:

!(function (){})()
function runIt(fun){ fun() }
runIt((function (){}))

Benchmark overview

Browser Typical speed boost/regression using optimize-js
Chrome 55 20.63%
Edge 14 13.52%
Firefox 50 8.26%
Safari 10 -1.04%

These numbers are based on a benchmark of common JS libraries. For benchmark details, see benchmarks.

To test on your own JavaScript bundle, check out test-optimize-js.

CLI

Usage: optimize-js [ options ]

Options:
  --source-map  include source map                                     [boolean]
  -h, --help    Show help                                              [boolean]

Examples:
  optimize-js input.js > output.js    optimize input.js
  optimize-js < input.js > output.js  read from stdin, write to stdout

JavaScript API

var optimizeJs = require('optimize-js');
var input = "!function() {console.log('wrap me!')}";
var output = optimizeJs(input); // "!(function() {console.log('wrap me!')})()"

You can also pass in arguments:

var optimizeJs = require('optimize-js');
var input = "!function() {console.log('wrap me!')}";
var output = optimizeJs(input, {
  sourceMap: true
}); // now the output has source maps

Why?

Modern JavaScript engines like V8, Chakra, and SpiderMonkey have a heuristic where they pre-parse most functions before doing a full parse. The pre-parse step merely checks for syntax errors while avoiding the cost of a full parse.

This heuristic is based on the assumption that, on the average web page, most JavaScript functions are never executed or are lazily executed. So a pre-parse can prevent a slower startup time by only checking for what the browser absolutely needs to know about the function (i.e. whether it's syntactically well-formed or not).

Unfortunately this assumption breaks down in the case of immediately-invoked function expressions (IIFEs), such as these:

(function () { console.log('executed!') })();
(function () { console.log('executed Crockford-style!') }());
!function () { console.log('executed UglifyJS-style!') }();

The good news is that JS engines have a further optimization, where they try to detect such IIFEs and skip the pre-parse step. Hooray!

The bad news, though, is that these heuristics don't always work, because they're based on a greedy method of checking for a '(' token immediately to the left of the function. (The parser avoids anything more intricate because it would amount to parsing the whole thing, negating the benefit of the pre-parse). In cases without the paren (which include common module formats like UMD/Browserify/Webpack/etc.), the browser will actually parse the function twice, first as a pre-parse and second as a full parse. This means that the JavaScript code runs much more slowly overall, because more time is spent parsing than needs to be. See "The cost of small modules" for an idea of how bad this can get.

Luckily, because the '(' optimization for IIFEs is so well-established, we can exploit this during our build process by parsing the entire JavaScript file in advance (a luxury the browser can't afford) and inserting parentheses in the cases where we know the function will be immediately executed (or where we have a good hunch). That's what optimize-js does.

More details on the IIFE optimization can be found in this discussion. Some of my thoughts on the virtues of compile-time optimizations can be found in this post.

FAQs

How does it work?

The current implementation is to parse to a syntax tree and check for functions that:

  1. Are immediately-invoked via any kind of call statement (function(){}(), !function(){}(), etc.)
  2. Are passed in directly as arguments to another function

The first method is an easy win – those functions are immediately executed. The second method is more of a heuristic, but tends to be a safe bet given common patterns like Node-style errbacks, Promise chains, and UMD/Browserify/Webpack module declarations.

In all such cases, optimize-js wraps the function in parentheses.

But... you're adding bytes!

Yes, optimize-js might add as many as two bytes (horror!) per function, which amounts to practically nil once you take gzip into account. To prove it, here are the gzipped sizes for the libraries I use in the benchmark:

Script Size (bytes) Difference (bytes)
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

Is optimize-js intended for library authors?

Sure! If you are already shipping a bundled, minified version of your library, then there's no reason not to apply optimize-js (assuming you benchmark your code to ensure it does indeed help!). However, note that optimize-js should run after Uglify, since Uglify strips extra parentheses and also negates IIFEs by default. This also means that if your users apply Uglification to your bundle, then the optimization will be undone.

Also note that because optimize-js optimizes for some patterns that are based on heuristics rather than known eagerly-invoked functions, it may actually hurt your performance in some cases. (See benchmarks below for examples.) Be sure to check that optimize-js is a help rather than a hindrance for your particular codebase, using something like:

<script>
var start = performance.now();
</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.

You may also want to check out 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.

Shouldn't this be Uglify's job?

Possibly! This is a free and open-source library, so I encourage anybody to borrow the code or the good ideas. :)

Why not paren-wrap every single function?

As described above, the pre-parsing optimization in browsers is a very good idea for the vast majority of the web, where most functions aren't immediately executed. However, since optimize-js knows when your functions are immediately executed (or can make reasonable guesses), it can be more judicious in applying the paren hack.

Does this really work for every JavaScript engine?

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!)

In the case of Chakra, Uglify-style IIFEs are actually already optimized, but using optimize-js doesn't hurt because a function preceded by '(' still goes into the fast path.

Benchmarks

These tests were run using a handful of popular libraries, wrapped in performance.now() measurements. Each test reported the median of 251 runs. optimize-js commit da51013 was tested. Minification was applied using uglifyjs -mc, Uglify 2.7.0.

You can also try a live version of the benchmark.

Chrome 55, Windows 10 RS1, Surfacebook i5

Script Original Optimized Improvement Minified Min+Optimized Improvement
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%

Edge 14, Windows 10 RS1, SurfaceBook i5

Script Original Optimized Improvement Minified Min+Optimized Improvement
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%

Firefox 50, Windows 10 RS1, Surfacebook i5

Script Original Optimized Improvement Minified Min+Optimized Improvement
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%

Safari 10, macOS Sierra, 2013 MacBook Pro i5

Script Original Optimized Improvement Minified Min+Optimized Improvement
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%

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.

Plugins

See also

Thanks

Thanks to @krisselden, @bmaurer, and @pleath for explaining these concepts in the various GitHub issues. Thanks also to astexplorer, acorn, and magic-string for making the implementation so easy.

Thanks to Sasha Aickin for generous contributions to improving this library (especially in v1.0.3) and prodding me to improve the accuracy of the benchmarks.

Contributing

Build and run tests:

npm install
npm test

Run the benchmarks:

npm run benchmark # then open localhost:9090 in a browser

Test code coverage:

npm run coverage

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

More Repositories

1

fuite

A tool for finding memory leaks in web apps
JavaScript
3,488
star
2

pokedex.org

Offline-capable Pokédex web site (unmaintained)
JavaScript
2,269
star
3

marky

High-resolution JavaScript timer based on performance.mark/measure (491 bytes min+gz)
JavaScript
1,097
star
4

pinafore

Alternative web client for Mastodon (UNMAINTAINED)
JavaScript
1,021
star
5

emoji-picker-element

A lightweight emoji picker for the modern web
JavaScript
956
star
6

slow-deps

🐌 Measure which dependencies in a project are slowest to npm install (UNMAINTAINED)
JavaScript
513
star
7

blob-util

Cross-browser utils for working with binary Blobs
TypeScript
489
star
8

Catlog

Logcat-reading app for Android (UNMAINTAINED)
Java
471
star
9

promise-worker

Promise-based messaging for Web Workers and Service Workers
JavaScript
465
star
10

pouchdb-find

Easy-to-use query language for PouchDB. ⚠️ NOTICE ⚠️: moved to the PouchDB repo
JavaScript
433
star
11

rollupify

Browserify transform to apply Rollup (UNMAINTAINED)
JavaScript
386
star
12

cjs-to-es6

CLI to convert CommonJS to ES6 modules (UNMAINTAINED)
JavaScript
262
star
13

pseudo-worker

A tiny and mostly spec-compliant WebWorker polyfill
JavaScript
243
star
14

cordova-plugin-sqlite-2

Native SQLite database API for Cordova/PhoneGap/Ionic, modeled after WebSQL (UNMAINTAINED)
JavaScript
169
star
15

CustomFastScrollViewDemo

Demo of an Android app using a FastScrollView with non-alphabetic overlays.
Java
130
star
16

pouchdb-electron

PouchDB for Electron (formerly Atom Shell)
126
star
17

hello-javascript

Demo of a JavaScript module that supports many publishing options
JavaScript
121
star
18

jison-debugger

UI for debugging Jison grammars (UNMAINTAINED)
JavaScript
117
star
19

pretty-s3-index-html

A prettier index.html for listing public files and folders in Amazon S3 buckets. (UNMAINTAINED)
HTML
112
star
20

node-websql

The WebSQL Database API, implemented for Node.js
JavaScript
87
star
21

SuperSaiyanScrollView

Super-fast, super-lightweight sectioned lists for Android
Java
81
star
22

state-of-binary-data-in-the-browser

The state of binary data in the browser (2015)
79
star
23

KeepScore

Score keeping app for Android (unmaintained)
Java
68
star
24

rollup-comparison

compare rollup to browserify/webpack
JavaScript
67
star
25

vdom-as-json

Convert virtual-dom objects to and from JSON (UNMAINTAINED)
JavaScript
64
star
26

database-comparison

Compare DOM-blocking in browser databases
JavaScript
58
star
27

vdom-serialized-patch

Serialize virtual-dom patches as a minimal JSON object (UNMAINTAINED)
JavaScript
56
star
28

pouchdb-phonegap-cordova

PouchDB for PhoneGap/Cordova
55
star
29

chord-magic

Musical chord parser, transposer, and disambiguator in JavaScript
JavaScript
54
star
30

html5workertest

Show which APIs are supported in Web Workers and Service Workers (UNMAINTAINED)
JavaScript
53
star
31

resources-for-mastodon-newbies

Links and tips for Mastodon newbies
53
star
32

fruitdown

Browser-based LevelDOWN adapter that works over Apple IndexedDB (UNMAINTAINED)
JavaScript
50
star
33

vuvuzela

Simple and non-recursive JSON parse/stringify library
JavaScript
45
star
34

AppTracker

Android app that logs app usage statistics in a background Service and displays app information (most frequently used, most recently used, etc.) in a widget or in the main activity.
Java
42
star
35

cost-of-small-modules

Benchmark showing the cost of various bundlers (repo locked 🔒)
Shell
38
star
36

pouchdb-async-storage

PouchDB adapter for AsyncStorageDOWN for use in React Native (INCOMPLETE)
JavaScript
37
star
37

ChordReaderRoot

Android app for reading and transposing downloaded guitar chord charts. (unmaintained)
Java
29
star
38

PouchDroid

PouchDB + Android = deliciously synchronous (DEPRECATED! DO NOT USE!)
JavaScript
29
star
39

async-functions-in-pouchdb

Demo of ES7 async functions in PouchDB
JavaScript
28
star
40

pouchdb-ionic

PouchDB for Ionic Framework
27
star
41

arrow-key-navigation

Add left/right focus navigation to a web app or KaiOS app
JavaScript
23
star
42

base64-encode-string

Simple module that converts a string to base64 (for educational purposes)
JavaScript
23
star
43

pouchdb-with-service-workers

Repo for brainstorming on PouchDB replication with service workers
22
star
44

tiny-queue

Simple JavaScript FIFO queue implementation to avoid having to do shift()
JavaScript
21
star
45

css-talk-2022

Talk given in October 2022 for perf.now about CSS runtime performance
JavaScript
21
star
46

pouchdb-nw

PouchDB for NW.js (aka Node-Webkit)
20
star
47

OfflineBrowser

Android app to view HTML pages offline.
Java
19
star
48

test-optimize-js

Web page to test optimize-js against any JavaScript file
JavaScript
19
star
49

serviceworker-update-demo

Effort to write a ServiceWorker demo app that properly manages updates
JavaScript
16
star
50

JapaneseNameConverterRoot

Android app to convert English names into Japanese characters.
Java
16
star
51

SimpleTalker

Simple Android app to speak some text passed in as a parameter. Intended to be used from the command line.
Java
16
star
52

pouchdb-ionic-hello-world

PouchDB Ionic "hello world" app (using Ionic v1)
JavaScript
15
star
53

pouchdb-cordova-hello-world-with-sqlite-plugin

"Hello world" Cordova app with PouchDB, using the SQLite Plugin
CSS
15
star
54

mingz

Check the browserified+min+gz size of any npm module (bash script)
14
star
55

braziljs-2016

Nolan's BrazilJS talk: "We can work it out: from Web Workers to Service Workers"
JavaScript
14
star
56

PopupDemo

Demo of a Quick Action-like popup in Android
Java
14
star
57

open-stylable

Web component where styles leak in, but they don't leak out
JavaScript
13
star
58

substring-trie

Minimalistic trie implementation for prefix searches
JavaScript
13
star
59

cordova-prepopulated-database-demo

Demo of prepopulated databases in Cordova SQLite Plugin 2
JavaScript
12
star
60

pouchdb-nw-hello-world

Demo of using PouchDB in NW.js (aka Node-WebKit)
JavaScript
12
star
61

package-json-versionify

Browserify transform to strip everything from package.json except for the "version" field.
JavaScript
12
star
62

measure-style-and-layout

Demo of measuring style and layout in a webapp
HTML
10
star
63

pouchdb-ionic-2-typescript-demo

Demo of PouchDB with Ionic 2 and TypeScript
JavaScript
10
star
64

browserify-count-modules

Count the total number of modules in a Browserify bundle.
JavaScript
10
star
65

web-starter-kit-rollupify

web starter kit, but with browserify+watchify+rollupify
HTML
10
star
66

webworker-postmessage-perf-test

Web Worker postMessage() benchmark
HTML
9
star
67

pouchdb-chrome-app

PouchDB for Chrome packaged apps
9
star
68

lodash-bundle-size-test

Test repo for experimenting with babel-plugin-lodash and lodash-webpack-plugin
JavaScript
8
star
69

emoji-mart-outside-react

Demo of how to use emoji-mart outside of React
JavaScript
8
star
70

throw-max-listeners-error

Throw an error if the max number of EventEmitter listeners is exceeded
JavaScript
7
star
71

pouchdb-getting-started-todo

Complete PouchDB "Getting Started" Todo app
CSS
7
star
72

memory-leaks-2020

Slides for a talk given at QueensJS in August 2020
HTML
7
star
73

database-comparison-worker-pouch

Fork of https://github.com/nolanlawson/database-comparison to demo worker-pouch
JavaScript
7
star
74

pouchdb-adapter-cordova-sqlite-demo

Demo of using pouchdb-adapter-cordova-sqlite in an Ionic 1 project (note: PouchDB v6, Ionic v1)
JavaScript
7
star
75

sugar-pouch

A sweeter, simpler interface for PouchDB (work in progress)
6
star
76

pouchdb-chrome-app-hello-world

"Hello world" Chrome app with PouchDB
JavaScript
6
star
77

shadow-selector-benchmark

Benchmark of shadow DOM with various CSS selectors
JavaScript
6
star
78

debug-websql

Simple script to console.log every SQL query received by Web SQL
JavaScript
6
star
79

sqlite-plugin-fork

Fork of the SQLite plugin from 2014, back when it was compatible with PouchDB
JavaScript
6
star
80

pouchdb-cordova-hello-world

"Hello world" Cordova app with PouchDB
CSS
6
star
81

fabric-calculator

Web app to calculate how much fabric you need for a sewing project
Svelte
6
star
82

RelatednessCalculator

Java library for parsing English relative names ("mom," "dad," "grandma," etc.) and calculating relatedness coefficients.
Java
6
star
83

pouchdb-perf-report-3.10

PouchDB Performance Report for v3.1.0, November 2014
5
star
84

pouchdb-prebuilt-demo

Demo of a prebult PouchDB database, used in a Cordova app.
JavaScript
5
star
85

async-functions-with-regenerator

A "hello world" for async/await with Babel and Regenerator
JavaScript
5
star
86

CatLogDonate

Java
5
star
87

webperf-2016-03

Nolan's 30-minute talk on Web Workers for the NYC Web Performance meetup in March 2016.
JavaScript
5
star
88

ultimate-crossword-app

The Ultimate Crossword - an online crossword puzzle (UNMAINTAINED)
JavaScript
5
star
89

react-wheel-jank-demo

Demo of a React app that causes janky scrolling by attaching a wheel event to the entire document
JavaScript
5
star
90

couch-community-compat-table

The state of sync pairs in the Couch community
4
star
91

offlinefirst-2016-03

Nolan's presentation for Offline First meetup, Boston March 2016
JavaScript
4
star
92

pouchdb-ionic-2-hello-world-with-sqlite

PouchDB "Hello world" app using Ionic v2, with native SQLite
TypeScript
4
star
93

async-rect

DEPRECATED - do not use
JavaScript
4
star
94

RelatednessCalculatorInterface

Grails frontend interface for the RelatednessCalculator
JavaScript
4
star
95

jnameconverter-server

JapaneseNameConverter simple RESTful web server
Java
4
star
96

cascadia-2016

Nolan's talk on Web Workers and Service Workers for Cascadia Fest 2016
JavaScript
4
star
97

starwars-data

generate data for the starwars-offline app
Python
4
star
98

pwa-deploy

Deploy a Progressive Web App to a connected Android device or emulator (EXPERIMENTAL - DON'T USE THIS)
Java
4
star
99

async-functions-with-kneden

A "hello world" for async/await with Babel and Kneden
JavaScript
3
star
100

independent-rendering-test

Independent Rendering test demo
JavaScript
3
star