Skip to content

Commit c09ca89

Browse files
flolualexeagle
authored andcommitted
feat(examples): add support for server side rendering with universal
1 parent dfd9aea commit c09ca89

9 files changed

Lines changed: 154 additions & 5 deletions

File tree

examples/angular/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This example is a monorepo, meant to show many different features and integratio
2424
- **Lazy loading**: in production mode, the application is served in chunks. Run `ng serve --prod`
2525
- **Differential loading**: in production mode, we load a pair of `<script>` tags. Modern browsers will load code in the ES2015 syntax, which is smaller and requires fewer polyfills. Older browsers will load ES5 syntax.
2626
- **Docker**: see below where we package up the production app for deployment on Kubernetes.
27+
- **Server Side Rendering**: with the help of Angular Universal you can render your application on the server
2728

2829
## Installation
2930

@@ -97,6 +98,14 @@ $ ng serve --prod
9798
$ bazel run //src:prodserver
9899
```
99100

101+
You can also use server side rendering.
102+
103+
```bash
104+
$ yarn server-ssr
105+
# or
106+
$ bazel run //src:universal_server
107+
```
108+
100109
### Code splitting
101110

102111
The production bundle is code split and routes such as `/` and `/todos`
@@ -128,6 +137,7 @@ We use the standard firebase deploy command.
128137
Run `yarn deploy` to release changes to bazel.angular.io.
129138

130139
### Kubernetes Engine
140+
131141
We use Bazel's docker support to package up our production server for deployment.
132142
Each time the app changes, we'll get a slim new docker layer with just the modified files, keeping the round-trip for deployment incremental and fast.
133143
This example is configured to run on Google Kubernetes Engine, so we can have an elastic pool of backend machines behind a load balancer.
@@ -146,9 +156,9 @@ Deploy to production:
146156

147157
1. Install gcloud and kubectl
148158
1. Authenticate to the Google Container Registry
149-
`gcloud auth configure-docker`
159+
`gcloud auth configure-docker`
150160
1. Authenticate to Kubernetes Engine
151-
`gcloud container clusters get-credentials angular-bazel-example --zone=us-west1-a`
161+
`gcloud container clusters get-credentials angular-bazel-example --zone=us-west1-a`
152162
1. For the first deployment: `bazel run :deploy.create`
153163
1. To update: `bazel run :deploy.replace`
154164

examples/angular/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
"@angular/material": "9.0.0",
1717
"@angular/platform-browser": "9.0.0",
1818
"@angular/platform-browser-dynamic": "9.0.0",
19+
"@angular/platform-server": "^9.0.0",
1920
"@angular/router": "9.0.0",
2021
"@ngrx/store": "9.0.0-beta.0",
22+
"@nguniversal/express-engine": "^9.0.0",
2123
"date-fns": "1.30.1",
2224
"rxjs": "6.5.3",
2325
"systemjs": "6.1.2",
@@ -66,6 +68,7 @@
6668
"serve": "ibazel run //src:devserver",
6769
"deploy": "ng build && firebase deploy",
6870
"serve-prod": "bazelisk run //src:prodserver",
71+
"serve-ssr": "bazelisk run //src:universal_server",
6972
"e2e": "bazelisk test //e2e:all",
7073
"test": "bazelisk test //src/...",
7174
"benchmark": "ibazel-benchmark-runner //src:devserver src/app/hello-world/hello-world.component.ts --url=http://localhost:5432",

examples/angular/src/BUILD.bazel

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web")
1+
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "pkg_web")
22
load("@io_bazel_rules_docker//container:container.bzl", "container_image")
33
load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image")
44
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
@@ -18,6 +18,12 @@ ts_config(
1818
deps = [":tsconfig.json"],
1919
)
2020

21+
ts_config(
22+
name = "tsconfig-server",
23+
src = "tsconfig.server.json",
24+
deps = [":tsconfig.json"],
25+
)
26+
2127
# Run the sass compiler to output "styles.css"
2228
# TODO(alexeagle): demonstrate the sass_library rule too
2329
sass_binary(
@@ -232,3 +238,25 @@ container_image(
232238
tags = ["local"],
233239
workdir = "/app/src/nodejs_image.binary.runfiles/examples_angular",
234240
)
241+
242+
ts_library(
243+
name = "universal_server_lib",
244+
srcs = [
245+
"server.ts",
246+
],
247+
deps = [
248+
"//src/app:app_server",
249+
"@npm//@nguniversal/express-engine",
250+
"@npm//@types/node",
251+
"@npm//express",
252+
],
253+
)
254+
255+
nodejs_binary(
256+
name = "universal_server",
257+
data = [
258+
":prodapp",
259+
":universal_server_lib",
260+
],
261+
entry_point = ":server.ts",
262+
)

examples/angular/src/app/BUILD.bazel

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ package(default_visibility = ["//:__subpackages__"])
44

55
ng_module(
66
name = "app",
7-
srcs = glob(["*.ts"]),
7+
srcs = glob(
8+
include = ["*.ts"],
9+
exclude = ["app.server.module.ts"],
10+
),
811
assets = ["app.component.html"],
912
tsconfig = "//src:tsconfig.json",
1013
deps = [
@@ -18,3 +21,13 @@ ng_module(
1821
"@npm//@ngrx/store",
1922
],
2023
)
24+
25+
ng_module(
26+
name = "app_server",
27+
srcs = ["app.server.module.ts"],
28+
tsconfig = "//src:tsconfig-server",
29+
deps = [
30+
":app",
31+
"@npm//@angular/platform-server",
32+
],
33+
)

examples/angular/src/app/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {todoReducer} from './todos/reducers/reducers';
1515
declarations: [AppComponent],
1616
imports: [
1717
AppRoutingModule, BrowserModule, BrowserAnimationsModule, MaterialModule, HomeModule,
18-
StoreModule.forRoot({todoReducer})
18+
StoreModule.forRoot({todoReducer}),
19+
BrowserModule.withServerTransition({ appId: 'angular-bazel-example' })
1920
],
2021
exports: [AppComponent],
2122
bootstrap: [AppComponent],
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {NgModule} from '@angular/core';
2+
import {ServerModule} from '@angular/platform-server';
3+
4+
import {AppModule} from './app.module';
5+
import {AppComponent} from './app.component';
6+
7+
@NgModule({
8+
imports: [AppModule, ServerModule],
9+
bootstrap: [AppComponent]
10+
})
11+
export class AppServerModule {}

examples/angular/src/server.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
///<reference types="node"/>
2+
import "zone.js/dist/zone-node";
3+
4+
import { ngExpressEngine } from "@nguniversal/express-engine";
5+
import * as express from "express";
6+
import { join } from "path";
7+
8+
const app = express();
9+
10+
const PORT = process.env.PORT || 4000;
11+
const DIST_FOLDER = join(process.cwd(), "src/prodapp");
12+
13+
import { AppServerModule } from "./app/app.server.module";
14+
15+
app.engine(
16+
"html",
17+
ngExpressEngine({
18+
bootstrap: AppServerModule,
19+
providers: [
20+
// TODO add support for lazy loading with server side rendering
21+
// provideModuleMap(LAZY_MODULE_MAP)
22+
]
23+
}) as any
24+
);
25+
26+
app.set("view engine", "html");
27+
app.set("views", DIST_FOLDER);
28+
29+
app.get("*.*", express.static(DIST_FOLDER, { maxAge: "1y" }));
30+
31+
// catch /favicon.ico route to prevent the following server error:
32+
// Error: Cannot match any routes. URL Segment: 'favicon.ico'
33+
app.get("/favicon.ico", (req, res) => res.send(""));
34+
35+
app.get("*", (req, res) => {
36+
res.render("example/index", { req });
37+
});
38+
39+
app.listen(PORT, () => {
40+
console.log(`Node Express server listening on http://localhost:${PORT}`);
41+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "./out-tsc/app-server",
5+
"module": "commonjs",
6+
"types": ["node"]
7+
},
8+
"files": ["src/main.server.ts", "server.ts"],
9+
"angularCompilerOptions": {
10+
"entryModule": "./src/app/app.server.module#AppServerModule"
11+
}
12+
}

