This handbook is designed to help you understand how to use Closure Compiler and learn its features.
The Closure Compiler is a tool for making JavaScript download and run faster. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls.
Closure Compiler can compile, optimize and bundle JavaScript code. You can use it for building your JavaScript apps for production. It is the most advanced JavaScript optimization tool. It generates smallest bundle and emits efficient JavaScript code by doing whole program analysis and optimization, removing closures, inlining function calls, reusing variable names and pre-computing constant expressions.
React + ReactDOM build comparison
Tool | Output size | Gzipped |
---|---|---|
Webpack 2 | 139KB Β Β Β | 42KB |
Closure Compiler | 90KB Β Β Β Β | 32KB |
Here's an example of compiler's compilation output:
input
function hello(name) {
const message = `Hello, ${name}!`;
console.log(message);
}
hello('New user');
output
console.log("Hello, New user!");
- Introduction
- Table of Contents
- Getting started
- Compilation levels
- Advanced compilation
- Compiler optimizations
- Dead code elimination and Tree-shaking
- Unreachable and redundant code elimination
- Cross-module code motion
- Constant folding
- Function call inlining
- Property flattening (collapsing)
- Variable and property renaming
- Statement fusion (merging) & variable declarations grouping
- Alternate syntax substitution
- RegExp optimization
- Known methods folding
- Property assignment collection
- Anonymous functions naming
- Compiler flags
- Supported languages
- JavaScript modules
- Recipes
- Who is using it?
Closure Compiler is written in Java, but it also has JavaScript port. The best way to use it is Splittable bundler.
npm i google-closure-compiler-js
const compile = require('google-closure-compiler-js').compile;
const flags = {
jsCode: [{src: 'const inc = (x) => x + 1;'}]
};
const out = compile(flags);
console.log(out.compiledCode); // 'var inc=function(a){return a+1};'
const ClosureCompiler = require('google-closure-compiler-js').webpack;
const path = require('path');
module.exports = {
entry: [
path.join(__dirname, 'entry.js')
],
output: {
path: path.join(__dirname, 'build'),
filename: 'bundle.js'
},
plugins: [
new ClosureCompiler({
options: {
languageIn: 'ECMASCRIPT6',
languageOut: 'ECMASCRIPT3',
compilationLevel: 'ADVANCED'
}
})
]
};
const compiler = require('google-closure-compiler-js').gulp();
gulp.task('build', function() {
return gulp.src('enrty.js', {base: './'})
.pipe(compiler({
compilationLevel: 'ADVANCED',
jsOutputFile: 'bundle.js',
createSourceMap: true
}))
.pipe(gulp.dest('./build'));
});
Splittable is a module bundler for JavaScript based on Closure Compiler. Basically it's a wrapper with zero configuration which supports ES6 and code splitting out of the box.
const splittable = require('splittable');
splittable({
// Create bundles from 2 entry modules `./src/a` and `./src/b`.
modules: ['./src/a', './src/b'],
writeTo: 'dist/',
})
.then((info) => {
console.info('Compilation successful');
if (info.warnings) {
console.warn(info.warnings);
}
})
.catch((error) => console.error('Compilation failed', error));
Compilation level is a compiler setting which denotes optimizations level to be applied to JavaScript code.
WHITESPACE_ONLY
Removes comments, line breaks, unnecessary spaces and other whitespace.
SIMPLE
(default)
Includes WHITESPACE_ONLY
optimizations and renames variable names to shorter names to reduce the size of output code. It renames only variables local to functions, which means that it won't break references to third party code from global scope.
ADVANCED
Includes SIMPLE
optimizations and performs aggressive transformations such as closures elimination, inlining function calls, reusing variable names, pre-computing constant expressions, tree-shaking, cross-module code motion and dead code elimination.
This kind of aggressive compression makes some assumptions about your code. If it doesn't conform to those assumptions, Closure Compiler will produce output that does not run.
With ADVANCED
compilation level Closure Compiler renames global variables, function names and properties and removes unused code. This can lead to output that will not run if your code doesn't follow certain rules.
If you want to use globally defined variables and functions in your code safely, you must tell the compiler about those references.
input
// `moment` is declared in global scope
window.moment().subtract(10, 'days').calendar();
output
// `moment` was renamed to `a`
// this will not run
window.a().b(10, 'days').calendar();
The way you do it is via externs. See Externs section for more information.
If you are about to build a library that exports to global scope, you must tell the compiler about variables that should be exported safely.
input
// `MY_APP` is declared in global scope
window.MY_APP = {};
output
// `MY_APP` was renamed to `a`
// it's no longer possible to reference `MY_APP`
window.a = {};
This should be done by exporting symbols into global scope. See Exporting to global scope section for more information.
Closure Compiler never rewrites strings. You should use only one way of declaring and accessing a property:
- declare with a symbol, access with dot notation
- declare with a string, access with a string
input
// `msg` property is declared and accessed in different ways
obj = { msg: 'Hey!' };
console.log(obj['msg']);
output
// `msg` symbol is renamed to `a`, but `'msg'` text is not
obj = { a: 'Hey!' };
console.log(obj.msg);
Closure Compiler performs a number of optimizations to produce a small output size. Some of them are being applied in intermediate compilation pass to produce AST which is suited best for further code optimization.
NOTE: Below is a list of the most interesting optimizations that compiler does. But there are more of them, you can find them all in comments in the source code of the compiler.
βDead codeβ is a code that is never going to be called in your program. Closure Compiler can efficiently determine and remove such code because it is a whole-program optimization compiler, which means that it performs analysis of the whole program (in comparison to less effective analysis on module level). It constructs a graph of all variables and dependencies which are declared in your code, does graph traversal to find what should be included and dismisses the rest, which is a dead code.
input
/* log.js module */
export const logWithMsg = (msg, arg) => console.log(msg, arg);
export const log = (arg) => console.log(arg);
/* math.js module */
export const min = (a, b) => Math.min(a, b);
export const max = (a, b) => Math.max(a, b);
export const exp = (x) => x * x;
export const sum = (xs) => xs.reduce((a, b) => a + b, 0);
/* entry.js entry point */
import * as math from './math';
import * as logger from './log';
const nums = [0, 1, 2, 3, 4, 5];
const msg = 'Result:';
if (false) {
logger.log('nothing');
} else {
logger.logWithMsg(msg, math.sum(nums));
}
output
// Even though the entire modules namespace was imported,
// tree-shaking didn't include dependency code that is not used here.
// Also a dead code within `if (false) { ... }` was removed.
var c = function(a) {
return a.reduce(function(a, b) {
return a + b;
}, 0);
}([0,1,2,3,4,5]);
console.log("Result:",c);
This optimization is a little different from dead code elimination. It removes βlive codeβ that doesn't have an impact on a program, e.g. statements without side effects (true;
), useless break
, continue
and return
.
Closure Compiler moves variables, functions and methods between modules. It moves the code along modules tree down to those modules where this code is needed. This can reduce parsing time before actual execution. This technique is especially useful for advanced code splitting which works on variables level, rather than modules level.
input
/* math.js module */
export const sum = (xs) => xs.reduce((a, b) => a + b, 0);
export const mult = (xs) => xs.reduce((a, b) => a * b, 1);
/* entry-1.js module */
import * as math from './math';
console.log(math.sum(nums));
/* entry-2.js module */
import * as math from './math';
console.log(math.sum([4, 5, 6]) + math.mult([3, 5, 6]));
output
/* common.js shared code */
function c(a) {
return a.reduce(function(a, b) {
return a + b;
}, 0);
};
/* entry-1.bundle.js */
console.log(c(nums)); // using shared code
/* entry-2.bundle.js */
console.log(
// using shared code
c([4,5,6]) +
// moved from `math.js` module
function(a) {
return a.reduce(function(a, b) {
return a * b;
}, 1);
}([3,5,6])
);
Constant folding is the process of recognizing and evaluating constant expressions at compile time rather than computing them at runtime. Terms in constant expressions are typically simple literals, such as the integer literal 2 , but they may also be variables whose values are known at compile time.
input
const years = 14;
const monthsInYear = 12;
const daysInMonth = 30;
console.log(years * monthsInYear * daysInMonth);
output
console.log(5040);
// `years * monthsInYear * daysInMonth` computed at compile time
// because they are known as constants
To inline a function means to replace a function call with its body. The function definition can be dismissed and it also eliminates an additional function call. If the compiler couldn't perform this type of inlining, it can inline function declaration with a call it in place.
input
const person = {
fname: 'John',
lname: 'Doe',
};
function getFullName({ fname, lname }) {
return fname + ' ' + lname;
}
console.log(getFullName(person));
output
var a = { a: "John", b: "Doe" };
console.log(a.a + " " + a.b);
Collapsing object properties into separate variables enables such optimizations as variable renaming, inlining and better dead code removal.
input
const person = {
fname: 'John',
lname: 'Doe'
};
console.log(person.fname);
output
var person$fname = "John",
person$lname = "Doe"; // <- is not used, can be removed
console.log(person$fname);
Small output size is partially achieved by renaming all variables and object properties. Because the compiler renames object properties you have to make sure that you are referencing and declaring properties either with symbol (obj.prop
) or string (obj['prop']
).
input
const user = window.session.user;
console.log(user.apiToken, user.tokenExpireDate);
output
var a = window.b.f;
console.log(a.a, a.c);
Statement fusion tries to merge multiple statements in a single one. And variable declarations grouping groups multiple variable declarations into a single one.
input
const fname = 'John';
const lname = 'Doe';
if (fname) {
console.log(fname);
}
output
var fname = "John", lname = "Doe";
fname && console.log(fname);
Simplifies conditional expressions, replaces if
s with ternary operator, object and array constructs with literals and simplifies return
s.
Removes unnecessary flags and reorders them for better gzip.
This precomputes known methods such as join
, indexOf
, substring
, substr
, parseInt
and parseFloat
when they are called with constants.
input
[0, 1, 2, 3, 4, 5].join('');
output
"012345"
Looks for assignments to properties of object/array immediately following its creation using the abbreviated syntax and merges assigned values into object/array creation construct.
input
const coll = [];
coll[0] = 0;
coll[2] = 5;
const obj = { x: 1 };
obj.y = 2;
output
var coll = [0, , 5], obj = { x:1, y:2 };
Gives anonymous function names. This makes it way easier to debug because debuggers and stack traces use the function names.
input
math.simple.add = function(a, b) {
return a + b;
};
output
math.simple.add = function $math$simple$add$(a, b) {
return a + b;
};
There are much more compiler flags, see all of them in google/closure-compiler-js repo.
Flag | Default | Usage |
---|---|---|
applyInputSourceMaps | true |
Compose input source maps into output source map |
assumeFunctionWrapper | false |
Enable additional optimizations based on the assumption that the output will be wrapped with a function wrapper. This flag is used to indicate that "global" declarations will not actually be global but instead isolated to the compilation unit. This enables additional optimizations. |
compilationLevel | SIMPLE |
Specifies the compilation level to use: WHITESPACE_ONLY , SIMPLE , ADVANCED |
env | BROWSER |
Determines the set of builtin externs to load: BROWSER , CUSTOM |
languageIn | ES6 |
Sets what language spec that input sources conform to. |
languageOut | ES5 |
Sets what language spec the output should conform to. |
newTypeInf | false |
Checks for type errors using the new type inference algorithm. |
outputWrapper | null |
Interpolate output into this string, replacing the token %output% |
processCommonJsModules | false |
Process CommonJS modules to a concatenable form, i.e., support require statements. |
rewritePolyfills | false |
Rewrite ES6 library calls to use polyfills provided by the compiler's runtime. |
warningLevel | DEFAULT |
Specifies the warning level to use: QUIET , DEFAULT , VERBOSE |
jsCode | [] |
Specifies the source code to compile. |
externs | [] |
Additional externs to use for this compile. |
createSourceMap | false |
Generates a source map mapping the generated source file back to its original sources. |
Language | Option name | Input | Output |
---|---|---|---|
ES3 | ECMASCRIPT3 |
β | β |
ES5 | ECMASCRIPT5 |
β | β |
ES5 | ECMASCRIPT5_STRICT |
β | β |
ES2015 | ECMASCRIPT6 |
β | |
ES2015 | ECMASCRIPT6_STRICT |
β | |
ES2015 | ECMASCRIPT6_TYPED |
β | β |
Extern is a JavaScript file which describes an interface of the external code. If you are going to use external function, you should declare a function with the same name, but without its body. In case when it is an object β declare an object with the same name and describe its shape by provinding property names.
Once you have all of required externs, they should be passed to compiler using --externs
flag per extern file, or as a value to externs
property in compiler configuration if you are using a tool on top of the compiler.
input
// `moment` is declared in global scope
window.moment().subtract(10, 'days').calendar();
extern
function moment() {}
moment.prototype = {
subtract: function() {},
calendar: function() {}
};
output
// `moment` and its prototype methods was not renamed
window.moment().subtract(10, 'days').calendar();
Externs can be generated for most libraries, see JavaScript Externs Generator.
If you are building a library and not using JavaScript modules, you can export functions and variables safely using bracket notation and quoted property names, since Closure Compiler doesn't rename strings.
input
function logger(x) {
console.log('LOG:', x);
}
window['logger'] = logger;
output
// `logger` function is exported properly into global scope
window.logger = function(a) {
console.log('LOG:', a);
};
Code splitting is a technique used to reduce initial script loading time by splitting a program into a number of bundles that are loaded later. This is especially important for modern web apps. With code splitting you could have a separate bundle for every route in a program, so initially browser will load only minimal amount of code to run and show requested view to a user and other code can be lazy-loaded later.