docs: add universal guide with production client app (#18707)

Revises both universal and client build to use AOT and webpack for both.
Guide text adjusted accordingly
Dodges CLI client build, expected in near future.

PR Close #18707
This commit is contained in:
Ward Bell 2017-09-14 19:47:33 -07:00 committed by Chuck Jazdzewski
parent 0b0d25fa33
commit 555b1cdf29
14 changed files with 1419 additions and 1177 deletions

View File

@ -2,6 +2,7 @@
**/src/environments/environment.prod.ts
**/src/environments/environment.ts
**/src/assets/.gitkeep
**/src/favicon.ico
**/src/styles.css
**/src/systemjs-angular-loader.js
**/src/systemjs.config.js
@ -73,7 +74,8 @@ aot-compiler/**/*.factory.d.ts
!styleguide/src/systemjs.custom.js
# universal
!universal/**/webpack.config.universal.js
!universal/webpack.config.client.js
!universal/webpack.config.universal.js
# plunkers
*plnkr.no-link.html

View File

@ -0,0 +1,22 @@
<!-- #docregion -->
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Angular Universal Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css">
<script src="shim.min.js"></script>
<script src="zone.min.js"></script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
<!-- #docregion client-app-bundle -->
<script src="client.js"></script>
<!-- #enddocregion client-app-bundle -->
</html>

View File

@ -4,19 +4,20 @@
<head>
<base href="/">
<title>Angular Universal Tour of Heroes</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css">
<!-- Polyfills -->
<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/systemjs/dist/system.src.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('main.js').catch(function(err){ console.error(err); });
System.import('main.js')
.catch(function(err){ console.error(err); });
</script>
</head>

View File

@ -4,7 +4,9 @@ import * as express from 'express';
import { enableProdMode } from '@angular/core';
// #docregion import-app-server-factory
// AppServerModuleNgFactory, generated by AOT compiler, is not available at design time
// AppServerModuleNgFactory, generated by AOT webpack plug-in,
// exists in-memory during build.
// It is not available in the file system at design time
import { AppServerModuleNgFactory } from '../../aot/src/universal/app-server.module.ngfactory';
// #enddocregion import-app-server-factory
@ -21,8 +23,8 @@ server.engine('html', universalEngine({
appModuleFactory: AppServerModuleNgFactory
}));
// engine should find templates (like index.html) in 'src/' by default
server.set('views', 'src');
// engine should find templates (like index-universal.html) in 'dist/' by default
server.set('views', 'dist');
// #enddocregion universal-engine
// CRITICAL TODO: add authentication/authorization middleware
@ -41,7 +43,7 @@ const pathWithNoExt = /^([^.]*)$/;
// treat any path without an extension as in-app navigation
server.get(pathWithNoExt, (req, res) => {
// render with the universal template engine
res.render('index.html', { req });
res.render('index-universal.html', { req });
});
// #enddocregion navigation-request
@ -51,8 +53,9 @@ server.use((req, res, next) => {
const fileName = req.originalUrl;
console.log(fileName);
// security: only serve files from node_modules or src
const root = fileName.startsWith('/node_modules/') ? '.' : 'src';
// security: only serve files from dist
const root = 'dist';
res.sendFile(fileName, { root }, err => {
if (err) { next(err); }
});

View File

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.universal.json",
"files": [
"src/main.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"entryModule": "./src/app/app.module#AppModule",
"skipMetadataEmit" : true
}
}

View File

@ -10,7 +10,7 @@
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"../../node_modules/@types/"
"./node_modules/@types/"
]
},

View File

@ -0,0 +1,34 @@
// #docregion
const ngtools = require('@ngtools/webpack');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
devtool: 'source-map',
entry: {
main: [ './src/main.ts' ]
},
resolve: {
extensions: ['.ts', '.js']
},
output: {
path: 'dist',
filename: 'client.js'
},
plugins: [
// compile with AOT
new ngtools.AotPlugin({
tsConfigPath: './tsconfig.client.json'
}),
// minify
new UglifyJSPlugin()
],
module: {
rules: [
{ test: /\.css$/, loader: 'raw-loader' },
{ test: /\.html$/, loader: 'raw-loader' },
{ test: /\.ts$/, loader: '@ngtools/webpack' }
]
}
}

View File