examples/angular/yarn.lock

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@
129129
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-9.0.0.tgz#b9454f29d8edaf024668baa9e07083eef73deac2"
130130
integrity sha512-2PR/o57HjZvKEnAF8ODeqxmeC90oth9dLTMrJNoI5MET0IeErKeI/9Sl5cLQuXC+lSVN5rOMCvDb74VWSno5yw==
131131

132+
"@angular/platform-server@^9.0.0":
133+
version "9.1.0"
134+
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-9.1.0.tgz#a4d5b20724acd54219603e32cee88b0b58ee6617"
135+
integrity sha512-JboTIUBgf9yHRjF93q8NJQoeIp5Zp4kg8Nmw1QLWO6SV+OpiOpKSrlScMVncCh0KiU6SIOmiuHJwBhLm78fi+Q==
136+
dependencies:
137+
domino "^2.1.2"
138+
xhr2 "^0.1.4"
139+
132140
"@angular/router@9.0.0":
133141
version "9.0.0"
134142
resolved "https://registry.yarnpkg.com/@angular/router/-/router-9.0.0.tgz#11784fc8ce9cb3314c7ec1083ff9be7c611181c2"
@@ -901,6 +909,18 @@
901909
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-9.0.0-beta.0.tgz#b352f0394b1b652ee2ae2039ba1757a704290846"
902910
integrity sha512-yfw+Qwiu3fNjjRD1B2ZSGpjd3a70fLjFfqhu2H636giia5cTnqbzIqOH3EnZzapADzwL6M7yy7tixWpJ24lbpQ==
903911

