andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 1 | # Closure Compilation |
| 2 | |
| 3 | ## I just need to fix the compile! |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 4 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 5 | ### Pre-requisites |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 6 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 7 | You'll need Java 7 (preferably the OpenJDK version). To install on Ubuntu: |
dbeam | 707cf27 | 2016-03-10 04:12:13 | [diff] [blame] | 8 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 9 | ```shell |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 10 | sudo apt-get install openjdk-7-jre |
dbeam | e3d03b7c | 2016-02-06 03:08:24 | [diff] [blame] | 11 | ``` |
| 12 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 13 | On Mac or Windows, visit: |
| 14 | [http://www.oracle.com/technetwork/java/javase/downloads/index.html](http://www.oracle.com/technetwork/java/javase/downloads/index.html) |
dbeam | e3d03b7c | 2016-02-06 03:08:24 | [diff] [blame] | 15 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 16 | ### Using ninja to compile the code |
| 17 | |
| 18 | We use GYP and ninja as our build system. To generate the ninja files from GYP: |
dbeam | 707cf27 | 2016-03-10 04:12:13 | [diff] [blame] | 19 | |
dbeam | e3d03b7c | 2016-02-06 03:08:24 | [diff] [blame] | 20 | ```shell |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 21 | # notice the 2 in compiled_resources2.gyp |
dbeam | e3d03b7c | 2016-02-06 03:08:24 | [diff] [blame] | 22 | GYP_GENERATORS=ninja tools/gyp/gyp --depth . third_party/closure_compiler/compiled_resources2.gyp |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 23 | ``` |
| 24 | |
| 25 | To compile the JavaScript: |
dbeam | 707cf27 | 2016-03-10 04:12:13 | [diff] [blame] | 26 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 27 | ```shell |
| 28 | ninja -C out/Default -j4 |
| 29 | ``` |
| 30 | |
| 31 | The output should look something like this: |
dbeam | 707cf27 | 2016-03-10 04:12:13 | [diff] [blame] | 32 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 33 | ```shell |
| 34 | ninja: Entering directory `out/Default/' |
| 35 | [30/106] ACTION Compiling chrome/browser/resources/md_history/constants.js |
| 36 | ``` |
| 37 | |
| 38 | To generate and run the **deprecated** v1 gyp format, remove the "2" from "compiled_resources2.gyp": |
dbeam | 707cf27 | 2016-03-10 04:12:13 | [diff] [blame] | 39 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 40 | ```shell |
| 41 | $ GYP_GENERATORS=ninja tools/gyp/gyp --depth . third_party/closure_compiler/compiled_resources.gyp |
| 42 | ``` |
| 43 | |
| 44 | Compiling works the same way for both v1 and v2 systems: |
dbeam | 707cf27 | 2016-03-10 04:12:13 | [diff] [blame] | 45 | |
dbeam | 43f31dd | 2016-03-09 22:05:59 | [diff] [blame] | 46 | ```shell |
| 47 | ninja -C out/Default -j4 |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 48 | ``` |
| 49 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 50 | ## Background |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 51 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 52 | In C++ and Java, compiling the code gives you _some_ level of protection against |
| 53 | misusing variables based on their type information. JavaScript is loosely typed |
| 54 | and therefore doesn't offer this safety. This makes writing JavaScript more |
| 55 | error prone as it's _one more thing_ to mess up. |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 56 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 57 | Because having this safety is handy, Chrome now has a way to optionally |
| 58 | typecheck your JavaScript and produce compiled output with |
| 59 | [Closure Compiler](https://developers.google.com/closure/compiler/). |
dschuyler | 63043614 | 2015-09-22 05:10:16 | [diff] [blame] | 60 | The type information is |
| 61 | [annotated in comment tags](https://developers.google.com/closure/compiler/docs/js-for-compiler) |
| 62 | that are briefly described below. |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 63 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 64 | See also: |
| 65 | [the design doc](https://docs.google.com/a/chromium.org/document/d/1Ee9ggmp6U-lM-w9WmxN5cSLkK9B5YAq14939Woo-JY0/edit). |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 66 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 67 | ## Assumptions |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 68 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 69 | A working Chrome checkout. See here: |
tfarina | 5f2d8e2f | 2016-03-04 09:57:33 | [diff] [blame] | 70 | https://www.chromium.org/developers/how-tos/get-the-code |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 71 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 72 | ## Typechecking Your Javascript |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 73 | |
| 74 | So you'd like to compile your JavaScript! |
| 75 | |
| 76 | Maybe you're working on a page that looks like this: |
| 77 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 78 | ```html |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 79 | <script src="other_file.js"></script> |
| 80 | <script src="my_product/my_file.js"></script> |
| 81 | ``` |
| 82 | |
| 83 | Where `other_file.js` contains: |
| 84 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 85 | ```javascript |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 86 | var wit = 100; |
| 87 | |
| 88 | // ... later on, sneakily ... |
| 89 | |
| 90 | wit += ' IQ'; // '100 IQ' |
| 91 | ``` |
| 92 | |
| 93 | and `src/my_product/my_file.js` contains: |
| 94 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 95 | ```javascript |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 96 | /** @type {number} */ var mensa = wit + 50; |
| 97 | alert(mensa); // '100 IQ50' instead of 150 |
| 98 | ``` |
| 99 | |
| 100 | In order to check that our code acts as we'd expect, we can create a |
| 101 | |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 102 | my_project/compiled_resources2.gyp |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 103 | |
| 104 | with the contents: |
| 105 | |
| 106 | ``` |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 107 | # Copyright 2016 The Chromium Authors. All rights reserved. |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 108 | # Use of this source code is governed by a BSD-style license that can be |
| 109 | # found in the LICENSE file. |
| 110 | { |
| 111 | 'targets': [ |
| 112 | { |
| 113 | 'target_name': 'my_file', # file name without ".js" |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 114 | 'dependencies': [ # No need to specify empty lists. |
| 115 | '../compiled_resources2.gyp:other_file', |
| 116 | '<(EXTERNS_GYP):any_needed_externs' # e.g. chrome.send(), chrome.app.window, etc. |
| 117 | ], |
| 118 | 'includes': ['../third_party/closure_compiler/compile_js2.gypi'], |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 119 | }, |
| 120 | ], |
| 121 | } |
| 122 | ``` |
| 123 | |
| 124 | You should get results like: |
| 125 | |
| 126 | ``` |
| 127 | (ERROR) Error in: my_project/my_file.js |
| 128 | ## /my/home/chromium/src/my_project/my_file.js:1: ERROR - initializing variable |
| 129 | ## found : string |
| 130 | ## required: number |
| 131 | ## /** @type {number} */ var mensa = wit + 50; |
| 132 | ## ^ |
| 133 | ``` |
| 134 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 135 | Yay! We can easily find our unexpected type errors and write less error-prone |
| 136 | code! |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 137 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 138 | ## Continuous Checking |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 139 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 140 | To compile your code on every commit, add a line to |
| 141 | /third_party/closure_compiler/compiled_resources.gyp |
| 142 | like this: |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 143 | |
| 144 | ``` |
| 145 | { |
| 146 | 'targets': [ |
| 147 | { |
| 148 | 'target_name': 'compile_all_resources', |
| 149 | 'dependencies': [ |
| 150 | # ... other projects ... |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 151 | ++ '../my_project/compiled_resources2.gyp:*', |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 152 | ], |
| 153 | } |
| 154 | ] |
| 155 | } |
| 156 | ``` |
| 157 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 158 | and the |
| 159 | [Closure compiler bot](http://build.chromium.org/p/chromium.fyi/builders/Closure%20Compilation%20Linux) |
| 160 | will [re-]compile your code whenever relevant .js files change. |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 161 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 162 | ## Using Compiled JavaScript |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 163 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 164 | Compiled JavaScript is output in |
| 165 | `src/out/<Debug|Release>/gen/closure/my_project/my_file.js` along with a source |
| 166 | map for use in debugging. In order to use the compiled JavaScript, we can create |
| 167 | a |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 168 | |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 169 | my_project/my_project_resources.gyp |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 170 | |
| 171 | with the contents: |
| 172 | |
| 173 | ``` |
| 174 | # Copyright 2015 The Chromium Authors. All rights reserved. |
| 175 | # Use of this source code is governed by a BSD-style license that can be |
| 176 | # found in the LICENSE file. |
| 177 | |
| 178 | { |
| 179 | 'targets': [ |
| 180 | { |
| 181 | # GN version: //my_project/resources |
| 182 | 'target_name': 'my_project_resources', |
| 183 | 'type': 'none', |
| 184 | 'variables': { |
| 185 | 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/my_project', |
| 186 | 'my_file_gen_js': '<(SHARED_INTERMEDIATE_DIR)/closure/my_project/my_file.js', |
| 187 | }, |
| 188 | 'actions': [ |
| 189 | { |
| 190 | # GN version: //my_project/resources:my_project_resources |
| 191 | 'action_name': 'generate_my_project_resources', |
| 192 | 'variables': { |
| 193 | 'grit_grd_file': 'resources/my_project_resources.grd', |
| 194 | 'grit_additional_defines': [ |
| 195 | '-E', 'my_file_gen_js=<(my_file_gen_js)', |
| 196 | ], |
| 197 | }, |
| 198 | 'includes': [ '../build/grit_action.gypi' ], |
| 199 | }, |
| 200 | ], |
| 201 | 'includes': [ '../build/grit_target.gypi' ], |
| 202 | }, |
| 203 | ], |
| 204 | } |
| 205 | ``` |
| 206 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 207 | The variables can also be defined in an existing .gyp file if appropriate. The |
| 208 | variables can then be used in to create a |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 209 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 210 | my_project/my_project_resources.grd |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 211 | |
| 212 | with the contents: |
| 213 | |
| 214 | ``` |
| 215 | <?xml version="1.0" encoding="utf-8"?> |
| 216 | <grit-part> |
| 217 | <include name="IDR_MY_FILE_GEN_JS" file="${my_file_gen_js}" use_base_dir="false" type="BINDATA" /> |
| 218 | </grit-part> |
| 219 | ``` |
| 220 | |
| 221 | In your C++, the resource can be retrieved like this: |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 222 | |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 223 | ``` |
| 224 | base::string16 my_script = |
| 225 | base::UTF8ToUTF16( |
| 226 | ResourceBundle::GetSharedInstance() |
| 227 | .GetRawDataResource(IDR_MY_FILE_GEN_JS) |
| 228 | .as_string()); |
| 229 | ``` |
| 230 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 231 | ## Debugging Compiled JavaScript |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 232 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 233 | Along with the compiled JavaScript, a source map is created: |
| 234 | `src/out/<Debug|Release>/gen/closure/my_project/my_file.js.map` |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 235 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 236 | Chrome DevTools has built in support for working with source maps: |
| 237 | https://developer.chrome.com/devtools/docs/javascript-debugging#source-maps |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 238 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 239 | In order to use the source map, you must first manually edit the path to the |
| 240 | 'sources' in the .js.map file that was generated. For example, if the source map |
| 241 | looks like this: |
| 242 | |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 243 | ``` |
| 244 | { |
| 245 | "version":3, |
| 246 | "file":"/tmp/gen/test_script.js", |
| 247 | "lineCount":1, |
| 248 | "mappings":"A,aAAA,IAAIA,OAASA,QAAQ,EAAG,CACtBC,KAAA,CAAM,OAAN,CADsB;", |
| 249 | "sources":["/tmp/tmp70_QUi"], |
| 250 | "names":["fooBar","alert"] |
| 251 | } |
| 252 | ``` |
| 253 | |
| 254 | sources should be changed to: |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 255 | |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 256 | ``` |
| 257 | ... |
| 258 | "sources":["/tmp/test_script.js"], |
| 259 | ... |
| 260 | ``` |
| 261 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 262 | In your browser, the source map can be loaded through the Chrome DevTools |
| 263 | context menu that appears when you right click in the compiled JavaScript source |
| 264 | body. A dialog will pop up prompting you for the path to the source map file. |
| 265 | Once the source map is loaded, the uncompiled version of the JavaScript will |
| 266 | appear in the Sources panel on the left. You can set break points in the |
| 267 | uncompiled version to help debug; behind the scenes Chrome will still be running |
| 268 | the compiled version of the JavaScript. |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 269 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 270 | ## Additional Arguments |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 271 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 272 | `compile_js.gypi` accepts an optional `script_args` variable, which passes |
| 273 | additional arguments to `compile.py`, as well as an optional `closure_args` |
| 274 | variable, which passes additional arguments to the closure compiler. You may |
| 275 | also override the `disabled_closure_args` for more strict compilation. |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 276 | |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 277 | For example, if you would like to specify multiple sources, strict compilation, |
| 278 | and an output wrapper, you would create a |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 279 | |
| 280 | ``` |
| 281 | my_project/compiled_resources.gyp |
| 282 | ``` |
| 283 | |
| 284 | with contents similar to this: |
dbeam | 1ff3458 | 2016-03-10 18:30:36 | [diff] [blame^] | 285 | |
andybons | 3322f76 | 2015-08-24 21:37:09 | [diff] [blame] | 286 | ``` |
| 287 | # Copyright 2015 The Chromium Authors. All rights reserved. |
| 288 | # Use of this source code is governed by a BSD-style license that can be |
| 289 | # found in the LICENSE file. |
| 290 | { |
| 291 | 'targets' :[ |
| 292 | { |
| 293 | 'target_name': 'my_file', |
| 294 | 'variables': { |
| 295 | 'source_files': [ |
| 296 | 'my_file.js', |
| 297 | 'my_file2.js', |
| 298 | ], |
| 299 | 'script_args': ['--no-single-file'], # required to process multiple files at once |
| 300 | 'closure_args': [ |
| 301 | 'output_wrapper=\'(function(){%output%})();\'', |
| 302 | 'jscomp_error=reportUnknownTypes', # the following three provide more strict compilation |
| 303 | 'jscomp_error=duplicate', |
| 304 | 'jscomp_error=misplacedTypeAnnotation', |
| 305 | ], |
| 306 | 'disabled_closure_args': [], # remove the disabled closure args for more strict compilation |
| 307 | }, |
| 308 | 'includes': ['../third_party/closure_compiler/compile_js.gypi'], |
| 309 | }, |
| 310 | ], |
| 311 | } |
andybons | 6eaa0c0d | 2015-08-26 20:12:52 | [diff] [blame] | 312 | ``` |