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