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
|
# 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/BUILD.gn
With these contents:
```
# Copyright 2018 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.
import("//third_party/closure_compiler/compile_js.gni")
js_type_check("closure_compile") {
deps = [
":make_things_pretty",
]
}
js_library("make_things_pretty") {
deps = [
"../lib:does_the_hard_stuff",
]
externs_list = [
"$externs_path/extern_name_goes_here.js"
]
}
```
## 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`.
After you set closure_compile = true in your gn args, you should be able to run:
```shell
ninja -C out/Default webui_closure_compile
```
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 folder, add an argument after the script name:
```shell
ninja -C out/Default ui:closure_compile
```
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!
## Preferred BUILD.gn structure
* Make all individual JS file targets a js\_library.
* The top level target should be called “closure\_compile”.
* If you have subfolders that need compiling, make “closure\_compile” a group(),
and any files in the current directory a js\_type\_check() called “<directory>\_resources”.
* Otherwise, just make “closure\_compile” a js\_type\_check with all your js\_library targets as deps
* Leave all closure targets below other kinds of targets becaure they’re less ‘important’
See also:
[Closure Compilation with GN](https://docs.google.com/a/chromium.org/document/d/1Ee9ggmp6U-lM-w9WmxN5cSLkK9B5YAq14939Woo-JY0/edit).
## Trying your change
Closure compilation runs in the compile step of Linux, Android and ChromeOS builds.
From the command line, you try your change with:
```shell
git cl try -b linux_chromium_rel_ng
```
## Integrating with the continuous build
To compile your code on every commit, add your file to the
`'webui_closure_compile'` target in `src/BUILD.gn`:
```
group("webui_closure_compile") {
data_deps = [
# Other projects
"my/project:closure_compile",
]
}
```
## 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
```
|