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