Skip to content

Commit

Permalink
build: set up hydration in universal app (#28223)
Browse files Browse the repository at this point in the history
Reworks the Universal app so that it's runnable and that it serves an SSR-generated `index.html`, allowing us to test hydration locally.
  • Loading branch information
crisbeto authored Dec 4, 2023
1 parent 47b3be5 commit 0762d69
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 118 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"build-docs-content": "ts-node --esm --project scripts/tsconfig.json ./scripts/build-docs-content-main.mts",
"build-and-check-release-output": "ts-node --esm --project scripts/tsconfig.json scripts/build-and-check-release-output.mts",
"dev-app": "ibazel run //src/dev-app:devserver",
"universal-app": "bazel run //src/universal-app:server",
"test": "node ./scripts/run-component-tests.js",
"test-local": "yarn -s test --local",
"test-firefox": "yarn -s test --firefox",
Expand Down
100 changes: 80 additions & 20 deletions src/universal-app/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test")
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "npm_package_bin")
load("//src/cdk:config.bzl", "CDK_TARGETS")
load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_TARGETS")
load("//src/material:config.bzl", "MATERIAL_TARGETS")
load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_TARGETS")
load("//tools:defaults.bzl", "devmode_esbuild", "ng_module", "sass_binary", "ts_library")
load("//tools:defaults.bzl", "devmode_esbuild", "http_server", "ng_module", "sass_binary", "ts_library")
load("//tools/angular:index.bzl", "LINKER_PROCESSED_FW_PACKAGES")

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

ts_library(
name = "server",
name = "client_lib",
srcs = [
"main.ts",
],
deps = [
":kitchen-sink",
"@npm//@angular/platform-browser",
],
)

ts_library(
name = "prerender_lib",
srcs = [
"prerender.ts",
],
Expand All @@ -35,22 +47,45 @@ ts_library(
"@npm//@angular/platform-server",
"@npm//@bazel/runfiles",
"@npm//@types/node",
"@npm//reflect-metadata",
"@npm//zone.js",
],
)

sass_binary(
name = "theme_scss",
src = "theme.scss",
name = "styles_scss",
src = "styles.scss",
deps = [
"//src/material:sass_lib",
"//src/material-experimental:sass_lib",
],
)

nodejs_binary(
name = "prerender",
data = [
"index-source.html",
":prerender_bundle",
":styles_scss",
],
entry_point = ":prerender_bundle.js",
templated_args = [
# TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver
# See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324
"--bazel_patch_module_resolver",
],
)

devmode_esbuild(
name = "client_bundle",
entry_points = [":main.ts"],
platform = "browser",
target = "es2016",
deps = LINKER_PROCESSED_FW_PACKAGES + [
":client_lib",
],
)

