Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Closure Compilation | 1 # Closure Compilation |
| 2 | 2 |
| 3 ## I just need to fix the compile! | 3 ## What is type safety? |
| 4 | 4 |
| 5 ### Pre-requisites | 5 [Strongly-typed languages](https://en.wikipedia.org/wiki/Strong_and_weak_typing) |
|
groby-ooo-7-16
2016/05/03 05:51:15
Completely unnecessary nitpick of the week: Strict
Dan Beam
2016/05/03 21:24:06
Acknowledged.
| |
| 6 like C++ and Java have the notion of variable types. | |
| 6 | 7 |
| 7 You'll need Java 7 (preferably the OpenJDK version). To install on Ubuntu: | 8 This is typically baked into how you declare variables: |
| 8 | 9 |
| 9 ```shell | 10 ```c++ |
| 10 sudo apt-get install openjdk-7-jre | 11 const int32 kUniversalAnswer = 42; // type = 32-bit integer |
| 11 ``` | 12 ``` |
| 12 | 13 |
| 13 On Mac or Windows, visit: | 14 or as [templates](https://en.wikipedia.org/wiki/Template_metaprogramming) for |
| 14 [http://www.oracle.com/technetwork/java/javase/downloads/index.html](http://www. oracle.com/technetwork/java/javase/downloads/index.html) | 15 containers or generics: |
| 15 | 16 |
| 16 ### Using ninja to compile the code | 17 ```c++ |
| 17 | 18 std::vector<int64> fibonacci_numbers; // a vector of 64-bit integers |
| 18 To compile the JavaScript, run this script: | |
| 19 | |
| 20 ```shell | |
| 21 third_party/closure_compiler/run_compiler | |
| 22 ``` | 19 ``` |
| 23 | 20 |
| 24 The output should look something like this: | 21 When differently-typed variables interact with eachother, the compiler can warn |
|
groby-ooo-7-16
2016/05/03 05:51:15
nit:each[space]other
Dan Beam
2016/05/03 21:24:05
Done.
| |
| 22 you if there's no sane default action to take. | |
| 25 | 23 |
| 26 ```shell | 24 Typing can also be manually annotated via mechanisms like `dynamic_cast` and |
| 27 ninja: Entering directory `out/Default/' | 25 `static_cast` or older C-style casts (i.e. `(Type)`). |
| 28 [30/106] ACTION Compiling chrome/browser/resources/md_history/constants.js | 26 |
| 27 Using stongly-typed languages provide _some_ level of protection against | |
| 28 accidentally using variables in the wrong context. | |
| 29 | |
| 30 JavaScript is loosely-typed and doesn't offer this safety by default. This makes | |
|
groby-ooo-7-16
2016/05/03 05:51:15
weakly-typed? Since that's what you start of with?
Dan Beam
2016/05/03 21:24:06
Done.
| |
| 31 writing JavaScript more error prone, and various type errors have resulted in | |
| 32 real bugs seen by many users. | |
| 33 | |
| 34 ## Chrome's solution to typechecking JavaScript | |
| 35 | |
| 36 Enter [Closure Compiler](https://developers.google.com/closure/compiler/), a | |
| 37 tool for analyzing JavaScript and checking for syntax errors, variable | |
| 38 references, and other common JavaScript pitfalls. | |
| 39 | |
| 40 To get the fullest type safety possible, it's often required to annotate your | |
| 41 JavaScript explicitly with [Closure-flavored @jsdoc | |
| 42 tags](https://developers.google.com/closure/compiler/docs/js-for-compiler) | |
| 43 | |
| 44 ```js | |
| 45 /** | |
| 46 * @param {string} version A software version number (i.e. "50.0.2661.94"). | |
| 47 * @return {!Array<number>} Numbers corresponing to |version| (i.e. [50, 0, 2661 , 94]). | |
| 48 */ | |
| 49 function versionSplit(version) { | |
| 50 return version.split('.').map(Number); | |
| 51 } | |
| 29 ``` | 52 ``` |
| 30 | 53 |
| 31 To compile only a specific target, add an argument after the script name: | |
| 32 | |
| 33 ```shell | |
| 34 third_party/closure_compiler/run_compiler people_page | |
| 35 ``` | |
| 36 | |
| 37 ## Background | |
| 38 | |
| 39 In C++ and Java, compiling the code gives you _some_ level of protection against | |
| 40 misusing variables based on their type information. JavaScript is loosely typed | |
| 41 and therefore doesn't offer this safety. This makes writing JavaScript more | |
| 42 error prone as it's _one more thing_ to mess up. | |
| 43 | |
| 44 Because having this safety is handy, Chrome now has a way to optionally | |
| 45 typecheck your JavaScript and produce compiled output with | |
| 46 [Closure Compiler](https://developers.google.com/closure/compiler/). | |
| 47 The type information is | |
| 48 [annotated in comment tags](https://developers.google.com/closure/compiler/docs/ js-for-compiler) | |
| 49 that are briefly described below. | |
| 50 | |
| 51 See also: | 54 See also: |
| 52 [the design doc](https://docs.google.com/a/chromium.org/document/d/1Ee9ggmp6U-lM -w9WmxN5cSLkK9B5YAq14939Woo-JY0/edit). | 55 [the design doc](https://docs.google.com/a/chromium.org/document/d/1Ee9ggmp6U-lM -w9WmxN5cSLkK9B5YAq14939Woo-JY0/edit). |
| 53 | 56 |
| 54 ## Assumptions | |
| 55 | |
| 56 A working Chrome checkout. See here: | |
| 57 https://www.chromium.org/developers/how-tos/get-the-code | |
| 58 | |
| 59 ## Typechecking Your Javascript | 57 ## Typechecking Your Javascript |
| 60 | 58 |
| 61 So you'd like to compile your JavaScript! | 59 Given an example file structure of: |
| 62 | 60 |
| 63 Maybe you're working on a page that looks like this: | 61 + lib/does_the_hard_stuff.js |
| 62 + ui/makes_things_pretty.js | |
| 64 | 63 |
| 65 ```html | 64 `lib/does_the_hard_stuff.js`: |
| 66 <script src="other_file.js"></script> | |
| 67 <script src="my_product/my_file.js"></script> | |
| 68 ``` | |
| 69 | |
| 70 Where `other_file.js` contains: | |
| 71 | 65 |
| 72 ```javascript | 66 ```javascript |
| 73 var wit = 100; | 67 var wit = 100; |
| 74 | 68 |
| 75 // ... later on, sneakily ... | 69 // ... later on, sneakily ... |
| 76 | 70 |
| 77 wit += ' IQ'; // '100 IQ' | 71 wit += ' IQ'; // '100 IQ' |
| 78 ``` | 72 ``` |
| 79 | 73 |
| 80 and `src/my_product/my_file.js` contains: | 74 `ui/makes_things_pretty.js`: |
| 81 | 75 |
| 82 ```javascript | 76 ```javascript |
| 83 /** @type {number} */ var mensa = wit + 50; | 77 /** @type {number} */ var mensa = wit + 50; |
| 78 | |
| 84 alert(mensa); // '100 IQ50' instead of 150 | 79 alert(mensa); // '100 IQ50' instead of 150 |
| 85 ``` | 80 ``` |
| 86 | 81 |
| 87 In order to check that our code acts as we'd expect, we can create a | 82 Closure compiler can notify us if we're using `string`s and `number`s in |
| 83 dangerous ways. | |
| 88 | 84 |
| 89 my_project/compiled_resources2.gyp | 85 To do this, we can create: |
| 90 | 86 |
| 91 with the contents: | 87 + ui/compiled_resources2.gyp |
| 88 | |
| 89 With these contents: | |
| 92 | 90 |
| 93 ``` | 91 ``` |
| 94 # Copyright 2016 The Chromium Authors. All rights reserved. | 92 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 95 # Use of this source code is governed by a BSD-style license that can be | 93 # Use of this source code is governed by a BSD-style license that can be |
| 96 # found in the LICENSE file. | 94 # found in the LICENSE file. |
| 97 { | 95 { |
| 98 'targets': [ | 96 'targets': [ |
| 99 { | 97 { |
| 100 'target_name': 'my_file', # file name without ".js" | 98 # Target names is typically just without ".js" |
| 101 'dependencies': [ # No need to specify empty lists. | 99 'target_name': 'makes_things_pretty', |
| 102 '../compiled_resources2.gyp:other_file', | 100 |
| 103 '<(EXTERNS_GYP):any_needed_externs' # e.g. chrome.send(), chrome.app.wi ndow, etc. | 101 'dependencies': [ |
| 102 '../lib/compiled_resources2.gyp:does_the_hard_stuff', | |
| 103 | |
| 104 # Teaches closure about non-standard environments/APIs, e.g. | |
| 105 # chrome.send(), chrome.app.window, etc. | |
| 106 '<(EXTERNS_GYP):extern_name_goes_here' | |
| 104 ], | 107 ], |
| 105 'includes': ['../third_party/closure_compiler/compile_js2.gypi'], | 108 |
| 109 'includes': ['../path/to/third_party/closure_compiler/compile_js2.gypi'], | |
| 106 }, | 110 }, |
| 107 ], | 111 ], |
| 108 } | 112 } |
| 109 ``` | 113 ``` |
| 110 | 114 |
| 111 You should get results like: | 115 ## Running Closure compiler locally |
| 116 | |
| 117 You can locally test that your code compiles on Linux or Mac. This requires | |
| 118 [Java](http://www.oracle.com/technetwork/java/javase/downloads/index.html) and a | |
| 119 [Chrome checkout](http://www.chromium.org/developers/how-tos/get-the-code) (i.e. | |
| 120 python, depot_tools). Note: on Ubuntu, you can probably just run `sudo apt-get | |
| 121 install openjdk-7-jre`. | |
| 122 | |
| 123 Now you should be able to run: | |
| 124 | |
| 125 ```shell | |
| 126 third_party/closure_compiler/run_compiler | |
| 127 ``` | |
| 128 | |
| 129 and should see output like this: | |
| 130 | |
| 131 ```shell | |
| 132 ninja: Entering directory `out/Default/' | |
| 133 [0/1] ACTION Compiling ui/makes_things_pretty.js | |
| 134 ``` | |
| 135 | |
| 136 To compile only a specific target, add an argument after the script name: | |
| 137 | |
| 138 ```shell | |
| 139 third_party/closure_compiler/run_compiler makes_things_pretty | |
| 140 ``` | |
| 141 | |
| 142 In our example code, this error should appear: | |
| 112 | 143 |
| 113 ``` | 144 ``` |
| 114 (ERROR) Error in: my_project/my_file.js | 145 (ERROR) Error in: ui/makes_things_pretty.js |
| 115 ## /my/home/chromium/src/my_project/my_file.js:1: ERROR - initializing variable | 146 ## /my/home/chromium/src/ui/makes_things_pretty.js:1: ERROR - initializing varia ble |
| 116 ## found : string | 147 ## found : string |
| 117 ## required: number | 148 ## required: number |
| 118 ## /** @type {number} */ var mensa = wit + 50; | 149 ## /** @type {number} */ var mensa = wit + 50; |
| 119 ## ^ | 150 ## ^ |
| 120 ``` | 151 ``` |
| 121 | 152 |
| 122 Yay! We can easily find our unexpected type errors and write less error-prone | 153 Hooray! We can catch type errors in JavaScript! |
| 123 code! | |
| 124 | 154 |
| 125 ## Continuous Checking | 155 ## Integrating with the continuous build |
| 126 | 156 |
| 127 To compile your code on every commit, add a line to | 157 To compile your code on every commit, add your file to the `'dependencies'` list |
| 128 /third_party/closure_compiler/compiled_resources.gyp | 158 in `src/third_party/closure_compiler/compiled_resources2.gyp`: |
| 129 like this: | |
| 130 | 159 |
| 131 ``` | 160 ``` |
| 132 { | 161 { |
| 133 'targets': [ | 162 'targets': [ |
| 134 { | 163 { |
| 135 'target_name': 'compile_all_resources', | 164 'target_name': 'compile_all_resources', |
| 136 'dependencies': [ | 165 'dependencies': [ |
| 137 # ... other projects ... | 166 # ... other projects ... |
| 138 ++ '../my_project/compiled_resources2.gyp:*', | 167 ++ '../my_project/compiled_resources2.gyp:*', |
| 139 ], | 168 ], |
| 140 } | 169 } |
| 141 ] | 170 ] |
| 142 } | 171 } |
| 143 ``` | 172 ``` |
| 144 | 173 |
| 145 and the | 174 this file is used by the |
|
groby-ooo-7-16
2016/05/03 05:51:15
[T]his...
Dan Beam
2016/05/03 21:24:06
Done.
| |
| 146 [Closure compiler bot](http://build.chromium.org/p/chromium.fyi/builders/Closure %20Compilation%20Linux) | 175 [Closure compiler bot](http://build.chromium.org/p/chromium.fyi/builders/Closure %20Compilation%20Linux) |
| 147 will [re-]compile your code whenever relevant .js files change. | 176 will automatically compile your code on every commit. |
|
groby-ooo-7-16
2016/05/03 05:51:15
And will automatically... or It will automatically
Dan Beam
2016/05/03 21:24:06
Done. (changed to "used by the closure compiler bo
| |
| 148 | 177 |
| 149 ## Using Compiled JavaScript | 178 ## Trying your change |
| 150 | 179 |
| 151 Compiled JavaScript is output in | 180 Closure compilation also has [try |
| 152 `src/out/<Debug|Release>/gen/closure/my_project/my_file.js` along with a source | 181 bots](https://build.chromium.org/p/tryserver.chromium.linux/builders/closure_com pilation) |
| 153 map for use in debugging. In order to use the compiled JavaScript, we can create | 182 which can check whether you could *would* break the build if it was committed. |
| 154 a | |
| 155 | 183 |
| 156 my_project/my_project_resources.gyp | 184 From the command line, you can issue a try job with your current diff by: |
|
groby-ooo-7-16
2016/05/03 05:51:15
s/diff/changes
Dan Beam
2016/05/03 21:24:06
Done. (From the command line, you try your change
| |
| 157 | 185 |
| 158 with the contents: | 186 ```shell |
| 187 git cl try -b closure_compilation | |
| 188 ``` | |
| 189 | |
| 190 To automatically check that your code typechecks cleanly before submitting, you | |
| 191 can add this line to your CL description: | |
| 159 | 192 |
| 160 ``` | 193 ``` |
| 161 # Copyright 2015 The Chromium Authors. All rights reserved. | 194 CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:closure_compilation |
| 162 # Use of this source code is governed by a BSD-style license that can be | |
| 163 # found in the LICENSE file. | |
| 164 | |
| 165 { | |
| 166 'targets': [ | |
| 167 { | |
| 168 # GN version: //my_project/resources | |
| 169 'target_name': 'my_project_resources', | |
| 170 'type': 'none', | |
| 171 'variables': { | |
| 172 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/my_project', | |
| 173 'my_file_gen_js': '<(SHARED_INTERMEDIATE_DIR)/closure/my_project/my_file .js', | |
| 174 }, | |
| 175 'actions': [ | |
| 176 { | |
| 177 # GN version: //my_project/resources:my_project_resources | |
| 178 'action_name': 'generate_my_project_resources', | |
| 179 'variables': { | |
| 180 'grit_grd_file': 'resources/my_project_resources.grd', | |
| 181 'grit_additional_defines': [ | |
| 182 '-E', 'my_file_gen_js=<(my_file_gen_js)', | |
| 183 ], | |
| 184 }, | |
| 185 'includes': [ '../build/grit_action.gypi' ], | |
| 186 }, | |
| 187 ], | |
| 188 'includes': [ '../build/grit_target.gypi' ], | |
| 189 }, | |
| 190 ], | |
| 191 } | |
| 192 ``` | 195 ``` |
| 193 | 196 |
| 194 The variables can also be defined in an existing .gyp file if appropriate. The | 197 Working in common resource directories in Chrome automatically add this line for |
|
groby-ooo-7-16
2016/05/03 05:51:15
add[s]
Dan Beam
2016/05/03 21:24:06
Done.
| |
| 195 variables can then be used in to create a | 198 you. |
| 196 | |
| 197 my_project/my_project_resources.grd | |
| 198 | |
| 199 with the contents: | |
| 200 | |
| 201 ``` | |
| 202 <?xml version="1.0" encoding="utf-8"?> | |
| 203 <grit-part> | |
| 204 <include name="IDR_MY_FILE_GEN_JS" file="${my_file_gen_js}" use_base_dir="fals e" type="BINDATA" /> | |
| 205 </grit-part> | |
| 206 ``` | |
| 207 | |
| 208 In your C++, the resource can be retrieved like this: | |
| 209 | |
| 210 ``` | |
| 211 base::string16 my_script = | |
| 212 base::UTF8ToUTF16( | |
| 213 ResourceBundle::GetSharedInstance() | |
| 214 .GetRawDataResource(IDR_MY_FILE_GEN_JS) | |
| 215 .as_string()); | |
| 216 ``` | |
| 217 | |
| 218 ## Debugging Compiled JavaScript | |
| 219 | |
| 220 Along with the compiled JavaScript, a source map is created: | |
| 221 `src/out/<Debug|Release>/gen/closure/my_project/my_file.js.map` | |
| 222 | |
| 223 Chrome DevTools has built in support for working with source maps: | |
| 224 https://developer.chrome.com/devtools/docs/javascript-debugging#source-maps | |
| 225 | |
| 226 In order to use the source map, you must first manually edit the path to the | |
| 227 'sources' in the .js.map file that was generated. For example, if the source map | |
| 228 looks like this: | |
| 229 | |
| 230 ``` | |
| 231 { | |
| 232 "version":3, | |
| 233 "file":"/tmp/gen/test_script.js", | |
| 234 "lineCount":1, | |
| 235 "mappings":"A,aAAA,IAAIA,OAASA,QAAQ,EAAG,CACtBC,KAAA,CAAM,OAAN,CADsB;", | |
| 236 "sources":["/tmp/tmp70_QUi"], | |
| 237 "names":["fooBar","alert"] | |
| 238 } | |
| 239 ``` | |
| 240 | |
| 241 sources should be changed to: | |
| 242 | |
| 243 ``` | |
| 244 ... | |
| 245 "sources":["/tmp/test_script.js"], | |
| 246 ... | |
| 247 ``` | |
| 248 | |
| 249 In your browser, the source map can be loaded through the Chrome DevTools | |
| 250 context menu that appears when you right click in the compiled JavaScript source | |
| 251 body. A dialog will pop up prompting you for the path to the source map file. | |
| 252 Once the source map is loaded, the uncompiled version of the JavaScript will | |
| 253 appear in the Sources panel on the left. You can set break points in the | |
| 254 uncompiled version to help debug; behind the scenes Chrome will still be running | |
| 255 the compiled version of the JavaScript. | |
| 256 | |
| 257 ## Additional Arguments | |
| 258 | |
| 259 `compile_js.gypi` accepts an optional `script_args` variable, which passes | |
| 260 additional arguments to `compile.py`, as well as an optional `closure_args` | |
| 261 variable, which passes additional arguments to the closure compiler. You may | |
| 262 also override the `disabled_closure_args` for more strict compilation. | |
| 263 | |
| 264 For example, if you would like to specify multiple sources, strict compilation, | |
| 265 and an output wrapper, you would create a | |
| 266 | |
| 267 ``` | |
| 268 my_project/compiled_resources.gyp | |
| 269 ``` | |
| 270 | |
| 271 with contents similar to this: | |
| 272 | |
| 273 ``` | |
| 274 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 275 # Use of this source code is governed by a BSD-style license that can be | |
| 276 # found in the LICENSE file. | |
| 277 { | |
| 278 'targets' :[ | |
| 279 { | |
| 280 'target_name': 'my_file', | |
| 281 'variables': { | |
| 282 'source_files': [ | |
| 283 'my_file.js', | |
| 284 'my_file2.js', | |
| 285 ], | |
| 286 'script_args': ['--no-single-file'], # required to process multiple file s at once | |
| 287 'closure_args': [ | |
| 288 'output_wrapper=\'(function(){%output%})();\'', | |
| 289 'jscomp_error=reportUnknownTypes', # the following three provide m ore strict compilation | |
| 290 'jscomp_error=duplicate', | |
| 291 'jscomp_error=misplacedTypeAnnotation', | |
| 292 ], | |
| 293 'disabled_closure_args': [], # remove the disabled closure args for more strict compilation | |
| 294 }, | |
| 295 'includes': ['../third_party/closure_compiler/compile_js.gypi'], | |
| 296 }, | |
| 297 ], | |
| 298 } | |
| 299 ``` | |
| OLD | NEW |