@ -1,40 +1,44 @@
// #docregion
const ngtools = require('@ngtools/webpack');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
devtool: 'source-map',
// #docregion entry
entry: {
main: [
'./src/universal/app-server.module.ts',
'./src/universal/server.ts'
]
},
// #enddocregion entry
resolve: {
extensions: ['.ts', '.js']
},
target: 'node',
// #docregion output
output: {
path: 'src/dist',
path: 'dist',
filename: 'server.js'
},
// #enddocregion output
// #docregion plugins
plugins: [
// compile with AOT
new ngtools.AotPlugin({
tsConfigPath: './tsconfig-universal.json'
})
tsConfigPath: './tsconfig.universal.json'
}),
// copy assets to the output (/dist) folder
new CopyWebpackPlugin([
{from: 'src/index-universal.html'},
{from: 'src/favicon.ico'},
{from: 'src/styles.css'},
{from: 'node_modules/core-js/client/shim.min.js'},
{from: 'node_modules/zone.js/dist/zone.min.js'},
])
],
// #enddocregion plugins
// #docregion rules
module: {
rules: [
{ test: /\.css$/, loader: 'raw-loader' },
{ test: /\.css$/, loader: 'raw-loader' },
{ test: /\.html$/, loader: 'raw-loader' },
{ test: /\.ts$/, loader: '@ngtools/webpack' }
{ test: /\.ts$/, loader: '@ngtools/webpack' }
]
}
// #enddocregion rules
}

View File

@ -1,8 +1,8 @@
{
"files":[
"!dist/",
"!**/*.d.ts",
"!**/src/**/*.js",
"!**/universal/**/*.js"
"!**/src/**/*.js"
],
"removeSystemJsConfig": false,
"type": "universal"

View File

@ -1,4 +1,4 @@
# Angular Universal
# Angular Universal: server-side rendering
This guide describes **Angular Universal**, a technology that runs your Angular application on the server.
@ -10,12 +10,6 @@ through a process called **server-side rendering (SSR)**.
It can generate and serve those pages in response to requests from browsers.
It can also pre-generate pages as HTML files that you serve later.
Universal's server-side rendering has several potential benefits:
* [Facilitate web crawlers (SEO)](#web-crawlers).
* [Show content sooner](#startup-performance).
* [Perform well on mobile and low power devices](#no-javascript).
This guide describes a Universal sample application that launches quickly as a server-rendered page.
Meanwhile, the browser downloads the full client version and switches to it automatically after the code loads.
@ -37,7 +31,7 @@ The build setup described in this guide is experimental and subject to change.
## Overview
This overview explains the benefits of a Universal application, how it works, and the limitations of server-side rendering. Then it describes the sample application that goes with this guide.
This overview explains the benefits of a Universal application and how it works. Then it describes the sample application that goes with this guide.
Subsequent sections describe a sample Universal application derived from the Tour of Heroes tutorial
and explain how to build and run that app.
@ -84,9 +78,9 @@ Displaying the first page quickly can be critical for user engagement.
Captive users of a line-of-business app may have to wait.
But a casual visitor will switch to a faster site if your app takes "too long" to show the first page.
While [AOT](guide/aot-compiler) compilation speeds up application start times, it may not be fast enough, especially on mobile devices with slow connections.
While [AOT](guide/aot-compiler) compilation speeds up application start times, it might not be fast enough for some of your audience, especially users on mobile devices with slow connections.
[53% of mobile site visits are abandoned](https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/) if pages take longer than 3 seconds to load.
Your app needs to load quickly, to engage users before they decide to do something else.
Your app may have to launch faster to engage these users before they decide to do something else.
With Angular Universal, you can generate landing pages for the app that look like the complete app.
The pages are pure HTML, and can display even if JavaScript is disabled.
@ -128,8 +122,6 @@ The `renderModuleFactory` renders that view within the `<app>` tag of the templa
Finally, the server returns the rendered page to the client.
{@a limitations}
### Working around the browser APIs
Because a Universal `platform-server` app doesn't execute in the browser, you may have to work around some of the APIs and capabilities that you otherwise take for granted on the client.
@ -171,7 +163,7 @@ You will create:
* a server-side app module, `app.server.module.ts`
* a Universal app renderer, `universal-engine.ts`
* an express web server to handle requests, `server.ts`
* a TypeScript config file, `tsconfig-universal.json`
* a TypeScript config file, `tsconfig.universal.json`
* a Webpack config file, `webpack.config.universal.js`
When you're done, the folder structure will look like this:
@ -179,22 +171,28 @@ When you're done, the folder structure will look like this:
<code-example format="." language="none" linenums="false">
src/
index.html <i>app web page</i>
index-universal.html <i>* universal app web page template</i>
main.ts <i>bootstrapper for client app</i>
style.css <i>styles for the app</i>
systemjs.config.js <i>SystemJS client configuration</i>
systemjs-angular-loader.js <i>SystemJS add-in</i>
tsconfig.json <i>TypeScript client configuration</i>
app/ ... <i>application code</i>
dist/
server.js <i>* AOT-compiled server bundle</i>
dist/ <i>* Post-build files</i>
client.js <i>* AOT-compiled client bundle</i>
server.js <i>* express server & universal app bundle</i>
index-universal.html <i>* copy of the app web page template</i>
... <i>* copies of other asset files</i>
universal/ <i>* folder for universal code</i>
app-server.module.ts <i>* server-side application module</i>
server.ts <i>* express web server</i>
universal-engine.ts <i>* express template engine</i>
bs-config.json <i>config file for lite server</i>
package.json <i>npm configuration</i>
tsconfig-universal.json <i>* TypeScript Universal configuration</i>
webpack.config.universal.js <i>* Webpack Universal configuration</i>
tsconfig.client.json <i>* TypeScript client AOT configuration</i>
tsconfig.universal.json <i>* TypeScript Universal configuration</i>
webpack.config.aot.js <i>* Webpack client AOT configuration</i>
webpack.config.universal.js <i>* Webpack Universal configuration</i>
</code-example>
The files marked with `*` are new and not in the original tutorial sample.
@ -210,19 +208,20 @@ This guide covers them in the sections below.
To get started, install these Universal and Webpack packages.
* `@angular/compiler-cli` - contains the AOT compiler
* `@angular/platform-server` - Universal server-side components
* `webpack` - Webpack JavaScript bundler
* `@ngtools/webpack` - Webpack loader and plugin for bundling compiled applications
* `raw-loader` - Webpack loader for text files
* `express` - node web server
* `@types/express` - TypeScript type definitions for express
* `@angular/compiler-cli` - contains the AOT compiler.
* `@angular/platform-server` - Universal server-side components.
* `webpack` - Webpack JavaScript bundler.
* `@ngtools/webpack` - Webpack loader and plugin for bundling compiled applications.
* `copy-webpack-plugin` - Webpack plugin to copy asset files to the output folder.
* `raw-loader` - Webpack loader for text files.
* `express` - node web server.
* `@types/express` - TypeScript type definitions for express.
Install them with the following commands:
<code-example format="." language="bash">
npm install @angular/compiler-cli @angular/platform-server express --save
npm install webpack @ngtools/webpack raw-loader @types/express --save-dev
npm install webpack @ngtools/webpack copy-webpack-plugin raw-loader @types/express --save-dev
</code-example>
### Modify the client app
@ -240,11 +239,12 @@ This gives the appearance of a near-instant application.
Meanwhile, the browser downloads the client app scripts in background.
Once loaded, Angular transitions from the static server-rendered page to the dynamically rendered views of the live client app.
To make this work, the template for server-side rendering contains the `<script>` tags necessary to load the JavaScript libraries and other assets for the full client app.
To make this work, the template for server-side rendering contains the `<script>` tags necessary to load the JavaScript and other asset files for the interactive client app.
As is often the case, the unmodified client `index.html` acts as the template for server-side rendering.
As is often the case, a production version of the client `index.html` acts as the template for server-side rendering.
You'll get to that [soon](#index-universal).
But you do have to adjust the root `AppModule`.
Essential changes to the root `AppModule` are the immediate concern.
Open file `src/app/app.module.ts` and find the `BrowserModule` import in the `NgModule` metadata.
Replace that import with this one:
@ -320,25 +320,38 @@ npm start
When you are done, shut down the server with `ctrl-C`.
#### Revert the build
<div class="alert is-important">
The rest of this page concentrates on the server-side universal app.
An important [teaching point below](#universal-in-action) assumes that you _did not compile the client-side app_.
To maintain that useful fiction, delete the client-side compiled files.
If you've been through this guide completely once before,
the compiler may fail with the following error:
<code-example format="." language="bash">
rm src/main.js* && rm src/app/*.js*
error TS2307: Cannot find module '../../aot/src/universal/app-server.module.ngfactory'.
</code-example>
You must exclude the _server-side_ `/universal` folder files from _client app_ compilation.
Open `tsconfig.json`, find the `"exclude"` node and add `"universal/*"` to the array.
The result might look something like this:
```
"exclude": [
"node_modules/*",
"universal/*"
]
```
Compile and run again with `npm start`.
</div>
<br><hr>
{@a server-code}
## Server code
To run an Angular Universal application, you need a server that accepts client requests and returns rendered pages.
That's not part of the client-side Angular app, so you need to add the necessary pieces.
To run an Angular Universal application, you'll need a server that accepts client requests and returns rendered pages.
Create a `universal/` folder as a sibling to the `app/` folder.
@ -492,7 +505,7 @@ That file _will exist_, briefly, during compilation.
The build process creates it in the `../aot` directory, bundles it with other `universal/` code, and erases it during post-build cleanup. It's never around when you're editing `server.ts`.
All will be well as long as you arrange for the AOT compiler to generate this module factory file _before_ it compiles _this_ web server file.
[Learn how below](#typescript-configuration).
[Learn how below](#universal-typescript-configuration).
#### Add the Universal template engine
@ -561,55 +574,91 @@ The following code filters for request URLs with no extensions and treats them a
#### Serve static files safely
A single `server.use()` treats all other URLs as requests for static assets.
A single `server.use()` treats all other URLs as requests for static assets
such as JavaScript, image, and style files.
To ensure that clients can only download the files that they are _permitted_ to see, you will [put all client-facing asset files in the `/dist` folder](#universal-webpack-configuration)
and will only honor requests for files from the `/dist` folder.
The following express code routes all remaining requests to `/dist`; it returns a `404 - NOT FOUND` if the file is not found.
<code-example path="universal/src/universal/server.ts" title="src/universal/server.ts (static files)" region="static" linenums="false">
</code-example>
In this app, valid static assets are either in the `node_modules` folder or the `src` folder,
a fact enforced by the `root` prefixed to the request URL.
An attempt to download a file located anywhere else results in `404 - not found`.
For security reasons, locate sensitive files outside of these two folders.
{@a universal-configuration}
## Configure for Universal
The server application requires its own build configuration, independently from the configuration of the client-side version.
The server application requires its own web page and its own build configuration.
You'll create two configuration files, one for TypeScript and one for Webpack.
{@a index-universal}
{@a typescript-configuration}
### Universal web page
### TypeScript configuration
The universal app renders pages based on a host web page template.
Simple universal apps make do with a slightly modified copy of the original `index.html`.
Create a `tsconfig.universal.json` file in the project root directory to configure TypeScript compilation of the universal app.
<div class="alert is-helpful">
You start with a copy of the client app's `tsconfig.json` and make the following changes.
If you build a production version of the client app with the CLI's `ng build --prod` command, you do not need a separate universal `index.html`.
The CLI constructs a suitable `index.html` for you. You can skip this subsection and continue to [universal TypeScript configuration](#universal-typescript-configuration).
* Set the `module` property to `es2015`.
The transpiled JavaScript will use `import` statements instead of `require()` calls.
Read on if you're building the app without the CLI.
</div>
Create an `index-universal.html` as follows, shown next to the development `index.html` for comparison.
<code-tabs>
<code-pane title="src/index-universal.html" path="universal/src/index-universal.html">
</code-pane>
<code-pane title="src/index.html" path="universal/src/index.html">
</code-pane>
</code-tabs>
The differences are few.
* Load the minified versions of the `shim` and `zone` polyfills from the root (which will be `/dist`)
* You won't use SystemJS for universal nor to load the client app.
* Instead you'll load the [production version of the client app](#build-client), `client.js`, which is the result of AOT compilation, minification, and bundling.
That's it for `index-universal.html`.
Next you'll create two universal configuration files, one for TypeScript and one for Webpack.
{@a universal-typescript-configuration}
### Universal TypeScript configuration
Create a `tsconfig.universal.json` file in the project root directory to configure TypeScript and AOT compilation of the universal app.
<code-example path="universal/tsconfig.universal.json" title="tsconfig.universal.json">
</code-example>
Certain settings are noteworthy for their difference from the `tsconfig.json` in the `src/` folder.
* The `module` property must be **es2015** because
the transpiled JavaScript will use `import` statements instead of `require()` calls.
* Set the `files` property to compile the `app-server.module` before the `universal-engine`,
* Point `"typeRoots"` to `"./node_modules/@types/"`
* Set the `files` property (instead of `exclude`) to compile the `app-server.module` before the `universal-engine`,
for the reason [explained above](#import-app-server-module-factory).
* Add a new `angularCompilerOptions` section with the following settings:
* The `angularCompilerOptions` section guides the AOT compiler:
* `genDir` - the temporary output directory for AOT compiled code.
* `entryModule` - the root module of the client application, expressed as `path/to/file#ClassName`.
* `skipMetadataEmit` - set `true` because you don't need metadata in the bundled application.
* `genDir` - the temporary output directory for AOT compiled code.
* `entryModule` - the root module of the client application, expressed as `path/to/file#ClassName`.
* `skipMetadataEmit` - set `true` because you don't need metadata in the bundled application.
The resulting `tsconfig.universal.json` should look like this.
<code-example path="universal/tsconfig-universal.json" title="tsconfig-universal.json">
</code-example>
{@a webpack-configuration}
### Webpack configuration
### Universal Webpack configuration
Create a `webpack.config.universal.js` file in the project root directory with the following code.
@ -625,18 +674,28 @@ A few observations may clarify some of the choices.
* The `@ngtools/webpack` loader loads and prepares the TypeScript files for compilation.
* The `AotPlugin` runs the AOT compiler (`ngc`) over the prepared TypeScript, guided by the `tsconfig-universal.json` you created [above](#typescript-configuration).
* The `AotPlugin` runs the AOT compiler (`ngc`) over the prepared TypeScript, guided by the `tsconfig.universal.json` you created [above](#universal-typescript-configuration).
* The `raw-loader` loads CSS and HTML files as strings.
* The `raw-loader` loads imported CSS and HTML files as strings.
You may need additional loaders or configuration for other file types.
* The compiled output and other asset files are bundled into `src/dist/server.js`.
* The compiled output is bundled into `dist/server.js`.
{@a build-and-serve-universal}
## Build and run
* The `CopyWebpackPlugin` copies specific static files from their source locations into the `/dist` folder.
These files include the universal app's web page template, `index-universal.html`,
and the JavaScript and CSS files mentioned in it
... with the notable exception of `client.js` [to be discussed below](#build-client).
<div class="alert is-helpful">
The `CopyWebpackPlugin` step is unnecessary if you [build the client](#build-client) with the CLI.
</div>
## Build and run with universal
Now that you've created the TypeScript and Webpack config files, you can build and run the Universal application.
@ -644,9 +703,10 @@ First add the _build_ and _serve_ commands to the `scripts` section of the `pack
<code-example format="." language="ts">
"scripts": {
...
"build:uni": "webpack --config webpack.config.universal.js",
"serve:uni": "node src/dist/server.js",
...
"serve:uni": "node dist/server.js",
...
}
</code-example>
@ -660,8 +720,8 @@ From the command prompt, type
npm run build:uni
</code-example>
Webpack compiles and bundles the universal app into a single output file, `src/dist/server.js`, per the [configuration above](#universal-configuration).
It also generates a [source map](https://webpack.js.org/configuration/devtool/), `src/dist/server.js.map` that correlates the bundle code to the source code.
Webpack compiles and bundles the universal app into a single output file, `dist/server.js`, per the [configuration above](#universal-configuration).
It also generates a [source map](https://webpack.js.org/configuration/devtool/), `dist/server.js.map` that correlates the bundle code to the source code.
Source maps are primarily for the browser's [dev tools](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps), but on the server they help locate compilation errors in your components.
@ -680,8 +740,6 @@ The console window should say
listening on port 3200...
</code-example>
{@a universal-in-action}
## Universal in action
Open a browser to http://localhost:3200/.
@ -701,29 +759,10 @@ But clicks, mouse-moves, and keyboard entries are inert.
User events other than `routerLink` clicks aren't supported.
The user must wait for the full client app to arrive.
It will never arrive until you compile the client app,
It will never arrive until you compile the client app
and move the output into the `dist/` folder,
a step you'll take in just a moment.
<div class="alert is-important">
If these features work, then you actually _did_ build the client version of the app and it is executing in the browser.
You most likely built it earlier when you confirmed that the app still worked after changing the hero services.
While you will compile the client version soon,
at the moment you want to see how the universal app behaves _without the client version_.
Pretend that you did not build the client version.
Open a terminal window and _delete the compiled files_.
<code-example format="." language="bash">
rm src/main.js* && rm src/app/*.js*
</code-example>
Now the app should still navigate but do nothing with button clicks.
</div>
#### Review the console log
Open the browser's development tools.
@ -731,98 +770,131 @@ In the console window you should see output like the following:
<code-example format="." language="bash" linenums="false">
building: /
Running in the browser with appId=uni
/favicon.ico
/styles.css
/node_modules/core-js/client/shim.min.js
/node_modules/zone.js/dist/zone.js
/node_modules/systemjs/dist/system.src.js
/systemjs.config.js
/main.js
Error: ENOENT: no such file or directory, stat '... ./src/main.js' ...
/shim.min.js
/zone.min.js
/client.js
Error: ENOENT: no such file or directory, stat '... dist/client.js' ...
</code-example>
The first line shows that the server received a request for '/' and passed it to the Universal engine, which then built the HTML page from your Angular application.
The application re-routes `/` to `/dashboard`.
Refresh the browser and the first console line becomes `building: /dashboard`.
Refresh the browser and the first line is `from cache: /` because your _universal template engine_
found the previously rendered page for `/` in its cache.
Refresh again. This time, the first line is `from cache: /dashboard` because your _universal template engine_
found the previously rendered page for `/dashboard` in its cache.
The remaining console log lines report requests for static files coming from `<link>` and `<script>` tags in the `index.html`.
The remaining console log lines report requests for static files coming from the `<link>` and `<script>` tags in the `index-universal.html`.
The `.js` files in particular are needed to run the client version of the app in the browser.
Once they're loaded, Angular _should_ replace the Universal-rendered page with the full client app.
Except that it didn't!
#### Missing _main.js_ error
#### Missing _client.js_ error
Note the error at the bottom of the console log that complains about a missing `main.js` file.
Note the error at the bottom of the console log that complains about a missing `client.js` file.
<code-example format="." language="bash">
Error: ENOENT: no such file or directory, stat '... ./src/main.js' ...
Error: ENOENT: no such file or directory, stat '... dist/client.js' ...
</code-example>
The full client app doesn't launch because `main.js` doesn't exist.
And `main.js` doesn't exist because you have not yet built the client version of the app.
The full client app doesn't launch because `client.js` doesn't exist.
And `client.js` doesn't exist because you have not yet built the client version of the app.
{@a client-transition}
{@a build-client}
## Build the client app
#### Build the client app
The express server is sending the universal server-side rendered pages to the client.
But it isn't serving the interactive client app because you haven't built it yet.
Now build the client-side version of the app.
A key motivation for universal is to quickly render the first page on the client so of course
you want to transition to the client app as quickly as possible too.
You should build a small, _production_ version of the client app with that AOT compiler that loads and runs fast.
#### Build the client with the CLI
If you're using the CLI to build the client app, you simply run the following command and you're done.
<code-example format="." language="bash">
npm run build
ng build --prod
</code-example>
<div class="alert is-helpful">
The CLI takes care of the rest, including copying all necessary files to the `/dist` folder.
By default the CLI produces two separate client app bundles, one with the vendor packages (`vendor.bundle.js`) and one with your application code (`inline.bundle.js`).
This command builds the client app in a verbose, development mode.
Alternatively, you can build the client using CLI _tools_ but **_without the CLI itself_**.
Read the following sub-sections if that interests you.
If not, skip ahead to the section on [throttling](#throttling).
Of course youd build the production version with minimal size in mind, even if you didnt use universal.
Building for production is covered elsewhere in the documentation.
#### Build the client by hand
</div>
You can build the application without the considerable help of the CLI.
You'll still compile with AOT.
You'll still bundle and minify with Webpack.
<div class="alert is-important">
You'll need two configuration files, just as you did for the universal server: one for TypeScript and one for Webpack.
The compiler may fail with the following error:
The client app versions are only slightly different from the corresponding server files.
Here they are, followed by notes that call out the differences:
<code-tabs>
<code-pane title="tsconfig.client.json" path="universal/tsconfig.client.json">
</code-pane>
<code-pane title="webpack.config.client.js" path="universal/webpack.config.client.js">
</code-pane>
</code-tabs>
The **_tsconfig.client.json_** inherits (via `extends`) most settings from the universal `tsconfig`. The _only_ substantive difference is in the `files` section which identifies the client app bootstrapping file, `main.ts`, from which the compiler discovers all other required files.
The **_webpack.config.client.js_** has a few differences,
all of them obvious.
* There is only one `entry.main` file, `main.ts`.
* The output filename is `client.js`.
* The `AotPlugin` references the `./tsconfig.client.json`.
* There's no need to copy asset files because the [universal Webpack config](#universal-webpack-configuration)
took care of them.
* Add the `UglifyJSPlugin` to minify the client app code.
Why minify the client code and not the server code?
You minify client code to reduce the payload transmitted to the browser. The universal server code stays on the server where minification is pointless.
#### Run Webpack for the client
Add an `npm` script to make it easy to build the client from the terminal window.
<code-example format="." language="ts">
"scripts": {
...
"build:uni-client": "webpack --config webpack.config.client.js",
...
}
</code-example>
Now run that command
<code-example format="." language="bash">
error TS2307: Cannot find module '../../aot/src/universal/app-server.module.ngfactory'.
npm run build:uni-client
</code-example>
You need to exclude the _server-side_ `/universal` folder files from _client app_ compilation.
Open `tsconfig.json`, find the `"exclude"` node and add `"universal/*"` to the array.
The result might look something like this:
```
"exclude": [
"node_modules/*",
"universal/*"
]
```
Compile again with `npm run build`.
</div>
Refresh the browser.
The console log shows that the server can find `client.js`
The Universal app is quickly replaced by the full client app.
The console log fills with requests for more files.
Most importantly, the event-based features now work as expected.
<div class="alert is-critical">
When you make application changes, remember to rebuild _both_ the universal _and_ the client-side versions of the app.
When you make application changes, remember to rebuild _both_ the universal server _and_ the client versions of the app.
</div>
{@a throttling}
#### Throttling
## Throttling
The transition from the server-rendered app to the client app happens quickly on a development machine.
You can simulate a slower network to see the transition more clearly and
@ -832,7 +904,7 @@ Open the Chrome Dev Tools and go to the Network tab.
Find the [Network Throttling](https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#throttling) dropdown on the far right of the menu bar.
Try one of the "3G" speeds.
The server-rendered app still launches quickly but the full client app takes many seconds to load.
The server-rendered app still launches quickly but the full client app may take seconds to load.
{@a conclusion}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -30,8 +30,9 @@
"test:webpack": "karma start karma.webpack.conf.js",
"build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail",
"build:babel": "babel src -d src --extensions \".es6\" --source-maps",
"build:uni-client": "webpack --config webpack.config.client.js",
"build:uni": "webpack --config webpack.config.universal.js",
"serve:uni": "node src/dist/server.js",
"serve:uni": "node dist/server.js",
"clean": "rimraf src/dist && rimraf src/app/*.js* && rimraf src/universal/*.js* && rimraf src/main.js*",
"copy-dist-files": "node ./copy-dist-files.js",

View File

@ -25,7 +25,7 @@
"@angular/platform-server": "~4.3.1",
"@angular/router": "~4.3.1",
"@angular/upgrade": "~4.3.1",
"angular-in-memory-web-api": "~0.4.0",
"angular-in-memory-web-api": "~0.4.6",
"core-js": "^2.4.1",
"express": "^4.14.1",
"rxjs": "^5.1.0",
@ -49,6 +49,12 @@
"@types/node": "^6.0.45",
"canonical-path": "0.0.2",
"concurrently": "^3.0.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.26.1",
"extract-text-webpack-plugin": "2.0.0-beta.5",
"file-loader": "^0.9.0",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.16.1",
"http-server": "^0.9.0",
"jasmine": "~2.4.1",
"jasmine-core": "~2.4.1",

File diff suppressed because it is too large Load Diff