summaryrefslogtreecommitdiff
path: root/chromium/docs/closure_compilation.md
blob: 81a002be57dd7dcb3ce49b298b2309e281b1f8c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# Closure Compilation

## What is type safety?

[Strongly-typed languages](https://en.wikipedia.org/wiki/Strong_and_weak_typing)
like C++ and Java have the notion of variable types.

This is typically baked into how you declare variables:

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

or as [templates](https://en.wikipedia.org/wiki/Template_metaprogramming) for
containers or generics:

```c++
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](https://developers.google.com/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](https://developers.google.com/closure/compiler/docs/js-for-compiler)

```js
/**
 * @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](https://docs.google.com/a/chromium.org/document/d/1Ee9ggmp6U-lM-w9WmxN5cSLkK9B5YAq14939Woo-JY0/edit).

## 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`:

```javascript
var wit = 100;

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

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

`ui/makes_things_pretty.js`:

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

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

Closure compiler can notify us if we're using `string`s and `number`s 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](http://www.oracle.com/technetwork/java/javase/downloads/index.html) and a
[Chrome checkout](https://www.chromium.org/developers/how-tos/get-the-code) (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:

```shell
third_party/closure_compiler/run_compiler
```

and should see output like this:

```shell
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:

```shell
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](https://build.chromium.org/p/tryserver.chromium.linux/builders/closure_compilation)
which can check whether you could *would* break the build if it was committed.

From the command line, you try your change with:

```shell
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](https://build.chromium.org/p/chromium.fyi/builders/Closure%20Compilation%20Linux)
to automatically compile your code on every commit.

## Externs

[Externs files](https://github.com/google/closure-compiler/wiki/FAQ#how-do-i-write-an-externs-file)
define APIs external to your JavaScript. They provide the compiler with the type
information needed to check usage of these APIs in your JavaScript, much like
forward declarations do in C++.

Third-party libraries like Polymer often provide externs. Chrome must also
provide externs for its extension APIs. Whenever an extension API's `idl` or
`json` schema is updated in Chrome, the corresponding externs file must be
regenerated:

```shell
./tools/json_schema_compiler/compiler.py -g externs \
  extensions/common/api/your_api_here.idl \
  > third_party/closure_compiler/externs/your_api_here.js
```