Skip to content

Commit 0762d69

Browse files
authored
build: set up hydration in universal app (#28223)
Reworks the Universal app so that it's runnable and that it serves an SSR-generated `index.html`, allowing us to test hydration locally.
1 parent 47b3be5 commit 0762d69

File tree

10 files changed

+245
-118
lines changed

10 files changed

+245
-118
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"build-docs-content": "ts-node --esm --project scripts/tsconfig.json ./scripts/build-docs-content-main.mts",
2222
"build-and-check-release-output": "ts-node --esm --project scripts/tsconfig.json scripts/build-and-check-release-output.mts",
2323
"dev-app": "ibazel run //src/dev-app:devserver",
24+
"universal-app": "bazel run //src/universal-app:server",
2425
"test": "node ./scripts/run-component-tests.js",
2526
"test-local": "yarn -s test --local",
2627
"test-firefox": "yarn -s test --firefox",

src/universal-app/BUILD.bazel

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test")
1+
load("@bazel_skylib//rules:build_test.bzl", "build_test")
2+
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "npm_package_bin")
23
load("//src/cdk:config.bzl", "CDK_TARGETS")
34
load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_TARGETS")
45
load("//src/material:config.bzl", "MATERIAL_TARGETS")
56
load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_TARGETS")
6-
load("//tools:defaults.bzl", "devmode_esbuild", "ng_module", "sass_binary", "ts_library")
7+
load("//tools:defaults.bzl", "devmode_esbuild", "http_server", "ng_module", "sass_binary", "ts_library")
78
load("//tools/angular:index.bzl", "LINKER_PROCESSED_FW_PACKAGES")
89

910
package(default_visibility = ["//visibility:public"])
@@ -24,7 +25,18 @@ ng_module(
2425
)
2526

2627
ts_library(
27-
name = "server",
28+
name = "client_lib",
29+
srcs = [
30+
"main.ts",
31+
],
32+
deps = [
33+
":kitchen-sink",
34+
"@npm//@angular/platform-browser",
35+
],
36+
)
37+
38+
ts_library(
39+
name = "prerender_lib",
2840
srcs = [
2941
"prerender.ts",
3042
],
@@ -35,22 +47,45 @@ ts_library(
3547
"@npm//@angular/platform-server",
3648
"@npm//@bazel/runfiles",
3749
"@npm//@types/node",
38-
"@npm//reflect-metadata",
39-
"@npm//zone.js",
4050
],
4151
)
4252

4353
sass_binary(
44-
name = "theme_scss",
45-
src = "theme.scss",
54+
name = "styles_scss",
55+
src = "styles.scss",
4656
deps = [
4757
"//src/material:sass_lib",
4858
"//src/material-experimental:sass_lib",
4959
],
5060
)
5161

