diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b15efac1991..eb4fa204ac0c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -255,20 +255,6 @@ jobs: - run: ./scripts/circleci/run-saucelabs-tests.sh - # ------------------------------------------------------------------------- - # Job that pre-render's the universal app with `@angular/platform-server`. - # This verifies that Angular Material can be rendered within Node. - # ------------------------------------------------------------------------- - prerender_build: - <<: *job_defaults - steps: - - *checkout_code - - *restore_cache - - *yarn_download - - *yarn_install - - - run: yarn gulp ci:prerender - # ---------------------------------- # Lint job. Runs the gulp lint task. # ---------------------------------- @@ -523,8 +509,6 @@ workflows: jobs: - e2e_tests: filters: *ignore_presubmit_branch_filter - - prerender_build: - filters: *ignore_presubmit_branch_filter release_output: jobs: diff --git a/src/universal-app/BUILD.bazel b/src/universal-app/BUILD.bazel new file mode 100644 index 000000000000..0c04c093efe8 --- /dev/null +++ b/src/universal-app/BUILD.bazel @@ -0,0 +1,57 @@ +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_test") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load("//:packages.bzl", "CDK_EXPERIMENTAL_TARGETS", "CDK_TARGETS", "MATERIAL_EXPERIMENTAL_SCSS_LIBS", "MATERIAL_EXPERIMENTAL_TARGETS", "MATERIAL_TARGETS") +load("//tools:defaults.bzl", "ng_module", "ts_library") + +ng_module( + name = "kitchen-sink", + srcs = [ + "kitchen-sink-mdc/kitchen-sink-mdc.ts", + "kitchen-sink-root.ts", + "kitchen-sink/kitchen-sink.ts", + ], + assets = [ + "kitchen-sink/kitchen-sink.html", + "kitchen-sink-mdc/kitchen-sink-mdc.html", + ], + deps = [ + "@npm//@angular/platform-server", + ] + CDK_TARGETS + CDK_EXPERIMENTAL_TARGETS + MATERIAL_TARGETS + MATERIAL_EXPERIMENTAL_TARGETS, +) + +ts_library( + name = "server", + srcs = [ + "prerender.ts", + ], + deps = [ + ":kitchen-sink", + "@npm//@angular/platform-server", + "@npm//@types/node", + "@npm//reflect-metadata", + "@npm//zone.js", + ], +) + +sass_binary( + name = "theme_scss", + src = "theme.scss", + include_paths = [ + "external/npm/node_modules", + ], + deps = [ + "//src/material/core:all_themes", + ] + MATERIAL_EXPERIMENTAL_SCSS_LIBS, +) + +nodejs_test( + name = "server_test", + data = [ + "index.html", + ":server", + ":theme_scss", + ], + entry_point = ":prerender.ts", +) diff --git a/src/universal-app/DEBUG.md b/src/universal-app/DEBUG.md new file mode 100644 index 000000000000..24fff3e3a8c1 --- /dev/null +++ b/src/universal-app/DEBUG.md @@ -0,0 +1,10 @@ +### 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 test //src/universal-app:server_test --test_output=all` +* `echo $(bazel info bazel-bin)/src/universal-app/index-prerendered.html` diff --git a/src/universal-app/kitchen-sink-root.ts b/src/universal-app/kitchen-sink-root.ts index c6ae78647c9d..b9f777702f32 100644 --- a/src/universal-app/kitchen-sink-root.ts +++ b/src/universal-app/kitchen-sink-root.ts @@ -17,7 +17,8 @@ export class KitchenSinkRoot { @NgModule({ imports: [ - BrowserModule.withServerTransition({appId: 'kitchen-sink'}), KitchenSinkMdcModule, + BrowserModule.withServerTransition({appId: 'kitchen-sink'}), + KitchenSinkMdcModule, KitchenSinkModule ], declarations: [KitchenSinkRoot], diff --git a/src/universal-app/prerender.ts b/src/universal-app/prerender.ts index bf668a74714e..62c4cbc26917 100644 --- a/src/universal-app/prerender.ts +++ b/src/universal-app/prerender.ts @@ -2,8 +2,7 @@ import 'reflect-metadata'; import 'zone.js'; import {renderModuleFactory} from '@angular/platform-server'; -import {readFileSync, writeFileSync} from 'fs-extra'; -import {log} from 'gulp-util'; +import {readFileSync, writeFileSync} from 'fs'; import {join} from 'path'; import {KitchenSinkRootServerModuleNgFactory} from './kitchen-sink-root.ngfactory'; @@ -11,9 +10,12 @@ import {KitchenSinkRootServerModuleNgFactory} from './kitchen-sink-root.ngfactor // Do not enable production mode, because otherwise the `MatCommonModule` won't execute // the browser related checks that could cause NodeJS issues. +// Resolve the path to the "index.html" through Bazel runfile resolution. +const indexHtmlPath = require.resolve('./index.html'); + const result = renderModuleFactory( KitchenSinkRootServerModuleNgFactory, - {document: readFileSync(join(__dirname, 'index.html'), 'utf-8')}); + {document: readFileSync(indexHtmlPath, 'utf-8')}); result .then(content => { @@ -22,7 +24,7 @@ result console.log('Inspect pre-rendered page here:'); console.log(`file://${filename}`); writeFileSync(filename, content, 'utf-8'); - log('Prerender done.'); + console.log('Prerender done.'); }) // If rendering the module factory fails, exit the process with an error code because otherwise // the CI task will not recognize the failure and will show as "success". The error message diff --git a/src/universal-app/tsconfig-build.json b/src/universal-app/tsconfig-build.json deleted file mode 100644 index 4ab47840e9f6..000000000000 --- a/src/universal-app/tsconfig-build.json +++ /dev/null @@ -1,42 +0,0 @@ -// TypeScript config file that is used to compile the Universal App. All sources are compiled -// inside of the output folder and therefore all paths can be relative to the output folder. -{ - "compilerOptions": { - "declaration": true, - "stripInternal": false, - "experimentalDecorators": true, - "noUnusedParameters": false, - "noUnusedLocals": false, - "strictNullChecks": true, - "noImplicitReturns": true, - "strictFunctionTypes": true, - "noImplicitAny": true, - "noImplicitThis": true, - "module": "commonjs", - "moduleResolution": "node", - "outDir": ".", - "rootDir": ".", - "sourceMap": true, - "target": "es2015", - "lib": ["es2015", "dom"], - "skipLibCheck": true, - "types": [], - "baseUrl": ".", - "paths": { - "@angular/material": ["./material"], - "@angular/cdk/*": ["./cdk/*"], - "@angular/material/*": ["./material/*"], - "@angular/cdk-experimental": ["./cdk-experimental"], - "@angular/cdk-experimental/*": ["./cdk-experimental/*"], - "@angular/material-experimental": ["./material-experimental"], - "@angular/material-experimental/*": ["./material-experimental/*"] - } - }, - "files": [ - "main.ts" - ], - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "fullTemplateTypeCheck": true - } -} diff --git a/src/universal-app/tsconfig-prerender.json b/src/universal-app/tsconfig-prerender.json deleted file mode 100644 index 36ec8c782456..000000000000 --- a/src/universal-app/tsconfig-prerender.json +++ /dev/null @@ -1,38 +0,0 @@ -// TypeScript config file that is used to compile the files that prerender the Universal app. -{ - "compilerOptions": { - "declaration": false, - "stripInternal": false, - "experimentalDecorators": true, - "noUnusedParameters": false, - "noUnusedLocals": false, - "strictNullChecks": true, - "noImplicitReturns": true, - "strictFunctionTypes": true, - "noImplicitAny": true, - "noImplicitThis": true, - "module": "commonjs", - "moduleResolution": "node", - "outDir": ".", - "rootDir": ".", - "sourceMap": true, - "target": "es2015", - "lib": ["es2015", "dom"], - "skipLibCheck": true, - "types": [], - "baseUrl": ".", - "paths": { - "@angular/material": ["./material"], - "@angular/cdk": ["./cdk"], - "@angular/cdk/*": ["./cdk/*"], - "@angular/material/*": ["./material/*"], - "@angular/cdk-experimental": ["./cdk-experimental"], - "@angular/cdk-experimental/*": ["./cdk-experimental/*"], - "@angular/material-experimental": ["./material-experimental"], - "@angular/material-experimental/*": ["./material-experimental/*"] - } - }, - "files": [ - "prerender.ts" - ] -} diff --git a/src/universal-app/tsconfig.json b/src/universal-app/tsconfig.json deleted file mode 100644 index e570a97799aa..000000000000 --- a/src/universal-app/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -// Configuration for IDEs only. -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "..", - "baseUrl": ".", - "paths": { - "@angular/cdk/*": ["../cdk/*"], - "@angular/material/*": ["../material/*"], - "@angular/material": ["../material/public_api"], - "@angular/cdk-experimental": ["../cdk-experimental"], - "@angular/cdk-experimental/*": ["../cdk-experimental/*"], - "@angular/material-experimental": ["../material-experimental"], - "@angular/material-experimental/*": ["../material-experimental/*"] - } - }, - "include": ["./**/*.ts"] -} diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index c31f01cdb864..4bbc4014c369 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -29,7 +29,6 @@ import './tasks/example-module'; import './tasks/lint'; import './tasks/material-release'; import './tasks/unit-test'; -import './tasks/universal'; /** Task that builds all available release packages. */ task('build-release-packages', sequenceTask( diff --git a/tools/gulp/tasks/ci.ts b/tools/gulp/tasks/ci.ts index a1d8a6660286..740f1166b628 100644 --- a/tools/gulp/tasks/ci.ts +++ b/tools/gulp/tasks/ci.ts @@ -12,8 +12,5 @@ task('ci:test', ['test:single-run'], () => process.exit(0)); */ task('ci:aot', ['build-aot:no-release-build']); -/** Task that verifies if all Material components are working with platform-server. */ -task('ci:prerender', ['prerender']); - /** Task that builds all release packages. */ task('ci:build-release-packages', ['build-release-packages']); diff --git a/tools/gulp/tasks/universal.ts b/tools/gulp/tasks/universal.ts deleted file mode 100644 index 7610d92d5d6c..000000000000 --- a/tools/gulp/tasks/universal.ts +++ /dev/null @@ -1,73 +0,0 @@ -import {dest, task} from 'gulp'; -import {ngcBuildTask, tsBuildTask, copyTask, execTask} from '../util/task-helpers'; -import {join} from 'path'; -import {copySync} from 'fs-extra'; -import {buildConfig, buildScssPipeline, sequenceTask} from 'material2-build-tools'; - -const {outputDir, packagesDir} = buildConfig; - -/** Path to the directory where all releases are created. */ -const releasesDir = join(outputDir, 'releases'); - -const appDir = join(packagesDir, 'universal-app'); -const outDir = join(outputDir, 'packages', 'universal-app'); - -// Paths to the different tsconfig files of the Universal app. -// Building the sources in the output directory is part of the workaround for -// https://github.com/angular/angular/issues/12249 -const tsconfigAppPath = join(outDir, 'tsconfig-build.json'); -const tsconfigPrerenderPath = join(outDir, 'tsconfig-prerender.json'); - -/** Path to the compiled prerender file. Running this file just dumps the HTML output for now. */ -const prerenderOutFile = join(outDir, 'prerender.js'); - -/** Task that builds the universal-app and runs the prerender script. */ -task('prerender', ['universal:build'], execTask( - // Runs node with the tsconfig-paths module to alias the @angular/material dependency. - 'node', ['-r', 'tsconfig-paths/register', prerenderOutFile], { - env: {TS_NODE_PROJECT: tsconfigPrerenderPath}, - // Errors in lifecycle hooks will write to STDERR, but won't exit the process with an - // error code, however we still want to catch those cases in the CI. - failOnStderr: true - } -)); - -task( - 'universal:build', - sequenceTask( - 'clean', - [ - 'cdk:build-release', - 'material:build-release', - 'cdk-experimental:build-release', - 'material-experimental:build-release', - 'youtube-player:build-release', - 'google-maps:build-release', - ], - ['universal:copy-release', 'universal:copy-files'], - ['universal:build-app-ts', 'universal:build-app-scss'], - 'universal:build-prerender-ts', - )); - -/** Task that builds the universal app in the output directory. */ -task('universal:build-app-ts', ngcBuildTask(tsconfigAppPath)); - -/** Task that builds the universal app styles in the output directory. */ -task('universal:build-app-scss', () => buildScssPipeline(appDir).pipe(dest(outDir))); - -/** Task that copies all files to the output directory. */ -task('universal:copy-files', copyTask(appDir, outDir)); - -/** Task that builds the prerender script in the output directory. */ -task('universal:build-prerender-ts', tsBuildTask(tsconfigPrerenderPath)); - -// As a workaround for https://github.com/angular/angular/issues/12249, we need to -// copy the Material and CDK ESM output inside of the universal-app output. -task('universal:copy-release', () => { - copySync(join(releasesDir, 'cdk'), join(outDir, 'cdk')); - copySync(join(releasesDir, 'material'), join(outDir, 'material')); - copySync(join(releasesDir, 'cdk-experimental'), join(outDir, 'cdk-experimental')); - copySync(join(releasesDir, 'material-experimental'), join(outDir, 'material-experimental')); - copySync(join(releasesDir, 'youtube-player'), join(outDir, 'youtube-player')); - copySync(join(releasesDir, 'google-maps'), join(outDir, 'google-maps')); -}); diff --git a/tools/gulp/util/task-helpers.ts b/tools/gulp/util/task-helpers.ts index 880905d626df..cb612280601d 100644 --- a/tools/gulp/util/task-helpers.ts +++ b/tools/gulp/util/task-helpers.ts @@ -1,7 +1,5 @@ import * as child_process from 'child_process'; -import * as fs from 'fs'; import * as gulp from 'gulp'; -import * as path from 'path'; // This import lacks type definitions. const gulpClean = require('gulp-clean'); @@ -9,31 +7,6 @@ const gulpClean = require('gulp-clean'); // There are no type definitions available for these imports. const resolveBin = require('resolve-bin'); - -/** If the string passed in is a glob, returns it, otherwise append '**\/*' to it. */ -function _globify(maybeGlob: string, suffix = '**/*') { - if (maybeGlob.indexOf('*') > -1) { - return maybeGlob; - } - try { - if (fs.statSync(maybeGlob).isFile()) { - return maybeGlob; - } - } catch {} - return path.join(maybeGlob, suffix); -} - - -/** Creates a task that runs the TypeScript compiler */ -export function tsBuildTask(tsConfigPath: string) { - return execNodeTask('typescript', 'tsc', ['-p', tsConfigPath]); -} - -/** Creates a task that runs the Angular Compiler CLI. */ -export function ngcBuildTask(tsConfigPath: string) { - return execNodeTask('@angular/compiler-cli', 'ngc', ['-p', tsConfigPath]); -} - /** Options that can be passed to execTask or execNodeTask. */ export interface ExecTaskOptions { // Whether STDOUT and STDERR messages should be printed. @@ -101,17 +74,6 @@ export function execNodeTask(packageName: string, executable: string | string[], }; } - -/** Copy files from a glob to a destination. */ -export function copyTask(srcGlobOrDir: string | string[], outRoot: string) { - if (typeof srcGlobOrDir === 'string') { - return () => gulp.src(_globify(srcGlobOrDir)).pipe(gulp.dest(outRoot)); - } else { - return () => gulp.src(srcGlobOrDir.map(name => _globify(name))).pipe(gulp.dest(outRoot)); - } -} - - /** Delete files. */ export function cleanTask(glob: string) { return () => gulp.src(glob, { read: false }).pipe(gulpClean(null));