Closure Compilation

What is type safety?

Strongly-typed languages like C++ and Java have the notion of variable types.

This is typically baked into how you declare variables:

const int32 kUniversalAnswer = 42;  // type = 32-bit integer

or as templates for containers or generics:

std::vector<int64> fibonacci_numbers;  // a vector of 64-bit integers

When differently-typed variables interact with each other, the compiler can warn you if there's no sane default action to take.

Typing can also be manually annotated via mechanisms like dynamic_cast and static_cast or older C-style casts (i.e. (Type)).

Using stongly-typed languages provide some level of protection against accidentally using variables in the wrong context.

JavaScript is weakly-typed and doesn't offer this safety by default. This makes writing JavaScript more error prone, and various type errors have resulted in real bugs seen by many users.

Chrome's solution to typechecking JavaScript

Enter Closure Compiler, a tool for analyzing JavaScript and checking for syntax errors, variable references, and other common JavaScript pitfalls.

To get the fullest type safety possible, it's often required to annotate your JavaScript explicitly with Closure-flavored @jsdoc tags

/**
 * @param {string} version A software version number (i.e. "50.0.2661.94").
 * @return {!Array<number>} Numbers corresponding to |version| (i.e. [50, 0, 2661, 94]).
 */
function versionSplit(version) {
  return version.split('.').map(Number);
}

See also: the design doc.

Typechecking Your Javascript

Given an example file structure of:

  • lib/does_the_hard_stuff.js
  • ui/makes_things_pretty.js

lib/does_the_hard_stuff.js:

var wit = 100;

// ... later on, sneakily ...

wit += ' IQ';  // '100 IQ'

ui/makes_things_pretty.js:

/** @type {number} */ var mensa = wit + 50;

alert(mensa);  // '100 IQ50' instead of 150

Closure compiler can notify us if we're using strings and numbers in dangerous ways.

To do this, we can create:

  • ui/compiled_resources2.gyp

With these contents:

# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
{
  'targets': [
    {
      # Target names is typically just without ".js"
      'target_name': 'makes_things_pretty',

      'dependencies': [
        '../lib/compiled_resources2.gyp:does_the_hard_stuff',

        # Teaches closure about non-standard environments/APIs, e.g.
        # chrome.send(), chrome.app.window, etc.
        '<(EXTERNS_GYP):extern_name_goes_here'
      ],

      'includes': ['../path/to/third_party/closure_compiler/compile_js2.gypi'],
    },
  ],
}

Running Closure compiler locally

You can locally test that your code compiles on Linux or Mac. This requires Java and a Chrome checkout (i.e. python, depot_tools). Note: on Ubuntu, you can probably just run sudo apt-get install openjdk-7-jre.

Now you should be able to run:

third_party/closure_compiler/run_compiler

and should see output like this:

ninja: Entering directory `out/Default/'
[0/1] ACTION Compiling ui/makes_things_pretty.js

To compile only a specific target, add an argument after the script name:

third_party/closure_compiler/run_compiler makes_things_pretty

In our example code, this error should appear:

(ERROR) Error in: ui/makes_things_pretty.js
## /my/home/chromium/src/ui/makes_things_pretty.js:1: ERROR - initializing variable
## found   : string
## required: number
## /** @type {number} */ var mensa = wit + 50;
##                                   ^

Hooray! We can catch type errors in JavaScript!

Trying your change

Closure compilation also has try bots which can check whether you could would break the build if it was committed.

From the command line, you try your change with:

git cl try -b closure_compilation

To automatically check that your code typechecks cleanly before submitting, you can add this line to your CL description:

CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:closure_compilation

Working in common resource directories in Chrome automatically adds this line for you.

Integrating with the continuous build

To compile your code on every commit, add your file to the 'dependencies' list in src/third_party/closure_compiler/compiled_resources2.gyp:

{
  'targets': [
    {
      'target_name': 'compile_all_resources',
      'dependencies': [
         # ... other projects ...
++       '../my_project/compiled_resources2.gyp:*',
      ],
    }
  ]
}

This file is used by the Closure compiler bot to automatically compile your code on every commit.