docs(toh-6-aot): add aot to toh-6 (#2496)

* docs(toh-6-aot): add aot to toh-6

s

s

s

s

s

* docs(toh-6-aot): add aot to toh-6

* docs(toh-6-aot): make aot e2e tests run in the same project
Updates tooling for this purpose and also the prose.
This commit is contained in:
Torgeir Helgevold 2016-10-12 04:44:49 -04:00 committed by Ward Bell
parent 2e17d587de
commit cdfe957ab8
21 changed files with 335 additions and 32 deletions

View File

@ -296,7 +296,12 @@ function runE2eTsTests(appDir, outputFile) {
var appBuildSpawnInfo = spawnExt('npm', ['run', config.build], { cwd: appDir });
var appRunSpawnInfo = spawnExt('npm', ['run', config.run, '--', '-s'], { cwd: appDir });
return runProtractor(appBuildSpawnInfo.promise, appDir, appRunSpawnInfo, outputFile);
var run = runProtractor(appBuildSpawnInfo.promise, appDir, appRunSpawnInfo, outputFile);
if (fs.existsSync(appDir + '/aot/index.html')) {
run = run.then(() => runProtractorAoT(appDir, outputFile));
}
return run;
}
function runProtractor(prepPromise, appDir, appRunSpawnInfo, outputFile) {
@ -341,6 +346,20 @@ function runProtractor(prepPromise, appDir, appRunSpawnInfo, outputFile) {
}
}
function runProtractorAoT(appDir, outputFile) {
fs.appendFileSync(outputFile, '++ AoT version ++\n');
var aotBuildSpawnInfo = spawnExt('npm', ['run', 'build:aot'], { cwd: appDir });
var promise = aotBuildSpawnInfo.promise;
var copyFileCmd = 'copy-dist-files.js';
if (fs.existsSync(appDir + '/' + copyFileCmd)) {
promise = promise.then(() =>
spawnExt('node', [copyFileCmd], { cwd: appDir }).promise );
}
var aotRunSpawnInfo = spawnExt('npm', ['run', 'http-server:e2e', 'aot', '--', '-s'], { cwd: appDir });
return runProtractor(promise, appDir, aotRunSpawnInfo, outputFile);
}
// start the server in appDir/build/web; then run protractor with the specified
// fileName; then shut down the example. All protractor output is appended
// to the outputFile.
@ -883,7 +902,7 @@ function harpCompile() {
gutil.log("NODE_ENV: " + process.env.NODE_ENV);
if(argv.page) harpJsonSetJade2NgTo(true);
if(skipLangs && fs.existsSync(WWW) && backupApiHtmlFilesExist(WWW)) {
gutil.log(`Harp site recompile: skipping recompilation of API docs for [${skipLangs}]`);
gutil.log(`API docs will be copied from existing ${WWW} folder.`)

View File

@ -9,6 +9,7 @@
"http-server:e2e": "http-server",
"http-server:cli": "http-server dist/",
"lite": "lite-server",
"lite:aot": "lite-server -c aot/bs-config.json",
"postinstall": "typings install",
"test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
"tsc": "tsc",
@ -17,7 +18,8 @@
"test:webpack": "karma start karma.webpack.conf.js",
"build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail",
"build:cli": "ng build",
"build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup.js",
"build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js",
"copy-dist-files": "node ./copy-dist-files.js",
"i18n": "ng-xi18n"
},
"keywords": [],

View File

@ -10,5 +10,9 @@
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"types": []
}
},
"exclude": [
"node_modules/*",
"**/*-aot.ts"
]
}

View File

@ -2,4 +2,4 @@
**/*.metadata.json
dist
!app/tsconfig.json
!rollup.js
!rollup-config.js

View File

@ -13,5 +13,4 @@ export class AppComponent {
toggleHeading() {
this.showHeading = !this.showHeading;
}
}

View File

@ -9,7 +9,6 @@
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
</head>

View File

@ -74,6 +74,7 @@
"raw-loader": "^0.5.1",
"rimraf": "^2.5.4",
"rollup-plugin-commonjs": "^4.1.0",
"source-map-explorer": "^1.3.2",
"style-loader": "^0.13.1",
"ts-loader": "^0.8.2",
"ts-node": "^1.3.0",

View File

@ -1 +1,3 @@
**/*.js
aot/**/*.ts
!rollup-config.js

View File

@ -0,0 +1,5 @@
{
"port": 8000,
"files": ["./aot/**/*.{html,htm,css,js}"],
"server": { "baseDir": "./aot" }
}

View File

@ -0,0 +1,20 @@
<!-- #docregion -->
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Angular Tour of Heroes</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="shim.min.js"></script>
<script src="zone.min.js"></script>
<!-- #docregion moduleId -->
<script>window.module = 'aot';</script>
<!-- #enddocregion moduleId -->
</head>
<body>
<my-app>Loading...</my-app>
</body>
<script src="dist/build.js"></script>
</html>

View File

@ -5,7 +5,6 @@ import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<nav>

View File

@ -0,0 +1,6 @@
// #docregion
import { platformBrowser } from '@angular/platform-browser';
import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory';
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

View File

@ -0,0 +1,11 @@
// #docregion
var fs = require('fs');
var resources = [
'node_modules/core-js/client/shim.min.js',
'node_modules/zone.js/dist/zone.min.js'
];
resources.map(function(f) {
var path = f.split('/');
var t = 'aot/' + path[path.length-1];
fs.createReadStream(f).pipe(fs.createWriteStream(t));
});

View File

@ -1,3 +1,4 @@
<!-- #docregion -->
<!DOCTYPE html>
<html>
<head>

View File