62+
nodejs_binary(
63+
name = "prerender",
64+
data = [
65+
"index-source.html",
66+
":prerender_bundle",
67+
":styles_scss",
68+
],
69+
entry_point = ":prerender_bundle.js",
70+
templated_args = [
71+
# TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver
72+
# See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324
73+
"--bazel_patch_module_resolver",
74+
],
75+
)
76+
77+
devmode_esbuild(
78+
name = "client_bundle",
79+
entry_points = [":main.ts"],
80+
platform = "browser",
81+
target = "es2016",
82+
deps = LINKER_PROCESSED_FW_PACKAGES + [
83+
":client_lib",
84+
],
85+
)
86+
5287
devmode_esbuild(
53-
name = "server_bundle",
88+
name = "prerender_bundle",
5489
entry_point = ":prerender.ts",
5590
platform = "node",
5691
# We cannot use `ES2017` or higher as that would result in `async/await` not being downleveled.
@@ -59,21 +94,46 @@ devmode_esbuild(
5994
# Note: We add all linker-processed FW packages as dependencies here so that ESBuild will
6095
# map all framework packages to their linker-processed bundles from `tools/angular`.
6196
deps = LINKER_PROCESSED_FW_PACKAGES + [
62-
":server",
97+
":prerender_lib",
6398
],
6499
)
65100

66-
nodejs_test(
67-
name = "server_test",
68-
data = [
69-
"index.html",
70-
":server_bundle",
71-
":theme_scss",
101+
npm_package_bin(
102+
name = "prerender_test_bin",
103+
outs = ["index-prerendered.html"],
104+
args = ["$@"],
105+
tool = ":prerender",
106+
)
107+
108+
npm_package_bin(
109+
name = "debug_prerender_bin",
110+
# Note: the file needs to be called `index.html` specifically so the server can pick it up.
111+
outs = ["index.html"],
112+
args = [
113+
"$@",
114+
"--debug", # Disables some testing behavior that can be annoying while debugging.
72115
],
73-
entry_point = ":server_bundle.js",
74-
templated_args = [
75-
# TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver
76-
# See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324
77-
"--bazel_patch_module_resolver",
116+
tool = ":prerender",
117+
)
118+
119+
build_test(
120+
name = "prerender_test",
121+
targets = [
122+
":prerender_test_bin",
123+
],
124+
)
125+
126+
http_server(
127+
name = "server",
128+
srcs = [
129+
":debug_prerender_bin",
130+
],
131+
additional_root_paths = [
132+
"npm/node_modules",
133+
],
134+
tags = ["manual"],
135+
deps = [
136+
":client_bundle",
137+
":styles_scss",
78138
],
79139
)

src/universal-app/DEBUG.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/universal-app/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
### Server-side debugging app
2+
3+
Application that renders all components on the server and hydrates them on the client. Common tasks:
4+
5+
* Run `yarn universal-app` to start a local server. **Does not support live reload**
6+
* To inspect the server-side-generated HTML, run `yarn universal-app`, visit the local server and
7+
use either the dev tools or "View source" to see the `index.html` provided by the server.
8+
* To test if all components would render on the server, run `yarn bazel test src/universal-app:prerender_test`.

src/universal-app/index.html renamed to src/universal-app/index-source.html

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,18 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>Angular Material Universal Kitchen Sink Test</title>
7-
<link href="theme.css" rel="stylesheet">
7+
<link href="styles.css" rel="stylesheet">
88
<link rel="preconnect" href="https://fonts.gstatic.com">
99
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
1010
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
1111
<base href="/">
12-
13-
<style>
14-
/* Make sure bottom sheet doesn't obscure components. */
15-
body {
16-
padding-bottom: 80px;
17-
}
18-
19-
/*
20-
* Hide the overlay so hover styles can be tested,
21-
* but show a message so we can see that the overlay is there.
22-
*/
23-
.cdk-overlay-backdrop {
24-
bottom: 100vh !important;
25-
}
26-
.cdk-overlay-backdrop::after {
27-
content: 'OVERLAY ACTIVE';
28-
background: lime;
29-
}
30-
</style>
3112
</head>
32-
<body>
13+
<body class="mat-app-background mat-typography">
3314
<kitchen-sink>Loading...</kitchen-sink>
15+
16+
<script src="zone.js/bundles/zone.umd.min.js"></script>
17+
<script src="https://maps.googleapis.com/maps/api/js?libraries=visualization"></script>
18+
<script src="https://unpkg.com/@googlemaps/markerclustererplus/dist/index.min.js"></script>
19+
<script src="client_bundle/main.js" type="module"></script>
3420
</body>
3521
</html>

src/universal-app/kitchen-sink/kitchen-sink.html

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
<h2>Autocomplete</h2>
2-
<mat-autocomplete>
3-
<mat-option>Grace Hopper</mat-option>
4-
<mat-option>Anita Borg</mat-option>
5-
<mat-option>Ada Lovelace</mat-option>
2+
3+
<mat-form-field>
4+
<mat-label>Computer scientists</mat-label>
5+
<input matInput [matAutocomplete]="autocomplete">
6+
</mat-form-field>
7+
8+
<mat-autocomplete #autocomplete>
9+
<mat-option value="Grace Hopper">Grace Hopper</mat-option>
10+
<mat-option value="Anita Borg">Anita Borg</mat-option>
11+
<mat-option value="Ada Lovelace">Ada Lovelace</mat-option>
612
</mat-autocomplete>
713

14+
<h2>Bottom sheet</h2>
15+
<button mat-raised-button (click)="openBottomSheet()">Open bottom sheet</button>
16+
817
<h2>Button</h2>
918

1019
<button mat-button>go</button>
@@ -91,14 +100,17 @@ <h2>Datepicker</h2>
91100
<mat-datepicker #birthday></mat-datepicker>
92101
</mat-form-field>
93102

94-
<h2>Disabled datepicker</h2>
103+
<h3>Disabled datepicker</h3>
95104

96105
<mat-form-field>
97106
<input type="text" disabled matInput [matDatepicker]="departureDate" placeholder="Departure date">
98107
<mat-datepicker-toggle matSuffix [for]="departureDate"></mat-datepicker-toggle>
99108
<mat-datepicker #departureDate></mat-datepicker>
100109
</mat-form-field>
101110

111+
<h2>Dialog</h2>
112+
<button mat-raised-button (click)="openDialog()">Open dialog</button>
113+
102114
<h2>Grid list</h2>
103115

104116
<mat-grid-list cols="4">
@@ -244,12 +256,19 @@ <h2>Select</h2>
244256

245257
<h2>Sidenav</h2>
246258
<mat-sidenav-container>
247-
<mat-sidenav opened>
259+
<!--
260+
TODO: add sample with multiple sidenavs which can be problematic for hydration.
261+
Currently blocked on https://github.com/angular/angular/issues/53246
262+
-->
263+
<!--
264+
Only attempt to capture focus when automated, otherwise it makes the page jump around.
265+
-->
266+
<mat-sidenav #sidenav opened [autoFocus]="isAutomated ? 'first-tabbable' : false">
248267
On the side
249-
<button>Button for testing focus trapping</button>
268+
<button (click)="sidenav.toggle()">Button for testing focus trapping</button>
250269
</mat-sidenav>
251270
Main content
252-
<button>Click me</button>
271+
<button (click)="sidenav.toggle()">Click me</button>
253272
</mat-sidenav-container>
254273

255274
<h2>Slide-toggle</h2>
@@ -272,6 +291,9 @@ <h2>Slider</h2>
272291
<input value="400" matSliderEndThumb>
273292
</mat-slider>
274293

294+
<h2>Snack bar</h2>
295+
<button mat-raised-button (click)="openSnackbar()">Open snackbar</button>
296+
275297
<h2>Tabs</h2>
276298

277299
<!--
@@ -313,21 +335,25 @@ <h2>Toolbar</h2>
313335
<h2>Sort</h2>
314336

315337
<table matSort>
316-
<tr>
317-
<th mat-sort-header="name">Name</th>
318-
<th mat-sort-header="calories">Calories</th>
319-
<th mat-sort-header="fat">Fat</th>
320-
<th mat-sort-header="carbs">Carbs</th>
321-
<th mat-sort-header="protein">Protein</th>
322-
</tr>
323-
324-
<tr>
325-
<td>Cupcake</td>
326-
<td>305</td>
327-
<td>4</td>
328-
<td>67</td>
329-
<td>4</td>
330-
</tr>
338+
<thead>
339+
<tr>
340+
<th mat-sort-header="name">Name</th>
341+
<th mat-sort-header="calories">Calories</th>
342+
<th mat-sort-header="fat">Fat</th>
343+
<th mat-sort-header="carbs">Carbs</th>
344+
<th mat-sort-header="protein">Protein</th>
345+
</tr>
346+
</thead>
347+
348+
<tbody>
349+
<tr>
350+
<td>Cupcake</td>
351+
<td>305</td>
352+
<td>4</td>
353+
<td>67</td>
354+
<td>4</td>
355+
</tr>
356+
</tbody>
331357
</table>
332358

333359
<h2>Tooltip</h2>
@@ -453,7 +479,8 @@ <h2>YouTube player</h2>
453479
<youtube-player videoId="dQw4w9WgXcQ"></youtube-player>
454480

455481
<h2>Google Map</h2>
456-
<google-map height="400px" width="750px">
482+
<!-- position: relative prevents the "Map failed to load" element from leaving the container -->
483+
<google-map height="400px" width="750px" style="position: relative">
457484
<map-marker [position]="{lat: 24, lng: 12}"></map-marker>
458485
<map-info-window>Hello</map-info-window>
459486
<map-polyline [options]="{

0 commit comments

Comments
 (0)