912+
"@nguniversal/common@9.1.0":
913+
version "9.1.0"
914+
resolved "https://registry.yarnpkg.com/@nguniversal/common/-/common-9.1.0.tgz#01ac2d2c47c6d04f955f1713697377afcaa1ad91"
915+
integrity sha512-Pvb3KhuV44PxLmVOf1dqnuckdaNdfT5tbiUu2/vVbdtyFdQpF40D1Zx4RumRymK0ZzTJGQsJtJSi2DJvvGgwMg==
916+
917+
"@nguniversal/express-engine@^9.0.0":
918+
version "9.1.0"
919+
resolved "https://registry.yarnpkg.com/@nguniversal/express-engine/-/express-engine-9.1.0.tgz#ddc1cecb4134a365142a9ba6ebb6ae8f99cb2ad1"
920+
integrity sha512-uRb8dJD3huk42eY1iOpmr8yfq/LTSOoUfKY4eVREE7nMePyZNSRQHNCEybHDTNh43FvTN38u+cA4dTlmMFD5GQ==
921+
dependencies:
922+
"@nguniversal/common" "9.1.0"
923+
904924
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
905925
version "1.1.2"
906926
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
@@ -2407,6 +2427,11 @@ dom-serialize@^2.2.0:
24072427
extend "^3.0.0"
24082428
void-elements "^2.0.0"
24092429

2430+
domino@^2.1.2:
2431+
version "2.1.4"
2432+
resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.4.tgz#78922e7fab7c610f35792b6c745b7962d342e9c4"
2433+
integrity sha512-l70mlQ7IjPKC8kT7GljQXJZmt5OqFL+RE91ik5y5WWQtsd9wP8R7gpFnNu96fK5MqAAZRXfLLsnzKtkty5fWGQ==
2434+
24102435
dot-prop@^4.1.0:
24112436
version "4.2.0"
24122437
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
@@ -7280,6 +7305,11 @@ xdg-basedir@^3.0.0:
72807305
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
72817306
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
72827307

7308+
xhr2@^0.1.4:
7309+
version "0.1.4"
7310+
resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f"
7311+
integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8=
7312+
72837313
xml2js@^0.4.17:
72847314
version "0.4.22"
72857315
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02"

0 commit comments

Comments
 (0)