@ -0,0 +1,24 @@
// #docregion
import rollup from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify'
//paths are relative to the execution path
export default {
entry: 'app/main-aot.js',
dest: 'aot/dist/build.js', // output a single application bundle
sourceMap: true,
sourceMapFile: 'aot/dist/build.js.map',
format: 'iife',
plugins: [
nodeResolve({jsnext: true, module: true}),
commonjs({
include: [
'node_modules/rxjs/**',
'node_modules/angular-in-memory-web-api/**'
],
}),
uglify()
]
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"types": []
},
"files": [
"app/app.module.ts",
"app/main-aot.ts",
"typings/index.d.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"skipMetadataEmit" : true
}
}

View File

@ -21,4 +21,4 @@
"include": [
"*/e2e-spec.ts"
]
}
}

View File

@ -15,20 +15,23 @@ a#toc
* [Load the bundle](#load)
* [Serve the app](#serve)
* [Source Code](#source-code)
* [Tour of Heroes](#toh)
a#overview
.l-main-section
:marked
## Overview
Angular component templates consist of a mix of standard html and Angular syntax (e.g. `ngIf`, `ngFor`).
Expressions like `ngIf` and `ngFor` are specific to Angular. The browser cannot execute them directly.
Before the browser can render the application, Angular components and their templates must be converted to executable JavaScript.
We refer to this step as _Angular compilation_ or just plain _compilation_.
You can compile the app in the browser, at runtime, as the application loads, using the _Just-in-Time_ (JiT) compiler.
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.
@ -36,8 +39,11 @@ a#overview
and a lot of library code that the application won't actually need.
Bigger apps take longer to transmit and are slower to load.
This cookbook describes how to improve performance by compiling at build time instead,
using a process called _Ahead-of-Time_ compilation (AoT).
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
@ -51,8 +57,6 @@ a#aot-jit
### Why do AoT compilation?
The performance improvement from doing AoT compilation can be significant for three reasons:
*Faster rendering*
With AoT, the browser downloads a pre-compiled version of the application.
@ -68,6 +72,19 @@ a#aot-jit
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
@ -75,7 +92,7 @@ a#compile
### Prepare for offline compilation
This cookbook takes the <a href='/docs/ts/latest/quickstart.html'>QuickStart</a> as its starting point.
Take the <a href='/docs/ts/latest/quickstart.html'>QuickStart</a> as a starting point.
A few minor changes to the lone `app.component` lead to these two class and html files:
+makeTabs(
@ -84,7 +101,7 @@ a#compile
null,
`app/app.component.html,
app/app.component.ts`
)
)(format='.')
:marked
Install a few new npm dependencies with the following command:
@ -118,6 +135,11 @@ code-example(format='.').
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.
@ -203,10 +225,11 @@ a#tree-shaking
code-example(format='.').
npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev
:marked
Next, create a configuration file to tell Rollup how to process the application.
The configuration file in this cookbook is named `rollup.js` and looks like this.
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.js', null, 'rollup.js')(format='.')
+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.
@ -228,7 +251,7 @@ code-example(format='.').
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.js','commonjs','rollup.js (CommonJs to ES2015 Plugin)')(format='.')
+makeExample('cb-aot-compiler/ts/rollup-config.js','commonjs','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
:marked
*Minification*
@ -236,7 +259,7 @@ code-example(format='.').
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.js','uglify','rollup.js (CommonJs to ES2015 Plugin)')(format='.')
+makeExample('cb-aot-compiler/ts/rollup-config.js','uglify','rollup-config.js (CommonJs to ES2015 Plugin)')(format='.')
.l-sub-section
:marked
@ -247,7 +270,7 @@ code-example(format='.').
### Run Rollup
Execute the Rollup process with this command:
code-example(format='.').
node_modules/.bin/rollup -c rollup.js
node_modules/.bin/rollup -c rollup-config.js
.l-sub-section
:marked
@ -283,21 +306,178 @@ code-example(format='.').
a#source-code
.l-main-section
:marked
## Source Code
## AoT QuickStart Source Code
Here is the pertinent AoT source code for this cookbook:
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.js`,
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.js`
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 are setup and launched so differently that they require their own `index.html` files.
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
They can't be in the same folder.
***Put the AoT version in the `/aot` folder***.
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`
*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:
`foo.component.html` no matter where `foo.component.ts` sits in the project folder structure.
While JiT app URLs are more flexible, stick with _component-relative_ URLs for compatibility with AoT compilation.
JiT-compiled apps, using 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 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 apps transpile to `commonjs` modules.
AoT-compiled apps 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.json (AoT),
tsconfig-aot.json (JiT)`
)
:marked
### Tree Shaking
Rollup does the Tree Shaking as before.
The Rollup configuration changes slightly to accommodate the `angular-in-memory-web-api` module
that the _Tour of Heroes_ app requires for data server simulation.
The `angular-in-memory-web-api` is a `commonjs` module like the RxJS library.
Add `angular-in-memory-web-api` to the _commonjs plugin_ `include` array,
next to the `rxjs` file specification.
+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")

View File

@ -7,6 +7,12 @@ block includes
The Angular documentation is a living document with continuous improvements.
This log calls attention to recent significant changes.
## NEW "Ahead of Time (AoT) Compilation" cookbook (2016-10-11)
The NEW [Ahead of Time (AoT) Compilation](../cookbook/aot-compiler.html) cookbook
explains what AoT compilation is and why you'd want it.
It demonstrates the basics with a QuickStart app
followed by the more advanced considerations of compiling and bundling the Tour of Heroes.
## Sync with Angular v.2.0.2 (2016-10-6)
Docs and code samples updated and tested with Angular v.2.0.2

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB