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:
parent
0b0d25fa33
commit
555b1cdf29
4
aio/content/examples/.gitignore
vendored
4
aio/content/examples/.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
**/src/environments/environment.prod.ts
|
**/src/environments/environment.prod.ts
|
||||||
**/src/environments/environment.ts
|
**/src/environments/environment.ts
|
||||||
**/src/assets/.gitkeep
|
**/src/assets/.gitkeep
|
||||||
|
**/src/favicon.ico
|
||||||
**/src/styles.css
|
**/src/styles.css
|
||||||
**/src/systemjs-angular-loader.js
|
**/src/systemjs-angular-loader.js
|
||||||
**/src/systemjs.config.js
|
**/src/systemjs.config.js
|
||||||
@ -73,7 +74,8 @@ aot-compiler/**/*.factory.d.ts
|
|||||||
!styleguide/src/systemjs.custom.js
|
!styleguide/src/systemjs.custom.js
|
||||||
|
|
||||||
# universal
|
# universal
|
||||||
!universal/**/webpack.config.universal.js
|
!universal/webpack.config.client.js
|
||||||
|
!universal/webpack.config.universal.js
|
||||||
|
|
||||||
# plunkers
|
# plunkers
|
||||||
*plnkr.no-link.html
|
*plnkr.no-link.html
|
||||||
|
22
aio/content/examples/universal/src/index-universal.html
Normal file
22
aio/content/examples/universal/src/index-universal.html
Normal 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>
|
@ -4,19 +4,20 @@
|
|||||||
<head>
|
<head>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<title>Angular Universal Tour of Heroes</title>
|
<title>Angular Universal Tour of Heroes</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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">
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
|
||||||
<!-- Polyfills -->
|
<!-- Polyfills -->
|
||||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
<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/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 src="systemjs.config.js"></script>
|
||||||
<script>
|
<script>
|
||||||
System.import('main.js').catch(function(err){ console.error(err); });
|
System.import('main.js')
|
||||||
|
.catch(function(err){ console.error(err); });
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -4,7 +4,9 @@ import * as express from 'express';
|
|||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
|
|
||||||
// #docregion import-app-server-factory
|
// #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';
|
import { AppServerModuleNgFactory } from '../../aot/src/universal/app-server.module.ngfactory';
|
||||||
// #enddocregion import-app-server-factory
|
// #enddocregion import-app-server-factory
|
||||||
|
|
||||||
@ -21,8 +23,8 @@ server.engine('html', universalEngine({
|
|||||||
appModuleFactory: AppServerModuleNgFactory
|
appModuleFactory: AppServerModuleNgFactory
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// engine should find templates (like index.html) in 'src/' by default
|
// engine should find templates (like index-universal.html) in 'dist/' by default
|
||||||
server.set('views', 'src');
|
server.set('views', 'dist');
|
||||||
// #enddocregion universal-engine
|
// #enddocregion universal-engine
|
||||||
|
|
||||||
// CRITICAL TODO: add authentication/authorization middleware
|
// CRITICAL TODO: add authentication/authorization middleware
|
||||||
@ -41,7 +43,7 @@ const pathWithNoExt = /^([^.]*)$/;
|
|||||||
// treat any path without an extension as in-app navigation
|
// treat any path without an extension as in-app navigation
|
||||||
server.get(pathWithNoExt, (req, res) => {
|
server.get(pathWithNoExt, (req, res) => {
|
||||||
// render with the universal template engine
|
// render with the universal template engine
|
||||||
res.render('index.html', { req });
|
res.render('index-universal.html', { req });
|
||||||
});
|
});
|
||||||
// #enddocregion navigation-request
|
// #enddocregion navigation-request
|
||||||
|
|
||||||
@ -51,8 +53,9 @@ server.use((req, res, next) => {
|
|||||||
const fileName = req.originalUrl;
|
const fileName = req.originalUrl;
|
||||||
console.log(fileName);
|
console.log(fileName);
|
||||||
|
|
||||||
// security: only serve files from node_modules or src
|
// security: only serve files from dist
|
||||||
const root = fileName.startsWith('/node_modules/') ? '.' : 'src';
|
const root = 'dist';
|
||||||
|
|
||||||
res.sendFile(fileName, { root }, err => {
|
res.sendFile(fileName, { root }, err => {
|
||||||
if (err) { next(err); }
|
if (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
13
aio/content/examples/universal/tsconfig.client.json
Normal file
13
aio/content/examples/universal/tsconfig.client.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.universal.json",
|
||||||
|
|
||||||
|
"files": [
|
||||||
|
"src/main.ts"
|
||||||
|
],
|
||||||
|
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"genDir": "aot",
|
||||||
|
"entryModule": "./src/app/app.module#AppModule",
|
||||||
|
"skipMetadataEmit" : true
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"../../node_modules/@types/"
|
"./node_modules/@types/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
34
aio/content/examples/universal/webpack.config.client.js
Normal file
34
aio/content/examples/universal/webpack.config.client.js
Normal 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' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +1,39 @@
|
|||||||
|
// #docregion
|
||||||
const ngtools = require('@ngtools/webpack');
|
const ngtools = require('@ngtools/webpack');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
// #docregion entry
|
|
||||||
entry: {
|
entry: {
|
||||||
main: [
|
main: [
|
||||||
'./src/universal/app-server.module.ts',
|
'./src/universal/app-server.module.ts',
|
||||||
'./src/universal/server.ts'
|
'./src/universal/server.ts'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// #enddocregion entry
|
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.js']
|
extensions: ['.ts', '.js']
|
||||||
},
|
},
|
||||||
target: 'node',
|
target: 'node',
|
||||||
// #docregion output
|
|
||||||
output: {
|
output: {
|
||||||
path: 'src/dist',
|
path: 'dist',
|
||||||
filename: 'server.js'
|
filename: 'server.js'
|
||||||
},
|
},
|
||||||
// #enddocregion output
|
|
||||||
// #docregion plugins
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
// compile with AOT
|
||||||
new ngtools.AotPlugin({
|
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: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{ test: /\.css$/, loader: 'raw-loader' },
|
{ test: /\.css$/, loader: 'raw-loader' },
|
||||||
@ -36,5 +41,4 @@ module.exports = {
|
|||||||
{ test: /\.ts$/, loader: '@ngtools/webpack' }
|
{ test: /\.ts$/, loader: '@ngtools/webpack' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
// #enddocregion rules
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"files":[
|
"files":[
|
||||||
|
"!dist/",
|
||||||
"!**/*.d.ts",
|
"!**/*.d.ts",
|
||||||
"!**/src/**/*.js",
|
"!**/src/**/*.js"
|
||||||
"!**/universal/**/*.js"
|
|
||||||
],
|
],
|
||||||
"removeSystemJsConfig": false,
|
"removeSystemJsConfig": false,
|
||||||
"type": "universal"
|
"type": "universal"
|
||||||
|
@ -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.
|
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 generate and serve those pages in response to requests from browsers.
|
||||||
It can also pre-generate pages as HTML files that you serve later.
|
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.
|
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.
|
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
|
## 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
|
Subsequent sections describe a sample Universal application derived from the Tour of Heroes tutorial
|
||||||
and explain how to build and run that app.
|
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.
|
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.
|
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.
|
[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.
|
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.
|
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.
|
Finally, the server returns the rendered page to the client.
|
||||||
|
|
||||||
{@a limitations}
|
|
||||||
|
|
||||||
### Working around the browser APIs
|
### 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.
|
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 server-side app module, `app.server.module.ts`
|
||||||
* a Universal app renderer, `universal-engine.ts`
|
* a Universal app renderer, `universal-engine.ts`
|
||||||
* an express web server to handle requests, `server.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`
|
* a Webpack config file, `webpack.config.universal.js`
|
||||||
|
|
||||||
When you're done, the folder structure will look like this:
|
When you're done, the folder structure will look like this:
|
||||||
@ -179,21 +171,27 @@ When you're done, the folder structure will look like this:
|
|||||||
<code-example format="." language="none" linenums="false">
|
<code-example format="." language="none" linenums="false">
|
||||||
src/
|
src/
|
||||||
index.html <i>app web page</i>
|
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>
|
main.ts <i>bootstrapper for client app</i>
|
||||||
style.css <i>styles for the app</i>
|
style.css <i>styles for the app</i>
|
||||||
systemjs.config.js <i>SystemJS client configuration</i>
|
systemjs.config.js <i>SystemJS client configuration</i>
|
||||||
systemjs-angular-loader.js <i>SystemJS add-in</i>
|
systemjs-angular-loader.js <i>SystemJS add-in</i>
|
||||||
tsconfig.json <i>TypeScript client configuration</i>
|
tsconfig.json <i>TypeScript client configuration</i>
|
||||||
app/ ... <i>application code</i>
|
app/ ... <i>application code</i>
|
||||||
dist/
|
dist/ <i>* Post-build files</i>
|
||||||
server.js <i>* AOT-compiled server bundle</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>
|
universal/ <i>* folder for universal code</i>
|
||||||
app-server.module.ts <i>* server-side application module</i>
|
app-server.module.ts <i>* server-side application module</i>
|
||||||
server.ts <i>* express web server</i>
|
server.ts <i>* express web server</i>
|
||||||
universal-engine.ts <i>* express template engine</i>
|
universal-engine.ts <i>* express template engine</i>
|
||||||
bs-config.json <i>config file for lite server</i>
|
bs-config.json <i>config file for lite server</i>
|
||||||
package.json <i>npm configuration</i>
|
package.json <i>npm configuration</i>
|
||||||
tsconfig-universal.json <i>* TypeScript 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>
|
webpack.config.universal.js <i>* Webpack Universal configuration</i>
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
@ -210,19 +208,20 @@ This guide covers them in the sections below.
|
|||||||
|
|
||||||
To get started, install these Universal and Webpack packages.
|
To get started, install these Universal and Webpack packages.
|
||||||
|
|
||||||
* `@angular/compiler-cli` - contains the AOT compiler
|
* `@angular/compiler-cli` - contains the AOT compiler.
|
||||||
* `@angular/platform-server` - Universal server-side components
|
* `@angular/platform-server` - Universal server-side components.
|
||||||
* `webpack` - Webpack JavaScript bundler
|
* `webpack` - Webpack JavaScript bundler.
|
||||||
* `@ngtools/webpack` - Webpack loader and plugin for bundling compiled applications
|
* `@ngtools/webpack` - Webpack loader and plugin for bundling compiled applications.
|
||||||
* `raw-loader` - Webpack loader for text files
|
* `copy-webpack-plugin` - Webpack plugin to copy asset files to the output folder.
|
||||||
* `express` - node web server
|
* `raw-loader` - Webpack loader for text files.
|
||||||
* `@types/express` - TypeScript type definitions for express
|
* `express` - node web server.
|
||||||
|
* `@types/express` - TypeScript type definitions for express.
|
||||||
|
|
||||||
Install them with the following commands:
|
Install them with the following commands:
|
||||||
|
|
||||||
<code-example format="." language="bash">
|
<code-example format="." language="bash">
|
||||||
npm install @angular/compiler-cli @angular/platform-server express --save
|
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>
|
</code-example>
|
||||||
|
|
||||||
### Modify the client app
|
### 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.
|
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.
|
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.
|
Open file `src/app/app.module.ts` and find the `BrowserModule` import in the `NgModule` metadata.
|
||||||
Replace that import with this one:
|
Replace that import with this one:
|
||||||
@ -320,25 +320,38 @@ npm start
|
|||||||
|
|
||||||
When you are done, shut down the server with `ctrl-C`.
|
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.
|
If you've been through this guide completely once before,
|
||||||
An important [teaching point below](#universal-in-action) assumes that you _did not compile the client-side app_.
|
the compiler may fail with the following error:
|
||||||
|
|
||||||
To maintain that useful fiction, delete the client-side compiled files.
|
|
||||||
|
|
||||||
<code-example format="." language="bash">
|
<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>
|
</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>
|
<br><hr>
|
||||||
|
|
||||||
{@a server-code}
|
{@a server-code}
|
||||||
|
|
||||||
## Server code
|
## Server code
|
||||||
|
|
||||||
To run an Angular Universal application, you need a server that accepts client requests and returns rendered pages.
|
To run an Angular Universal application, you'll 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.
|
|
||||||
|
|
||||||
Create a `universal/` folder as a sibling to the `app/` folder.
|
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`.
|
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.
|
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
|
#### 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
|
#### 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 path="universal/src/universal/server.ts" title="src/universal/server.ts (static files)" region="static" linenums="false">
|
||||||
</code-example>
|
</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}
|
{@a universal-configuration}
|
||||||
|
|
||||||
## Configure for Universal
|
## 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`.
|
Read on if you're building the app without the CLI.
|
||||||
The transpiled JavaScript will use `import` statements instead of `require()` calls.
|
|
||||||
|
</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).
|
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.
|
* `genDir` - the temporary output directory for AOT compiled code.
|
||||||
* `entryModule` - the root module of the client application, expressed as `path/to/file#ClassName`.
|
* `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.
|
* `skipMetadataEmit` - set `true` because you don't need metadata in the bundled application.
|
||||||
|
|
||||||
The resulting `tsconfig.universal.json` should look like this.
|
### Universal Webpack configuration
|
||||||
|
|
||||||
<code-example path="universal/tsconfig-universal.json" title="tsconfig-universal.json">
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
{@a webpack-configuration}
|
|
||||||
|
|
||||||
### Webpack configuration
|
|
||||||
|
|
||||||
Create a `webpack.config.universal.js` file in the project root directory with the following code.
|
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 `@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.
|
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.
|
Now that you've created the TypeScript and Webpack config files, you can build and run the Universal application.
|
||||||
|
|
||||||
@ -644,8 +703,9 @@ First add the _build_ and _serve_ commands to the `scripts` section of the `pack
|
|||||||
|
|
||||||
<code-example format="." language="ts">
|
<code-example format="." language="ts">
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
...
|
||||||
"build:uni": "webpack --config webpack.config.universal.js",
|
"build:uni": "webpack --config webpack.config.universal.js",
|
||||||
"serve:uni": "node src/dist/server.js",
|
"serve:uni": "node dist/server.js",
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
</code-example>
|
</code-example>
|
||||||
@ -660,8 +720,8 @@ From the command prompt, type
|
|||||||
npm run build:uni
|
npm run build:uni
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Webpack compiles and bundles the universal app into a single output file, `src/dist/server.js`, per the [configuration above](#universal-configuration).
|
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/), `src/dist/server.js.map` that correlates the bundle code to the source code.
|
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.
|
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...
|
listening on port 3200...
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
{@a universal-in-action}
|
|
||||||
|
|
||||||
## Universal in action
|
## Universal in action
|
||||||
|
|
||||||
Open a browser to http://localhost:3200/.
|
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.
|
User events other than `routerLink` clicks aren't supported.
|
||||||
The user must wait for the full client app to arrive.
|
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.
|
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
|
#### Review the console log
|
||||||
|
|
||||||
Open the browser's development tools.
|
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">
|
<code-example format="." language="bash" linenums="false">
|
||||||
building: /
|
building: /
|
||||||
|
Running in the browser with appId=uni
|
||||||
|
/favicon.ico
|
||||||
/styles.css
|
/styles.css
|
||||||
/node_modules/core-js/client/shim.min.js
|
/shim.min.js
|
||||||
/node_modules/zone.js/dist/zone.js
|
/zone.min.js
|
||||||
/node_modules/systemjs/dist/system.src.js
|
/client.js
|
||||||
/systemjs.config.js
|
Error: ENOENT: no such file or directory, stat '... dist/client.js' ...
|
||||||
/main.js
|
|
||||||
Error: ENOENT: no such file or directory, stat '... ./src/main.js' ...
|
|
||||||
</code-example>
|
</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 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_
|
The remaining console log lines report requests for static files coming from the `<link>` and `<script>` tags in the `index-universal.html`.
|
||||||
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 `.js` files in particular are needed to run the client version of the app in the browser.
|
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.
|
Once they're loaded, Angular _should_ replace the Universal-rendered page with the full client app.
|
||||||
|
|
||||||
Except that it didn't!
|
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">
|
<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>
|
</code-example>
|
||||||
|
|
||||||
The full client app doesn't launch because `main.js` doesn't exist.
|
The full client app doesn't launch because `client.js` doesn't exist.
|
||||||
And `main.js` doesn't exist because you have not yet built the client version of the app.
|
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">
|
<code-example format="." language="bash">
|
||||||
npm run build
|
ng build --prod
|
||||||
</code-example>
|
</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 you’d build the production version with minimal size in mind, even if you didn’t use universal.
|
#### Build the client by hand
|
||||||
Building for production is covered elsewhere in the documentation.
|
|
||||||
|
|
||||||
</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">
|
<code-example format="." language="bash">
|
||||||
error TS2307: Cannot find module '../../aot/src/universal/app-server.module.ngfactory'.
|
npm run build:uni-client
|
||||||
</code-example>
|
</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.
|
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 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.
|
Most importantly, the event-based features now work as expected.
|
||||||
|
|
||||||
<div class="alert is-critical">
|
<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>
|
</div>
|
||||||
|
|
||||||
{@a throttling}
|
## Throttling
|
||||||
|
|
||||||
#### Throttling
|
|
||||||
|
|
||||||
The transition from the server-rendered app to the client app happens quickly on a development machine.
|
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
|
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.
|
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.
|
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}
|
{@a conclusion}
|
||||||
|
|
||||||
|
BIN
aio/tools/examples/shared/boilerplate/src/favicon.ico
Normal file
BIN
aio/tools/examples/shared/boilerplate/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
@ -30,8 +30,9 @@
|
|||||||
"test:webpack": "karma start karma.webpack.conf.js",
|
"test:webpack": "karma start karma.webpack.conf.js",
|
||||||
"build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail",
|
"build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail",
|
||||||
"build:babel": "babel src -d src --extensions \".es6\" --source-maps",
|
"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",
|
"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*",
|
"clean": "rimraf src/dist && rimraf src/app/*.js* && rimraf src/universal/*.js* && rimraf src/main.js*",
|
||||||
"copy-dist-files": "node ./copy-dist-files.js",
|
"copy-dist-files": "node ./copy-dist-files.js",
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"@angular/platform-server": "~4.3.1",
|
"@angular/platform-server": "~4.3.1",
|
||||||
"@angular/router": "~4.3.1",
|
"@angular/router": "~4.3.1",
|
||||||
"@angular/upgrade": "~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",
|
"core-js": "^2.4.1",
|
||||||
"express": "^4.14.1",
|
"express": "^4.14.1",
|
||||||
"rxjs": "^5.1.0",
|
"rxjs": "^5.1.0",
|
||||||
@ -49,6 +49,12 @@
|
|||||||
"@types/node": "^6.0.45",
|
"@types/node": "^6.0.45",
|
||||||
"canonical-path": "0.0.2",
|
"canonical-path": "0.0.2",
|
||||||
"concurrently": "^3.0.0",
|
"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",
|
"http-server": "^0.9.0",
|
||||||
"jasmine": "~2.4.1",
|
"jasmine": "~2.4.1",
|
||||||
"jasmine-core": "~2.4.1",
|
"jasmine-core": "~2.4.1",
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user