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 |