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 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:
@ -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
<divclass="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,
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.
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.
<divclass="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.
* 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.
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).
<divclass="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
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.
<divclass="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-exampleformat="."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:
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-exampleformat="."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-exampleformat="."language="bash">
npm run build
ng build --prod
</code-example>
<divclass="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.
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.
<divclass="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:
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.
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.
<divclass="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.