devmode_esbuild(
name = "server_bundle",
name = "prerender_bundle",
entry_point = ":prerender.ts",
platform = "node",
# We cannot use `ES2017` or higher as that would result in `async/await` not being downleveled.
Expand All @@ -59,21 +94,46 @@ devmode_esbuild(
# Note: We add all linker-processed FW packages as dependencies here so that ESBuild will
# map all framework packages to their linker-processed bundles from `tools/angular`.
deps = LINKER_PROCESSED_FW_PACKAGES + [
":server",
":prerender_lib",
],
)

nodejs_test(
name = "server_test",
data = [
"index.html",
":server_bundle",
":theme_scss",
npm_package_bin(
name = "prerender_test_bin",
outs = ["index-prerendered.html"],
args = ["$@"],
tool = ":prerender",
)

npm_package_bin(
name = "debug_prerender_bin",
# Note: the file needs to be called `index.html` specifically so the server can pick it up.
outs = ["index.html"],
args = [
"$@",
"--debug", # Disables some testing behavior that can be annoying while debugging.
],
entry_point = ":server_bundle.js",
templated_args = [
# TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver
# See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324
"--bazel_patch_module_resolver",
tool = ":prerender",
)

build_test(
name = "prerender_test",
targets = [
":prerender_test_bin",
],
)

http_server(
name = "server",
srcs = [
":debug_prerender_bin",
],
additional_root_paths = [
"npm/node_modules",
],
tags = ["manual"],
deps = [
":client_bundle",
":styles_scss",
],
)
10 changes: 0 additions & 10 deletions src/universal-app/DEBUG.md

This file was deleted.

8 changes: 8 additions & 0 deletions src/universal-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Server-side debugging app

Application that renders all components on the server and hydrates them on the client. Common tasks:

* Run `yarn universal-app` to start a local server. **Does not support live reload**
* To inspect the server-side-generated HTML, run `yarn universal-app`, visit the local server and
use either the dev tools or "View source" to see the `index.html` provided by the server.
* To test if all components would render on the server, run `yarn bazel test src/universal-app:prerender_test`.
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,18 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Angular Material Universal Kitchen Sink Test</title>
<link href="theme.css" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<base href="/">

<style>
/* Make sure bottom sheet doesn't obscure components. */
body {
padding-bottom: 80px;
}

/*
* Hide the overlay so hover styles can be tested,
* but show a message so we can see that the overlay is there.
*/
.cdk-overlay-backdrop {
bottom: 100vh !important;
}
.cdk-overlay-backdrop::after {
content: 'OVERLAY ACTIVE';
background: lime;
}
</style>
</head>
<body>
<body class="mat-app-background mat-typography">
<kitchen-sink>Loading...</kitchen-sink>

<script src="zone.js/bundles/zone.umd.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?libraries=visualization"></script>
<script src="https://unpkg.com/@googlemaps/markerclustererplus/dist/index.min.js"></script>
<script src="client_bundle/main.js" type="module"></script>
</body>
</html>
75 changes: 51 additions & 24 deletions src/universal-app/kitchen-sink/kitchen-sink.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
<h2>Autocomplete</h2>
<mat-autocomplete>
<mat-option>Grace Hopper</mat-option>
<mat-option>Anita Borg</mat-option>
<mat-option>Ada Lovelace</mat-option>

<mat-form-field>
<mat-label>Computer scientists</mat-label>
<input matInput [matAutocomplete]="autocomplete">
</mat-form-field>

<mat-autocomplete #autocomplete>
<mat-option value="Grace Hopper">Grace Hopper</mat-option>
<mat-option value="Anita Borg">Anita Borg</mat-option>
<mat-option value="Ada Lovelace">Ada Lovelace</mat-option>
</mat-autocomplete>

<h2>Bottom sheet</h2>
<button mat-raised-button (click)="openBottomSheet()">Open bottom sheet</button>

<h2>Button</h2>

<button mat-button>go</button>
Expand Down Expand Up @@ -91,14 +100,17 @@ <h2>Datepicker</h2>
<mat-datepicker #birthday></mat-datepicker>
</mat-form-field>

<h2>Disabled datepicker</h2>
<h3>Disabled datepicker</h3>

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

<h2>Dialog</h2>
<button mat-raised-button (click)="openDialog()">Open dialog</button>

<h2>Grid list</h2>

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

<h2>Sidenav</h2>
<mat-sidenav-container>
<mat-sidenav opened>
<!--
TODO: add sample with multiple sidenavs which can be problematic for hydration.
Currently blocked on https://github.com/angular/angular/issues/53246
-->
<!--
Only attempt to capture focus when automated, otherwise it makes the page jump around.
-->
<mat-sidenav #sidenav opened [autoFocus]="isAutomated ? 'first-tabbable' : false">
On the side
<button>Button for testing focus trapping</button>
<button (click)="sidenav.toggle()">Button for testing focus trapping</button>
</mat-sidenav>
Main content
<button>Click me</button>
<button (click)="sidenav.toggle()">Click me</button>
</mat-sidenav-container>

<h2>Slide-toggle</h2>
Expand All @@ -272,6 +291,9 @@ <h2>Slider</h2>
<input value="400" matSliderEndThumb>
</mat-slider>

<h2>Snack bar</h2>
<button mat-raised-button (click)="openSnackbar()">Open snackbar</button>

<h2>Tabs</h2>

<!--
Expand Down Expand Up @@ -313,21 +335,25 @@ <h2>Toolbar</h2>
<h2>Sort</h2>

<table matSort>
<tr>
<th mat-sort-header="name">Name</th>
<th mat-sort-header="calories">Calories</th>
<th mat-sort-header="fat">Fat</th>
<th mat-sort-header="carbs">Carbs</th>
<th mat-sort-header="protein">Protein</th>
</tr>

<tr>
<td>Cupcake</td>
<td>305</td>
<td>4</td>
<td>67</td>
<td>4</td>
</tr>
<thead>
<tr>
<th mat-sort-header="name">Name</th>
<th mat-sort-header="calories">Calories</th>
<th mat-sort-header="fat">Fat</th>
<th mat-sort-header="carbs">Carbs</th>
<th mat-sort-header="protein">Protein</th>
</tr>
</thead>

<tbody>
<tr>
<td>Cupcake</td>
<td>305</td>
<td>4</td>
<td>67</td>
<td>4</td>
</tr>
</tbody>
</table>

<h2>Tooltip</h2>
Expand Down Expand Up @@ -453,7 +479,8 @@ <h2>YouTube player</h2>
<youtube-player videoId="dQw4w9WgXcQ"></youtube-player>

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

0 comments on commit 0762d69

Please sign in to comment.