Skip to content

Commit e12c3a5

Browse files
docs: Update Universal Story to include module map
@nguniversal/module-map-ngfactory-loader has been release and with it, support for Lazy loading! This PR updates the existing story to guide users how they can use this functionality.
1 parent 3af138f commit e12c3a5

File tree

1 file changed

+103
-14
lines changed

1 file changed

+103
-14
lines changed

docs/documentation/stories/universal-rendering.md

+103-14
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@ or
1616
$ yarn add @angular/platform-server --dev
1717
```
1818

19+
## Step 1: Install `@nguniversal/express-engine` and `@nguniversal/express-engine`
1920

20-
## Step 1: Prepare your app for Universal rendering
21+
```bash
22+
$ npm install --save @nguniversal/express-engine @nguniversal/module-map-ngfactory-loader
23+
```
24+
or
25+
```bash
26+
$ yarn add @nguniversal/express-engine @nguniversal/module-map-ngfactory-loader
27+
```
28+
29+
## Step 2: Prepare your app for Universal rendering
2130

2231
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:
2332

@@ -49,6 +58,7 @@ This example places it alongside `app.module.ts` in a file named `app.server.mod
4958
```javascript
5059
import {NgModule} from '@angular/core';
5160
import {ServerModule} from '@angular/platform-server';
61+
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';
5262

5363
import {AppModule} from './app.module';
5464
import {AppComponent} from './app.component';
@@ -59,6 +69,7 @@ import {AppComponent} from './app.component';
5969
// by the ServerModule from @angular/platform-server.
6070
AppModule,
6171
ServerModule,
72+
ModuleMapLoaderModule
6273
],
6374
// Since the bootstrapped component is not inherited from your
6475
// imported AppModule, it needs to be repeated here.
@@ -67,7 +78,7 @@ import {AppComponent} from './app.component';
6778
export class AppServerModule {}
6879
```
6980

70-
## Step 2: Create a server main file and tsconfig to build it
81+
## Step 3: Create a server main file and tsconfig to build it
7182

7283
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`:
7384

@@ -105,7 +116,7 @@ Add a section for `"angularCompilerOptions"` and set `"entryModule"` to your `Ap
105116
}
106117
```
107118

108-
## Step 3: Create a new project in `.angular-cli.json`
119+
## Step 4: Create a new project in `.angular-cli.json`
109120

110121
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"`.
111122

@@ -127,7 +138,7 @@ Then, remove the `"polyfills"` key - those aren't needed on the server, and adju
127138
"root": "src",
128139
// Build to dist-server instead of dist. This prevents
129140
// client and server builds from overwriting each other.
130-
"outDir": "dist-server",
141+
"outDir": "dist/dist-server",
131142
"assets": [
132143
"assets",
133144
"favicon.ico"
@@ -166,8 +177,8 @@ With these steps complete, you should be able to build a server bundle for your
166177
# This builds the client application in dist/
167178
$ ng build --prod
168179
...
169-
# This builds the server bundle in dist-server/
170-
$ ng build --prod --app 1
180+
# This builds the server bundle in dist/dist-server/ and disables output hashing
181+
$ ng build --prod --app 1 --output-hashing=false
171182
Date: 2017-07-24T22:42:09.739Z
172183
Hash: 9cac7d8e9434007fd8da
173184
Time: 4933ms
@@ -179,25 +190,103 @@ chunk {1} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes [entry] [rende
179190

180191
With this bundle built, you can use `renderModuleFactory` from `@angular/platform-server` to test it out.
181192

193+
### prerender.js:
194+
182195
```javascript
183196
// Load zone.js for the server.
184197
require('zone.js/dist/zone-node');
198+
require('reflect-metadata')
199+
const fs = require('fs');
185200

186201
// Import renderModuleFactory from @angular/platform-server.
187-
var renderModuleFactory = require('@angular/platform-server').renderModuleFactory;
202+
const { renderModuleFactory } = require('@angular/platform-server');
203+
204+
// Import module map for lazy loading
205+
const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');
188206

189207
// Import the AOT compiled factory for your AppServerModule.
190208
// This import will change with the hash of your built server bundle.
191-
var AppServerModuleNgFactory = require('./dist-server/main.988d7a161bd984b7eb54.bundle').AppServerModuleNgFactory;
209+
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(`./dist/dist-server/main.bundle`);
210+
211+
// Load the index.html file containing referances to your application bundle.
212+
const index = fs.readFileSync('./dist/index.html', 'utf8');
213+
214+
// Writes rendered HTML to ./dist/index.html, replacing the file if it already exists.
215+
renderModuleFactory(AppServerModuleNgFactory, {
216+
document: index,
217+
url: '/',
218+
extraProviders: [
219+
provideModuleMap(LAZY_MODULE_MAP)
220+
]
221+
})
222+
.then(html => fs.writeFileSync('./dist/index.html', html));
223+
```
224+
225+
After pre rendering the application with `renderModuleFactory` we can now serve the application, to do this create a server.js file within the dist folder
192226

193-
// Load the index.html file.
194-
var index = require('fs').readFileSync('./src/index.html', 'utf8');
227+
### server.js:
228+
229+
```javascript
230+
require('zone.js/dist/zone-node');
231+
require('reflect-metadata');
232+
const express = require('express');
233+
const fs = require('fs');
195234

196-
// Render to HTML and log it to the console.
197-
renderModuleFactory(AppServerModuleNgFactory, {document: index, url: '/'}).then(html => console.log(html));
235+
const { platformServer, renderModuleFactory } = require('@angular/platform-server');
236+
const { ngExpressEngine } = require('@nguniversal/express-engine');
237+
// Import module map for lazy loading
238+
const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');
239+
240+
// Import the AOT compiled factory for your AppServerModule.
241+
// This import will change with the hash of your built server bundle.
242+
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(`./dist-server/main.bundle`);
243+
244+
const app = express();
245+
const port = 8000;
246+
const baseUrl = `http://localhost:${port}`;
247+
248+
// Set the engine
249+
app.engine('html', ngExpressEngine({
250+
bootstrap: AppServerModuleNgFactory,
251+
providers: [
252+
provideModuleMap(LAZY_MODULE_MAP)
253+
]
254+
}));
255+
256+
app.set('view engine', 'html');
257+
258+
app.set('views', './');
259+
app.use('/', express.static('./', {index: false}));
260+
261+
app.get('*', (req, res) => {
262+
res.render('index', {
263+
req,
264+
res
265+
});
266+
});
267+
268+
app.listen(port, () => {
269+
console.log(`Listening at ${baseUrl}`);
270+
});
271+
```
272+
273+
Test it out by running the server
274+
```bash
275+
$ cd dist && node server.js
276+
```
277+
278+
An easy way to build and serve this application is to add the following script to package.json
279+
280+
DOS
281+
```bash
282+
$ "build:ssr": "ng build --prod && ng build --prod --app 1 --output-hashing=false && node prerender && copy server.js dist/server.js",
283+
```
284+
or
285+
BASH
286+
```bash
287+
$ "build:ssr": "ng build --prod && ng build --prod --app 1 --output-hashing=false && node prerender && cp server.js dist/server.js",
198288
```
199289

200290
## Caveats
201291

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.
292+
* 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)