forked from angular/angular.io
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathaot-compiler.jade
500 lines (387 loc) · 18.6 KB
/
aot-compiler.jade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
include ../_util-fns
:marked
This cookbook describes how to radically improve performance by compiling _Ahead of Time_ (AoT)
during a build process.
a#toc
:marked
## Table of Contents
* [Overview](#overview)
* [_Ahead-of-Time_ vs _Just-in-Time_](#aot-jit)
* [Compile with AoT](#compile)
* [Bootstrap](#bootstrap)
* [Tree Shaking](#tree-shaking)
* [Load the bundle](#load)
* [Serve the app](#serve)
* [Source Code](#source-code)
* [Tour of Heroes](#toh)
a#overview
.l-main-section
:marked
## Overview
An Angular application consist largely of components and their HTML templates.
Before the browser can render the application,
the components and templates must be converted to executable JavaScript by the _Angular compiler_.
.l-sub-section
:marked
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo" target="_blank">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.
:marked
You can compile the app in the browser, at runtime, as the application loads, using the **_Just-in-Time_ (JiT) compiler**.
This is the standard development approach shown throughout the documentation.
It's great .. but it has shortcomings.
JiT compilation incurs a runtime performance penalty.
Views take longer to render because of the in-browser compilation step.
The application is bigger because it includes the Angular compiler
and a lot of library code that the application won't actually need.
Bigger apps take longer to transmit and are slower to load.
Compilation can uncover many component-template binding errors.
JiT compilation discovers them at runtime which is later than we'd like.
The **_Ahead-of-Time_ (AoT) compiler** can catch template errors early and improve performance
by compiling at build time as you'll learn in this chapter.
a#aot-jit
.l-main-section
:marked
## _Ahead-of-time_ (AoT) vs _Just-in-time_ (JiT)
There is actually only one Angular compiler. The difference between AoT and JiT is a matter of timing and tooling.
With AoT, the compiler runs once at build time using one set of libraries;
With JiT it runs every time for every user at runtime using a different set of libraries.
### Why do AoT compilation?
*Faster rendering*
With AoT, the browser downloads a pre-compiled version of the application.
The browser loads executable code so it can render the application immediately, without waiting to compile the app first.
*Fewer asynchronous requests*
The compiler _inlines_ external html templates and css style sheets within the application JavaScript,
eliminating separate ajax requests for those source files.
*Smaller Angular framework download size*
There's no need to download the Angular compiler if the app is already compiled.
The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.
*Detect template errors earlier*
The AoT compiler detects and reports template binding errors during the build step
before users can see them.
*Better security*
AoT compiles HTML templates and components into JavaScript files long before they are served to the client.
With no templates to read and no risky client-side HTML or JavaScript evaluation,
there are fewer opportunities for injection attacks.
a#compile
.l-main-section
:marked
## Compile with AoT
### Prepare for offline compilation
Take the <a href='../guide/setup.html'>Setup</a> as a starting point.
A few minor changes to the lone `app.component` lead to these two class and html files:
+makeTabs(
`cb-aot-compiler/ts/app/app.component.html,
cb-aot-compiler/ts/app/app.component.ts`,
null,
`app/app.component.html,
app/app.component.ts`
)(format='.')
:marked
Install a few new npm dependencies with the following command:
code-example(format='.').
npm install @angular/compiler-cli @angular/platform-server --save
:marked
You will run the `ngc` compiler provided in the `@angular/compiler-cli` npm package
instead of the TypeScript compiler (`tsc`).
`ngc` is a drop-in replacement for `tsc` and is configured much the same way.
`ngc` requires its own `tsconfig.json` with AoT-oriented settings.
Copy the original `tsconfig.json` to a file called `tsconfig-aot.json`, then modify it to look as follows.
+makeExample('cb-aot-compiler/ts/tsconfig-aot.json', null, 'tsconfig-aot.json')(format='.')
:marked
The `compilerOptions` section is unchanged except for one property.
**Set the `module` to `es2015`**.
This is important as explained later in the [Tree Shaking](#tree-shaking) section.
What's really new is the `ngc` section at the bottom called `angularCompilerOptions`.
Its `"genDir"` property tells the compiler
to store the compiled output files in a new `aot` folder.
The `"skipMetadataEmit" : true` property prevents the compiler from generating metadata files with the compiled application.
Metadata files are not necessary when targeting TypeScript files, so there is no reason to include them.
### Compiling the application
Initiate AoT compilation from the command line using the previously installed `ngc` compiler by executing:
code-example(format='.').
node_modules/.bin/ngc -p tsconfig-aot.json
.l-sub-section
:marked
Windows users should surround the `ngc` command in double quotes:
code-example(format='.').
"node_modules/.bin/ngc" -p tsconfig-aot.json
:marked
`ngc` expects the `-p` switch to point to a `tsconfig.json` file or a folder containing a `tsconfig.json` file.
After `ngc` completes, look for a collection of _NgFactory_ files in the `aot` folder (the folder specified as `genDir` in `tsconfig-aot.json`).
These factory files are essential to the compiled application.
Each component factory creates an instance of the component at runtime by combining the original class file
and a JavaScript representation of the component's template.
Note that the original component class is still referenced internally by the generated factory.
.l-sub-section
:marked
The curious can open the `aot/app.component.ngfactory.ts` to see the original Angular template syntax
in its intermediate, compiled-to-TypeScript form.
JiT compilation generates these same _NgFactories_ in memory where they are largely invisible.
AoT compilation reveals them as separate, physical files.
:marked
.alert.is-important
:marked
Do not edit the _NgFactories_! Re-compilation replaces these files and all edits will be lost.
a#bootstrap
.l-main-section
:marked
## Bootstrap
The AoT path changes application bootstrapping.
Instead of bootstrapping `AppModule`, you bootstrap the application with the generated module factory, `AppModuleNgFactory`.
Switch from the `platformBrowserDynamic.bootstrap` used in JiT compilation to
`platformBrowser().bootstrapModuleFactory` and pass in the `AppModuleNgFactory`.
Here is AoT bootstrap in `main.ts` next to the familiar JiT version:
+makeTabs(
`cb-aot-compiler/ts/app/main.ts,
cb-aot-compiler/ts/app/main-jit.ts`,
null,
`app/main.ts (AoT),
app/main.ts (JiT)`
)
:marked
Be sure to recompile with `ngc`!
a#tree-shaking
:marked
## Tree Shaking
AoT compilation sets the stage for further optimization through a process called _Tree Shaking_.
A Tree Shaker walks the dependency graph, top to bottom, and _shakes out_ unused code like
dead needles in a Christmas tree.
Tree Shaking can greatly reduce the downloaded size of the application
by removing unused portions of both source and library code.
In fact, most of the reduction in small apps comes from removing unreferenced Angular features.
For example, this demo application doesn't use anything from the `@angular/forms` library.
There is no reason to download Forms-related Angular code and tree shaking ensures that you don't.
Tree Shaking and AoT compilation are separate steps.
Tree Shaking can only target JavaScript code.
AoT compilation converts more of the application to JavaScript,
which in turn makes more of the application "Tree Shakable".
### Rollup
This cookbook illustrates a Tree Shaking utility called _Rollup_.
Rollup statically analyzes the application by following the trail of `import` and `export` statements.
It produces a final code _bundle_ that excludes code that is exported, but never imported.
Rollup can only Tree Shake `ES2015` modules which have `import` and `export` statements.
.l-sub-section
:marked
Recall that `tsconfig-aot.json` is configured to produce `ES2015` modules.
It's not important that the code itself be written with `ES2015` syntax such as `class` and `const`.
What matters is that the code uses ES `import` and `export` statements rather than `require` statements.
:marked
Install the Rollup dependencies with this command:
code-example(format='.').
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev
:marked
Next, create a configuration file (`rollup-config.js`)
in the project root directory to tell Rollup how to process the application.
The cookbook configuration file looks like this.
+makeExample('cb-aot-compiler/ts/rollup-config.js', null, 'rollup-config.js')(format='.')
:marked
It tells Rollup that the app entry point is `app/main.js` .
The `dest` attribute tells Rollup to create a bundle called `build.js` in the `dist` folder.
Then there are plugins.
:marked
### Rollup Plugins
Optional plugins filter and transform the Rollup inputs and output.
*RxJS*
Rollup expects application source code to use `ES2015` modules.
Not all external dependencies are published as `ES2015` modules.
In fact, most are not. Many of them are published as _CommonJS_ modules.
The _RxJs_ observable library is an essential Angular dependency published as an ES5 JavaScript _CommonJS_ module.
Luckily there is a Rollup plugin that modifies _RxJs_
to use the ES `import` and `export` statements that Rollup requires.
Rollup then preserves in the final bundle the parts of `RxJS` referenced by the application.
+makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
:marked
*Minification*
Rollup Tree Shaking reduces code size considerably. Minification makes it smaller still.
This cookbook relies on the _uglify_ Rollup plugin to minify and mangle the code.
+makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
.l-sub-section
:marked
In a production setting, you would also enable gzip on the web server to compress
the code into an even smaller package going over the wire.
:marked
### Run Rollup
Execute the Rollup process with this command:
code-example(format='.').
node_modules/.bin/rollup -c rollup-config.js
.l-sub-section
:marked
Rollup may log many lines with the following warning message:
code-example(format='.', language='bash').
The `this` keyword is equivalent to `undefined` at the top level of an ES module, and has been rewritten
:marked
You can safely ignore these warnings.
a#load
.l-main-section
:marked
## Load the Bundle
Loading the generated application bundle does not require a module loader like SystemJS.
Remove the scripts that concern SystemJS.
Instead, load the bundle file using a single `script` tag:
+makeExample('cb-aot-compiler/ts/index.html','bundle','index.html (load bundle)')(format='.')
a#serve
.l-main-section
:marked
## Serve the app
You'll need a web server to host the application.
Use the same _Lite Server_ employed elsewhere in the documentation:
code-example(format='.').
npm run lite
:marked
The server starts, launches a browser, and the app should appear.
a#source-code
.l-main-section
:marked
## AoT QuickStart Source Code
Here's the pertinent source code:
+makeTabs(
`cb-aot-compiler/ts/app/app.component.html,
cb-aot-compiler/ts/app/app.component.ts,
cb-aot-compiler/ts/app/main.ts,
cb-aot-compiler/ts/index.html,
cb-aot-compiler/ts/tsconfig-aot.json,
cb-aot-compiler/ts/rollup-config.js`,
null,
`app/app.component.html,
app/app.component.ts,
app/main.ts,
index.html,
tsconfig-aot.json,
rollup-config.js`
)
a#toh
.l-main-section
:marked
## Tour of Heroes
The sample above is a trivial variation of the QuickStart app.
In this section you apply what you've learned about AoT compilation and Tree Shaking
to an app with more substance, the tutorial [_Tour of Heroes_](../tutorial/toh-pt6.html).
### JiT in development, AoT in production
Today AoT compilation and Tree Shaking take more time than is practical for development. That will change soon.
For now, it's best to JiT compile in development and switch to AoT compilation before deploying to production.
Fortunately, the source code can be compiled either way without change _if_ you account for a few key differences.
***index.html***
The JiT and AoT apps require their own `index.html` files because they setup and launch so differently.
**Put the AoT version in the `/aot` folder** because two `index.html` files can't be in the same folder.
Here they are for comparison:
+makeTabs(
`toh-6/ts/aot/index.html,
toh-6/ts/index.html`,
null,
`aot/index.html (AoT),
index.html (JiT)`
)
:marked
The JiT version relies on `SystemJS` to load individual modules and requires the `reflect-metadata` shim.
Both scripts appear in its `index.html`.
The AoT version loads the entire application in a single script, `aot/dist/build.js`.
It does not need `SystemJS` or the `reflect-metadata` shim; those scripts are absent from its `index.html`
***main.ts***
JiT and AoT applications boot in much the same way but require different Angular libraries to do so.
The key differences, covered in the [Bootstrap](#bootstrap) section above,
are evident in these `main` files which can and should reside in the same folder:
+makeTabs(
`toh-6/ts/app/main-aot.ts,
toh-6/ts/app/main.ts`,
null,
`app/main-aot.ts (AoT),
app/main.ts (JiT)`
)
:marked
***Component-relative Template URLS***
The AoT compiler requires that `@Component` URLS for external templates and css files be _component-relative_.
That means that the value of `@Component.templateUrl` is a URL value _relative_ to the component class file.
For example, a `'hero.component.html'` URL means that the template file is a sibling of its companion `hero.component.ts` file.
While JiT app URLs are more flexible, stick with _component-relative_ URLs for compatibility with AoT compilation.
JiT-compiled applications that use the SystemJS loader and _component-relative_ URLs *must set the* `@Component.moduleId` *property to* `module.id`.
The `module` object is undefined when an AoT-compiled app runs.
The app fails with a null reference error unless you assign a global `module` value in the `index.html` like this:
+makeExample('toh-6/ts/aot/index.html','moduleId')(format='.')
.l-sub-section
:marked
Setting a global `module` is a temporary expedient.
:marked
***TypeScript configuration***
JiT-compiled applications transpile to `commonjs` modules.
AoT-compiled applications transpile to _ES2015_/_ES6_ modules to facilitate Tree Shaking.
AoT requires its own TypeScript configuration settings as well.
You'll need separate TypeScript configuration files such as these:
+makeTabs(
`toh-6/ts/tsconfig-aot.json,
toh-6/ts/tsconfig.json`,
null,
`tsconfig-aot.json (AoT),
tsconfig.json (JiT)`
)
.callout.is-helpful
header @Types and node modules
:marked
In the file structure of _this particular sample project_,
the `node_modules` folder happens to be two levels up from the project root.
Therefore, `"typeRoots"` must be set to `"../../node_modules/@types/"`.
In a more typical project, `node_modules` would be a sibling of `tsconfig-aot.json`
and `"typeRoots"` would be set to `"node_modules/@types/"`.
Edit your `tsconfig-aot.json` to fit your project's file structure.
:marked
### Tree Shaking
Rollup does the Tree Shaking as before.
+makeExample('toh-6/ts/rollup-config.js',null,'rollup-config.js')(format='.')
:marked
### Running the application
.alert.is-important
:marked
The general audience instructions for running the AoT build of the Tour of Heroes app are not ready.
The following instructions presuppose that you have cloned the
<a href="https://github.com/angular/angular.io" target="_blank">angular.io</a>
github repository and prepared it for development as explained in the repo's README.md.
The _Tour of Heroes_ source code is in the `public/docs/_examples/toh-6/ts` folder.
:marked
Run the JiT-compiled app with `npm start` as for all other JiT examples.
Compiling with AoT presupposes certain supporting files, most of them discussed above.
+makeTabs(
`toh-6/ts/aot/index.html,
toh-6/ts/aot/bs-config.json,
toh-6/ts/copy-dist-files.js,
toh-6/ts/rollup-config.js,
toh-6/ts/tsconfig-aot.json`,
null,
`aot/index.html,
aot/bs-config.json,
copy-dist-files.js,
rollup-config.js,
tsconfig-aot.json`)
:marked
Extend the `scripts` section of the `package.json` with these npm scripts:
code-example(format='.').
"build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js",
"lite:aot": "lite-server -c aot/bs-config.json",
:marked
Copy the AoT distribution files into the `/aot` folder with the node script:
code-example(format='.').
node copy-dist-files
.l-sub-section
:marked
You won't do that again until there are updates to `zone.js` or the `core-js` shim for old browsers.
:marked
Now AoT-compile the app and launch it with the `lite` server:
code-example(format='.').
npm run build:aot && npm run lite:aot
:marked
### Inspect the Bundle
It's fascinating to see what the generated JavaScript bundle looks like after Rollup.
The code is minified, so you won't learn much from inspecting the bundle directly.
But the <a href="https://github.com/danvk/source-map-explorer/blob/master/README.md" target="_blank">source-map-explorer</a>
tool can be quite revealing.
Install it:
code-example(format='.').
npm install source-map-explorer --save-dev
:marked
Run the following command to generate the map.
code-example(format='.').
node_modules/.bin/source-map-explorer aot/dist/build.js
:marked
The `source-map-explorer` analyzes the source map generated with the bundle and draws a map of all dependencies,
showing exactly which application and Angular modules and classes are included in the bundle.
Here's the map for _Tour of Heroes_.
a(href="/resources/images/cookbooks/aot-compiler/toh6-bundle.png", target="_blank", title="View larger image")
figure.image-display
img(src="/resources/images/cookbooks/aot-compiler/toh6-bundle.png" alt="TOH-6-bundle")