From dc5cc893d6c3d4e5e6f6c4b19bee632b66a94fc0 Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Mon, 27 Mar 2023 18:43:53 -0400 Subject: [PATCH] feat(@schematics/angular): Update universal schematic to support standalone applications --- .../app/app.config.server.ts.template | 19 +++++++ .../standalone-src/main.server.ts.template | 15 ++++++ .../schematics/angular/universal/index.ts | 6 ++- .../angular/universal/index_spec.ts | 51 +++++++++++++++++++ .../angular/utility/ng-ast-utils.ts | 13 +++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 packages/schematics/angular/universal/files/standalone-src/app/app.config.server.ts.template create mode 100644 packages/schematics/angular/universal/files/standalone-src/main.server.ts.template diff --git a/packages/schematics/angular/universal/files/standalone-src/app/app.config.server.ts.template b/packages/schematics/angular/universal/files/standalone-src/app/app.config.server.ts.template new file mode 100644 index 000000000000..00416b5e1a88 --- /dev/null +++ b/packages/schematics/angular/universal/files/standalone-src/app/app.config.server.ts.template @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; +import { provideServerSupport } from '@angular/platform-server'; +import { appConfig } from './app.config'; + +const serverConfig: ApplicationConfig = { + providers: [ + provideServerSupport() + ] +}; + +export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/packages/schematics/angular/universal/files/standalone-src/main.server.ts.template b/packages/schematics/angular/universal/files/standalone-src/main.server.ts.template new file mode 100644 index 000000000000..d7ec1bb8636b --- /dev/null +++ b/packages/schematics/angular/universal/files/standalone-src/main.server.ts.template @@ -0,0 +1,15 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { config } from './app/app.config.server'; + +const bootstrap = () => bootstrapApplication(AppComponent, config); + +export default bootstrap; diff --git a/packages/schematics/angular/universal/index.ts b/packages/schematics/angular/universal/index.ts index 6890e6953dee..53b19b4f0ebd 100644 --- a/packages/schematics/angular/universal/index.ts +++ b/packages/schematics/angular/universal/index.ts @@ -17,6 +17,7 @@ import { chain, mergeWith, move, + noop, strings, url, } from '@angular-devkit/schematics'; @@ -27,6 +28,7 @@ import { getPackageJsonDependency, } from '../utility/dependencies'; import { latestVersions } from '../utility/latest-versions'; +import { isStandaloneApp } from '../utility/ng-ast-utils'; import { relativePathToWorkspaceRoot } from '../utility/paths'; import { targetBuildNotFoundError } from '../utility/project-targets'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; @@ -133,7 +135,9 @@ export default function (options: UniversalOptions): Rule { context.addTask(new NodePackageInstallTask()); } - const templateSource = apply(url('./files/src'), [ + const isStandalone = isStandaloneApp(host, clientBuildOptions.main); + + const templateSource = apply(url(isStandalone ? './files/standalone-src' : './files/src'), [ applyTemplates({ ...strings, ...options, diff --git a/packages/schematics/angular/universal/index_spec.ts b/packages/schematics/angular/universal/index_spec.ts index fbc6a13bef20..61582a64f804 100644 --- a/packages/schematics/angular/universal/index_spec.ts +++ b/packages/schematics/angular/universal/index_spec.ts @@ -188,4 +188,55 @@ describe('Universal Schematic', () => { }; expect(compilerOptions.types).toContain('@angular/localize'); }); + + describe('standalone application', () => { + let standaloneAppOptions; + let defaultStandaloneOptions: UniversalOptions; + beforeEach(async () => { + const standaloneAppName = 'baz'; + standaloneAppOptions = { + ...appOptions, + name: standaloneAppName, + standalone: true, + }; + defaultStandaloneOptions = { + project: standaloneAppName, + }; + appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree); + }); + + it('should create not root module file', async () => { + const tree = await schematicRunner.runSchematic( + 'universal', + defaultStandaloneOptions, + appTree, + ); + const filePath = '/projects/baz/src/app/app.server.module.ts'; + expect(tree.exists(filePath)).toEqual(false); + }); + + it('should create a main file', async () => { + const tree = await schematicRunner.runSchematic( + 'universal', + defaultStandaloneOptions, + appTree, + ); + const filePath = '/projects/baz/src/main.server.ts'; + expect(tree.exists(filePath)).toEqual(true); + const contents = tree.readContent(filePath); + expect(contents).toContain(`bootstrapApplication(AppComponent, config)`); + }); + + it('should create server app config file', async () => { + const tree = await schematicRunner.runSchematic( + 'universal', + defaultStandaloneOptions, + appTree, + ); + const filePath = '/projects/baz/src/app/app.config.server.ts'; + expect(tree.exists(filePath)).toEqual(true); + const contents = tree.readContent(filePath); + expect(contents).toContain(`const serverConfig: ApplicationConfig = {`); + }); + }); }); diff --git a/packages/schematics/angular/utility/ng-ast-utils.ts b/packages/schematics/angular/utility/ng-ast-utils.ts index 7318308c1190..cddcef183bb0 100644 --- a/packages/schematics/angular/utility/ng-ast-utils.ts +++ b/packages/schematics/angular/utility/ng-ast-utils.ts @@ -9,6 +9,7 @@ import { normalize } from '@angular-devkit/core'; import { SchematicsException, Tree } from '@angular-devkit/schematics'; import { dirname } from 'path'; +import { findBootstrapApplicationCall } from '../private/standalone'; import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript'; import { findNode, getSourceNodes } from '../utility/ast-utils'; @@ -78,3 +79,15 @@ export function getAppModulePath(host: Tree, mainPath: string): string { return modulePath; } + +export function isStandaloneApp(host: Tree, mainPath: string): boolean { + const source = ts.createSourceFile( + mainPath, + host.readText(mainPath), + ts.ScriptTarget.Latest, + true, + ); + const bootstrapCall = findBootstrapApplicationCall(source); + + return bootstrapCall !== null; +}