diff --git a/package.json b/package.json index 9e2c64e67579..e785772c12b6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/universal-app/BUILD.bazel b/src/universal-app/BUILD.bazel index f981c0546e08..ae0c52b272fa 100644 --- a/src/universal-app/BUILD.bazel +++ b/src/universal-app/BUILD.bazel @@ -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"]) @@ -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", ], @@ -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. @@ -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", ], ) diff --git a/src/universal-app/DEBUG.md b/src/universal-app/DEBUG.md deleted file mode 100644 index cf1190863bd3..000000000000 --- a/src/universal-app/DEBUG.md +++ /dev/null @@ -1,10 +0,0 @@ -### Debugging the pre-rendered HTML file - -Since the pre-rendered HTML file is built through a Bazel test target, the -generated HTML file will not be stored in a folder of the repository. Instead, -the file will be stored in the `bazel-out` folder. - -You can retrieve the path to the file by either running: - -* `bazel run //src/universal-app:server_test --test_output=all` -* `echo $(bazel info bazel-testlogs)/src/universal-app/server_test/test.outputs/index-prerendered.html` diff --git a/src/universal-app/README.md b/src/universal-app/README.md new file mode 100644 index 000000000000..7236f8ac0e04 --- /dev/null +++ b/src/universal-app/README.md @@ -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`. diff --git a/src/universal-app/index.html b/src/universal-app/index-source.html similarity index 53% rename from src/universal-app/index.html rename to src/universal-app/index-source.html index 7a2332c9b242..793b41b6725c 100644 --- a/src/universal-app/index.html +++ b/src/universal-app/index-source.html @@ -4,32 +4,18 @@ Angular Material Universal Kitchen Sink Test - + - - - + Loading... + + + + + diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index e056f2e7b0d8..0577c3829a1a 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -1,10 +1,19 @@

Autocomplete

- - Grace Hopper - Anita Borg - Ada Lovelace + + + Computer scientists + + + + + Grace Hopper + Anita Borg + Ada Lovelace +

Bottom sheet

+ +

Button

@@ -91,7 +100,7 @@

Datepicker

-

Disabled datepicker

+

Disabled datepicker

@@ -99,6 +108,9 @@

Disabled datepicker

+

Dialog

+ +

Grid list

@@ -244,12 +256,19 @@

Select

Sidenav

- + + + On the side - + Main content - +

Slide-toggle

@@ -272,6 +291,9 @@

Slider

+

Snack bar

+ +

Tabs

+ Hello writeFileSync(outputPath, content)) + .catch(error => { + // If rendering fails, print the error and exit the process with a non-zero exit code. + console.error(error); + process.exit(1); + }); function bootstrap() { return bootstrapApplication(KitchenSink, { providers: [ provideNoopAnimations(), + provideServerRendering(), provideClientHydration(), { - // If an error is thrown asynchronously during server-side rendering it'll get logged to - // stderr, but it won't cause the build to fail. We still want to catch these errors so we - // provide an ErrorHandler that rethrows the error and causes the process to exit correctly. + provide: AUTOMATED_KITCHEN_SINK, + useValue: !isDebugMode, + }, + { + // If an error is thrown asynchronously during server-side rendering + // it'll get logged to stderr, but it won't cause the build to fail. + // We still want to catch these errors so we provide an ErrorHandler + // that rethrows the error and causes the process to exit correctly. provide: ErrorHandler, useValue: { handleError: (error: Error) => { @@ -37,25 +58,3 @@ function bootstrap() { ], }); } - -const result = renderApplication(bootstrap, { - document: readFileSync(indexHtmlPath, 'utf-8'), -}); -const outDir = process.env['TEST_UNDECLARED_OUTPUTS_DIR'] as string; - -result - .then(content => { - const filename = join(outDir, 'index-prerendered.html'); - const themeFilename = join(outDir, 'theme.css'); - - console.log('Inspect pre-rendered page here:'); - console.log(`file://${filename}`); - writeFileSync(filename, content, 'utf-8'); - writeFileSync(themeFilename, readFileSync(themeCssPath, 'utf-8'), 'utf-8'); - console.log('Prerender done.'); - }) - // If rendering fails, print the error and exit the process with a non-zero exit code. - .catch(error => { - console.error(error); - process.exit(1); - }); diff --git a/src/universal-app/theme.scss b/src/universal-app/styles.scss similarity index 59% rename from src/universal-app/theme.scss rename to src/universal-app/styles.scss index 5e44bb588c60..9bab6949a03d 100644 --- a/src/universal-app/theme.scss +++ b/src/universal-app/styles.scss @@ -14,10 +14,28 @@ mat.$theme-legacy-inspection-api-compatibility: false; $candy-app-primary: mat.define-palette(mat.$indigo-palette); $candy-app-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); $candy-app-theme: mat.define-light-theme(( - color: (primary: $candy-app-primary, accent: $candy-app-accent), - typography: mat.define-typography-config(), - density: 0 + color: (primary: $candy-app-primary, accent: $candy-app-accent), + typography: mat.define-typography-config(), + density: 0 )); // Include the default theme styles. @include mat.all-component-themes($candy-app-theme); +@include mat.typography-hierarchy($candy-app-theme); + +body.test-automated { + // Make sure bottom sheet doesn't obscure components. + 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; + } +} +