+
+ Angular Universal Tour of Heroes
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
diff --git a/aio/content/examples/universal/src/index.html b/aio/content/examples/universal/src/index.html
index 727b1fc833..2ce59e8d44 100644
--- a/aio/content/examples/universal/src/index.html
+++ b/aio/content/examples/universal/src/index.html
@@ -4,19 +4,20 @@
Angular Universal Tour of Heroes
+
-
+
-
-
+
diff --git a/aio/content/examples/universal/src/universal/server.ts b/aio/content/examples/universal/src/universal/server.ts
index e307c7d622..92103a89cd 100644
--- a/aio/content/examples/universal/src/universal/server.ts
+++ b/aio/content/examples/universal/src/universal/server.ts
@@ -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); }
});
diff --git a/aio/content/examples/universal/tsconfig.client.json b/aio/content/examples/universal/tsconfig.client.json
new file mode 100644
index 0000000000..3f4458f02f
--- /dev/null
+++ b/aio/content/examples/universal/tsconfig.client.json
@@ -0,0 +1,13 @@
+{
+ "extends": "./tsconfig.universal.json",
+
+ "files": [
+ "src/main.ts"
+ ],
+
+ "angularCompilerOptions": {
+ "genDir": "aot",
+ "entryModule": "./src/app/app.module#AppModule",
+ "skipMetadataEmit" : true
+ }
+}
diff --git a/aio/content/examples/universal/tsconfig-universal.json b/aio/content/examples/universal/tsconfig.universal.json
similarity index 94%
rename from aio/content/examples/universal/tsconfig-universal.json
rename to aio/content/examples/universal/tsconfig.universal.json
index f23de99091..4bbda5e956 100644
--- a/aio/content/examples/universal/tsconfig-universal.json
+++ b/aio/content/examples/universal/tsconfig.universal.json
@@ -10,7 +10,7 @@
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
- "../../node_modules/@types/"
+ "./node_modules/@types/"
]
},
diff --git a/aio/content/examples/universal/webpack.config.client.js b/aio/content/examples/universal/webpack.config.client.js
new file mode 100644
index 0000000000..f924f7bcae
--- /dev/null
+++ b/aio/content/examples/universal/webpack.config.client.js
@@ -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' }
+ ]
+ }
+}
diff --git a/aio/content/examples/universal/webpack.config.universal.js b/aio/content/examples/universal/webpack.config.universal.js
index f2d31f85ee..b24943fc13 100644
--- a/aio/content/examples/universal/webpack.config.universal.js
+++ b/aio/content/examples/universal/webpack.config.universal.js
@@ -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
}
diff --git a/aio/content/examples/universal/zipper.json b/aio/content/examples/universal/zipper.json
index a493091fd5..37b42ad643 100644
--- a/aio/content/examples/universal/zipper.json
+++ b/aio/content/examples/universal/zipper.json
@@ -1,8 +1,8 @@
{
"files":[
+ "!dist/",
"!**/*.d.ts",
- "!**/src/**/*.js",
- "!**/universal/**/*.js"
+ "!**/src/**/*.js"
],
"removeSystemJsConfig": false,
"type": "universal"
diff --git a/aio/content/guide/universal.md b/aio/content/guide/universal.md
index 31d72768de..414e6bf667 100644
--- a/aio/content/guide/universal.md
+++ b/aio/content/guide/universal.md
@@ -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 `` 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:
src/
index.html app web page
+ index-universal.html * universal app web page template
main.ts bootstrapper for client app
style.css styles for the app
systemjs.config.js SystemJS client configuration
systemjs-angular-loader.js SystemJS add-in
tsconfig.json TypeScript client configuration
app/ ... application code
- dist/
- server.js * AOT-compiled server bundle
+ dist/ * Post-build files
+ client.js * AOT-compiled client bundle
+ server.js * express server & universal app bundle
+ index-universal.html * copy of the app web page template
+ ... * copies of other asset files
universal/ * folder for universal code
app-server.module.ts * server-side application module
server.ts * express web server
universal-engine.ts * express template engine
bs-config.json config file for lite server
package.json npm configuration
-tsconfig-universal.json * TypeScript Universal configuration
-webpack.config.universal.js * Webpack Universal configuration
+tsconfig.client.json * TypeScript client AOT configuration
+tsconfig.universal.json * TypeScript Universal configuration
+webpack.config.aot.js * Webpack client AOT configuration
+webpack.config.universal.js * Webpack Universal configuration
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:
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
### 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 `