Skip to content

Commit 2a52691

Browse files
committed
1 parent c26a5f7 commit 2a52691

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

docs/documentation/stories.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@
3030
- [Linked Library](stories/linked-library)
3131
- [Multiple apps](stories/multiple-apps)
3232
- [Continuous Integration](stories/continuous-integration)
33+
- [Universal Rendering](stories/universal-rendering)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Universal bundles
2+
3+
Angular CLI supports generation of a Universal build for your application. This is a CommonJS-formatted bundle which can be `require()`'d into a Node application (for example, an Express server) and used with `@angular/platform-server`'s APIs to prerender your application.
4+
5+
This story will show you how to set up Universal bundling for an existing `@angular/cli` project in 4 steps.
6+
7+
## Step 0: Install `@angular/platform-server`
8+
9+
Install `@angular/platform-server` into your project. Make sure you use the same version as the other `@angular` packages in your project.
10+
11+
```bash
12+
$ npm install --save-dev @angular/platform-server
13+
```
14+
or
15+
```bash
16+
$ yarn add @angular/platform-server
17+
```
18+
19+
20+
## Step 1: Prepare your app for Universal rendering
21+
22+
The first thing you need to do is make your `AppModule` compatible with Universal by addding `.withServerTransition()` and an application ID to your `BrowserModule` import:
23+
24+
25+
### src/app/app.module.ts:
26+
27+
```javascript
28+
@NgModule({
29+
bootstrap: [AppComponent],
30+
imports: [
31+
// Add .withServerTransition() to support Universal rendering.
32+
// The application ID can be any identifier which is unique on
33+
// the page.
34+
BrowserModule.withServerTransition({appId: 'my-app'}),
35+
...
36+
],
37+
38+
})
39+
export class AppModule {}
40+
```
41+
42+
Next, create a module specifically for your application when running on the server. It's recommended to call this module `AppServerModule`.
43+
44+
This example places it alongside `app.module.ts` in a file named `app.server.module.ts`:
45+
46+
47+
### src/app/app.server.module.ts:
48+
49+
```javascript
50+
import {NgModule} from '@angular/core';
51+
import {ServerModule} from '@angular/platform-server';
52+
53+
import {AppModule} from './app.module';
54+
import {AppComponent} from './app.component';
55+
56+
@NgModule({
57+
imports: [
58+
// The AppServerModule should import your AppModule followed
59+
// by the ServerModule from @angular/platform-server.
60+
AppModule,
61+
ServerModule,
62+
],
63+
// Since the bootstrapped component is not inherited from your
64+
// imported AppModule, it needs to be repeated here.
65+
bootstrap: [AppComponent],
66+
})
67+
export class AppServerModule {}
68+
```
69+
70+
## Step 2: Create a server main file and tsconfig to build it
71+
72+
Create a main file for your Universal bundle. This file only needs to export your `AppServerModule`. It can go in `src`. This example calls this file `main.server.ts`:
73+
74+
### src/main.server.ts:
75+
76+
```javascript
77+
export {AppServerModule} from './app/app.server.module';
78+
```
79+
80+
Copy `tsconfig.app.json` to `tsconfig-server.json` and change it to build with a `"module"` target of `"commonjs"`.
81+
82+
Add a section for `"angularCompilerOptions"` and set `"entryModule"` to your `AppServerModule`, specified as a path to the import with a hash (`#`) containing the symbol name. In this example, this would be `app/app.server.module#AppServerModule`.
83+
84+
### src/tsconfig.server.json:
85+
86+
```javascript
87+
{
88+
"extends": "../tsconfig.json",
89+
"compilerOptions": {
90+
"outDir": "../out-tsc/app",
91+
"baseUrl": "./",
92+
// Set the module format to "commonjs":
93+
"module": "commonjs",
94+
"types": []
95+
},
96+
"exclude": [
97+
"test.ts",
98+
"**/*.spec.ts"
99+
],
100+
// Add "angularCompilerOptions" with the AppServerModule you wrote
101+
// set as the "entryModule".
102+
"angularCompilerOptions": {
103+
"entryModule": "app/app.server.module#AppServerModule"
104+
}
105+
}
106+
```
107+
108+
## Step 3: Create a new project in `.angular-cli.json`
109+
110+
In `.angular-cli.json` there is an array under the key `"apps"`. Copy the configuration for your client application there, and paste it as a new entry in the array, with an additional key `"platform"` set to `"server"`.
111+
112+
Then, remove the `"polyfills"` key - those aren't needed on the server, and adjust `"main"`, and `"tsconfig"` to point to the files you wrote in step 2. Finally, adjust `"outDir"` to a new location (this example uses `dist-server`).
113+
114+
### .angular-cli.json:
115+
116+
```javascript
117+
{
118+
...
119+
"apps": [
120+
{
121+
// Keep your original application config intact here.
122+
// It will be app 0.
123+
},
124+
{
125+
// This is your server app. It is app 1.
126+
"platform": "server",
127+
"root": "src",
128+
// Build to dist-server instead of dist. This prevents
129+
// client and server builds from overwriting each other.
130+
"outDir": "dist-server",
131+
"assets": [
132+
"assets",
133+
"favicon.ico"
134+
],
135+
"index": "index.html",
136+
// Change the main file to point to your server main.
137+
"main": "main.server.ts",
138+
// Remove polyfills.
139+
// "polyfills": "polyfills.ts",
140+
"test": "test.ts",
141+
// Change the tsconfig to point to your server config.
142+
"tsconfig": "tsconfig.server.json",
143+
"testTsconfig": "tsconfig.spec.json",
144+
"prefix": "app",
145+
"styles": [
146+
"styles.css"
147+
],
148+
"scripts": [],
149+
"environmentSource": "environments/environment.ts",
150+
"environments": {
151+
"dev": "environments/environment.ts",
152+
"prod": "environments/environment.prod.ts"
153+
}
154+
}
155+
],
156+
...
157+
}
158+
159+
```
160+
161+
## Building the bundle
162+
163+
With these steps complete, you should be able to build a server bundle for your application, using the `--app` flag to tell the CLI to build the server bundle, referencing its index of `1` in the `"apps"` array in `.angular-cli.json`:
164+
165+
```bash
166+
# This builds the client application in dist/
167+
$ ng build --prod
168+
...
169+
# This builds the server bundle in dist-server/
170+
$ ng build --prod --app 1
171+
Date: 2017-07-24T22:42:09.739Z
172+
Hash: 9cac7d8e9434007fd8da
173+
Time: 4933ms
174+
chunk {0} main.988d7a161bd984b7eb54.bundle.js (main) 9.49 kB [entry] [rendered]
175+
chunk {1} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes [entry] [rendered]
176+
```
177+
178+
## Testing the bundle
179+
180+
With this bundle built, you can use `renderModuleFactory` from `@angular/platform-server` to test it out.
181+
182+
```javascript
183+
// Load zone.js for the server.
184+
require('zone.js/dist/zone-node');
185+
186+
// Import renderModuleFactory from @angular/platform-server.
187+
var renderModuleFactory = require('@angular/platform-server').renderModuleFactory;
188+
189+
// Import the AOT compiled factory for your AppServerModule.
190+
// This import will change with the hash of your built server bundle.
191+
var AppServerModuleNgFactory = require('./dist-server/main.988d7a161bd984b7eb54.bundle').AppServerModuleNgFactory;
192+
193+
// Load the index.html file.
194+
var index = require('fs').readFileSync('./src/index.html', 'utf8');
195+
196+
// Render to HTML and log it to the console.
197+
renderModuleFactory(AppServerModuleNgFactory, {document: index, url: '/'}).then(html => console.log(html));
198+
```
199+
200+
## Caveats
201+
202+
* Lazy loading is not yet supported, but coming very soon. Currently lazy loaded routes aren't available for prerendering, and you will get a `System is not defined` error.
203+
* The bundle produced has a hash in the filename from webpack. When deploying this to a production server, you will need to ensure the correct bundle is required, either by renaming the file or passing the bundle name as an argument to your server.

0 commit comments

Comments
 (0)