diff --git a/goldens/public-api/angular/ssr/index.api.md b/goldens/public-api/angular/ssr/index.api.md index 95b8ec8dd7aa..23d13774aba3 100644 --- a/goldens/public-api/angular/ssr/index.api.md +++ b/goldens/public-api/angular/ssr/index.api.md @@ -4,37 +4,6 @@ ```ts -import { ApplicationRef } from '@angular/core'; -import { StaticProvider } from '@angular/core'; -import { Type } from '@angular/core'; - -// @public -export class CommonEngine { - constructor(options?: CommonEngineOptions | undefined); - render(opts: CommonEngineRenderOptions): Promise; -} - -// @public (undocumented) -export interface CommonEngineOptions { - bootstrap?: Type<{}> | (() => Promise); - enablePerformanceProfiler?: boolean; - providers?: StaticProvider[]; -} - -// @public (undocumented) -export interface CommonEngineRenderOptions { - bootstrap?: Type<{}> | (() => Promise); - // (undocumented) - document?: string; - // (undocumented) - documentFilePath?: string; - inlineCriticalCss?: boolean; - providers?: StaticProvider[]; - publicPath?: string; - // (undocumented) - url?: string; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/goldens/public-api/angular/ssr/node/index.api.md b/goldens/public-api/angular/ssr/node/index.api.md new file mode 100644 index 000000000000..23541740991d --- /dev/null +++ b/goldens/public-api/angular/ssr/node/index.api.md @@ -0,0 +1,40 @@ +## API Report File for "@angular/ssr_node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { ApplicationRef } from '@angular/core'; +import { StaticProvider } from '@angular/core'; +import { Type } from '@angular/core'; + +// @public +export class CommonEngine { + constructor(options?: CommonEngineOptions | undefined); + render(opts: CommonEngineRenderOptions): Promise; +} + +// @public (undocumented) +export interface CommonEngineOptions { + bootstrap?: Type<{}> | (() => Promise); + enablePerformanceProfiler?: boolean; + providers?: StaticProvider[]; +} + +// @public (undocumented) +export interface CommonEngineRenderOptions { + bootstrap?: Type<{}> | (() => Promise); + // (undocumented) + document?: string; + // (undocumented) + documentFilePath?: string; + inlineCriticalCss?: boolean; + providers?: StaticProvider[]; + publicPath?: string; + // (undocumented) + url?: string; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/angular/ssr/BUILD.bazel b/packages/angular/ssr/BUILD.bazel index 0d0dea781e32..73869ac9c920 100644 --- a/packages/angular/ssr/BUILD.bazel +++ b/packages/angular/ssr/BUILD.bazel @@ -44,6 +44,7 @@ ng_package( tags = ["release-package"], deps = [ ":ssr", + "//packages/angular/ssr/node", ], ) diff --git a/packages/angular/ssr/node/BUILD.bazel b/packages/angular/ssr/node/BUILD.bazel new file mode 100644 index 000000000000..c3cbf2ae6cef --- /dev/null +++ b/packages/angular/ssr/node/BUILD.bazel @@ -0,0 +1,20 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "node", + srcs = glob( + [ + "*.ts", + "src/**/*.ts", + ], + ), + module_name = "@angular/ssr/node", + deps = [ + "//packages/angular/ssr", + "@npm//@angular/core", + "@npm//@angular/platform-server", + "@npm//@types/node", + ], +) diff --git a/packages/angular/ssr/node/index.ts b/packages/angular/ssr/node/index.ts new file mode 100644 index 000000000000..36d8b2a62dff --- /dev/null +++ b/packages/angular/ssr/node/index.ts @@ -0,0 +1,9 @@ +/** + * @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.dev/license + */ + +export * from './public_api'; diff --git a/packages/angular/ssr/node/public_api.ts b/packages/angular/ssr/node/public_api.ts new file mode 100644 index 000000000000..e65ed9da7c6a --- /dev/null +++ b/packages/angular/ssr/node/public_api.ts @@ -0,0 +1,13 @@ +/** + * @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.dev/license + */ + +export { + CommonEngine, + type CommonEngineRenderOptions, + type CommonEngineOptions, +} from './src/common-engine/common-engine'; diff --git a/packages/angular/ssr/src/common-engine/common-engine.ts b/packages/angular/ssr/node/src/common-engine/common-engine.ts similarity index 100% rename from packages/angular/ssr/src/common-engine/common-engine.ts rename to packages/angular/ssr/node/src/common-engine/common-engine.ts diff --git a/packages/angular/ssr/src/common-engine/inline-css-processor.ts b/packages/angular/ssr/node/src/common-engine/inline-css-processor.ts similarity index 90% rename from packages/angular/ssr/src/common-engine/inline-css-processor.ts rename to packages/angular/ssr/node/src/common-engine/inline-css-processor.ts index d9e8d0e2c2d3..31df8dcc3b67 100644 --- a/packages/angular/ssr/src/common-engine/inline-css-processor.ts +++ b/packages/angular/ssr/node/src/common-engine/inline-css-processor.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ +import { ɵInlineCriticalCssProcessor as InlineCriticalCssProcessor } from '@angular/ssr'; import { readFile } from 'node:fs/promises'; -import { InlineCriticalCssProcessor } from '../utils/inline-critical-css'; export class CommonEngineInlineCriticalCssProcessor { private readonly resourceCache = new Map(); diff --git a/packages/angular/ssr/src/common-engine/peformance-profiler.ts b/packages/angular/ssr/node/src/common-engine/peformance-profiler.ts similarity index 100% rename from packages/angular/ssr/src/common-engine/peformance-profiler.ts rename to packages/angular/ssr/node/src/common-engine/peformance-profiler.ts diff --git a/packages/angular/ssr/private_export.ts b/packages/angular/ssr/private_export.ts index 4d53ca3e58f1..9772d2252afc 100644 --- a/packages/angular/ssr/private_export.ts +++ b/packages/angular/ssr/private_export.ts @@ -16,3 +16,5 @@ export { setAngularAppManifest as ɵsetAngularAppManifest, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, } from './src/manifest'; + +export { InlineCriticalCssProcessor as ɵInlineCriticalCssProcessor } from './src/utils/inline-critical-css'; diff --git a/packages/angular/ssr/public_api.ts b/packages/angular/ssr/public_api.ts index f1650b989948..e714ba0d0e44 100644 --- a/packages/angular/ssr/public_api.ts +++ b/packages/angular/ssr/public_api.ts @@ -6,10 +6,4 @@ * found in the LICENSE file at https://angular.dev/license */ -export { - CommonEngine, - type CommonEngineRenderOptions, - type CommonEngineOptions, -} from './src/common-engine/common-engine'; - export * from './private_export'; diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index 0b0ac4a8e3d6..5705d91b9387 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -365,7 +365,7 @@ LARGE_SPECS = { "@npm//browser-sync", "@npm//express", "@npm//undici", - "//packages/angular/ssr", + "//packages/angular/ssr/node", ], }, } diff --git a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/proxy_spec.ts b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/proxy_spec.ts index 5806589d1bcd..5e2816a2c271 100644 --- a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/proxy_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/proxy_spec.ts @@ -25,7 +25,7 @@ describe('Serve SSR Builder', () => { 'src/main.server.ts': ` import 'zone.js/node'; - import { CommonEngine } from '@angular/ssr'; + import { CommonEngine } from '@angular/ssr/node'; import * as express from 'express'; import { resolve, join } from 'node:path'; import { AppServerModule } from './app/app.module.server'; diff --git a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/ssl_spec.ts b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/ssl_spec.ts index f1265c7988c1..4a3725c100c4 100644 --- a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/ssl_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/ssl_spec.ts @@ -25,7 +25,7 @@ describe('Serve SSR Builder', () => { 'src/main.server.ts': ` import 'zone.js/node'; - import { CommonEngine } from '@angular/ssr'; + import { CommonEngine } from '@angular/ssr/node'; import * as express from 'express'; import { resolve, join } from 'node:path'; import { AppServerModule } from './app/app.module.server'; diff --git a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/works_spec.ts b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/works_spec.ts index 51c3ff52bef6..857e6ab8ea5b 100644 --- a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/works_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/works_spec.ts @@ -24,7 +24,7 @@ describe('Serve SSR Builder', () => { 'src/main.server.ts': ` import 'zone.js/node'; - import { CommonEngine } from '@angular/ssr'; + import { CommonEngine } from '@angular/ssr/node'; import * as express from 'express'; import { resolve, join } from 'node:path'; import { AppServerModule } from './app/app.module.server'; diff --git a/packages/schematics/angular/migrations/migration-collection.json b/packages/schematics/angular/migrations/migration-collection.json index 0c2b31ad1ae5..6b1647e5d602 100644 --- a/packages/schematics/angular/migrations/migration-collection.json +++ b/packages/schematics/angular/migrations/migration-collection.json @@ -11,6 +11,11 @@ "version": "19.0.0", "factory": "./update-workspace-config/migration", "description": "Update the workspace configuration by replacing deprecated options in 'angular.json' for compatibility with the latest Angular CLI changes." + }, + "update-ssr-imports": { + "version": "19.0.0", + "factory": "./update-ssr-imports/migration", + "description": "Update '@angular/ssr' import paths to use the new '/node' entry point when 'CommonEngine' is detected." } } } diff --git a/packages/schematics/angular/migrations/update-ssr-imports/migration.ts b/packages/schematics/angular/migrations/update-ssr-imports/migration.ts new file mode 100644 index 000000000000..847200791b61 --- /dev/null +++ b/packages/schematics/angular/migrations/update-ssr-imports/migration.ts @@ -0,0 +1,92 @@ +/** + * @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.dev/license + */ + +import { DirEntry, Rule, UpdateRecorder } from '@angular-devkit/schematics'; +import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript'; + +function* visit(directory: DirEntry): IterableIterator { + for (const path of directory.subfiles) { + if (path.endsWith('.ts') && !path.endsWith('.d.ts')) { + const entry = directory.file(path); + if (entry) { + const content = entry.content; + if (content.includes('CommonEngine') && !content.includes('@angular/ssr/node')) { + const source = ts.createSourceFile( + entry.path, + content.toString().replace(/^\uFEFF/, ''), + ts.ScriptTarget.Latest, + true, + ); + + yield source; + } + } + } + } + + for (const path of directory.subdirs) { + if (path === 'node_modules' || path.startsWith('.')) { + continue; + } + + yield* visit(directory.dir(path)); + } +} + +/** + * Schematics rule that identifies and updates import declarations in TypeScript files. + * Specifically, it modifies imports of '@angular/ssr' by appending '/node' if the + * `CommonEngine` is used from the old entry point. + * + */ +export default function (): Rule { + return (tree) => { + for (const sourceFile of visit(tree.root)) { + let recorder: UpdateRecorder | undefined; + + const allImportDeclarations = sourceFile.statements.filter((n) => ts.isImportDeclaration(n)); + if (allImportDeclarations.length === 0) { + continue; + } + + const ssrImports = allImportDeclarations.filter( + (n) => ts.isStringLiteral(n.moduleSpecifier) && n.moduleSpecifier.text === '@angular/ssr', + ); + for (const ssrImport of ssrImports) { + const ssrNamedBinding = getNamedImports(ssrImport); + if (ssrNamedBinding) { + const isUsingOldEntryPoint = ssrNamedBinding.elements.some((e) => + e.name.text.startsWith('CommonEngine'), + ); + + if (!isUsingOldEntryPoint) { + continue; + } + + recorder ??= tree.beginUpdate(sourceFile.fileName); + recorder.insertRight(ssrImport.moduleSpecifier.getEnd() - 1, '/node'); + } + } + + if (recorder) { + tree.commitUpdate(recorder); + } + } + }; +} + +function getNamedImports( + importDeclaration: ts.ImportDeclaration | undefined, +): ts.NamedImports | undefined { + const namedBindings = importDeclaration?.importClause?.namedBindings; + if (namedBindings && ts.isNamedImports(namedBindings)) { + return namedBindings; + } + + return undefined; +} diff --git a/packages/schematics/angular/migrations/update-ssr-imports/migration_spec.ts b/packages/schematics/angular/migrations/update-ssr-imports/migration_spec.ts new file mode 100644 index 000000000000..6cbc7ebbee6e --- /dev/null +++ b/packages/schematics/angular/migrations/update-ssr-imports/migration_spec.ts @@ -0,0 +1,67 @@ +/** + * @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.dev/license + */ + +import { tags } from '@angular-devkit/core'; +import { EmptyTree } from '@angular-devkit/schematics'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; + +describe('CommonEngine migration', () => { + const schematicRunner = new SchematicTestRunner( + 'migrations', + require.resolve('../migration-collection.json'), + ); + + let tree: UnitTestTree; + beforeEach(() => { + tree = new UnitTestTree(new EmptyTree()); + }); + + function runMigration(): Promise { + return schematicRunner.runSchematic('update-ssr-imports', {}, tree); + } + + it(`should replace 'CommonEngine*' imports from '@angular/ssr' to '@angular/ssr/node'`, async () => { + tree.create( + '/index.ts', + tags.stripIndents` + import { CommonEngine } from '@angular/ssr'; + import type { CommonEngineOptions, CommonEngineRenderOptions } from '@angular/ssr'; + `, + ); + + const newTree = await runMigration(); + expect(newTree.readContent('/index.ts')).toBe(tags.stripIndents` + import { CommonEngine } from '@angular/ssr/node'; + import type { CommonEngineOptions, CommonEngineRenderOptions } from '@angular/ssr/node'; + `); + }); + + it(`should not replace 'CommonEngine*' imports from '@angular/ssr/node'`, async () => { + const input = tags.stripIndents` + import { CommonEngine } from '@angular/ssr/node'; + import type { CommonEngineOptions, CommonEngineRenderOptions } from '@angular/ssr/node'; + `; + + tree.create('/index.ts', input); + + const newTree = await runMigration(); + expect(newTree.readContent('/index.ts')).toBe(input); + }); + + it(`should not replace 'CommonEngine*' imports from other package`, async () => { + const input = tags.stripIndents` + import { CommonEngine } from 'unknown'; + import type { CommonEngineOptions, CommonEngineRenderOptions } from 'unknown'; + `; + + tree.create('/index.ts', input); + + const newTree = await runMigration(); + expect(newTree.readContent('/index.ts')).toBe(input); + }); +}); diff --git a/packages/schematics/angular/ssr/files/application-builder/server.ts.template b/packages/schematics/angular/ssr/files/application-builder/server.ts.template index 399fba5148b3..b8f7e04fb7f2 100644 --- a/packages/schematics/angular/ssr/files/application-builder/server.ts.template +++ b/packages/schematics/angular/ssr/files/application-builder/server.ts.template @@ -1,5 +1,5 @@ import { APP_BASE_HREF } from '@angular/common'; -import { CommonEngine } from '@angular/ssr'; +import { CommonEngine } from '@angular/ssr/node'; import express from 'express'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; diff --git a/packages/schematics/angular/ssr/files/server-builder/server.ts.template b/packages/schematics/angular/ssr/files/server-builder/server.ts.template index de1bf66f9726..8cfcc0e4638b 100644 --- a/packages/schematics/angular/ssr/files/server-builder/server.ts.template +++ b/packages/schematics/angular/ssr/files/server-builder/server.ts.template @@ -1,7 +1,7 @@ import 'zone.js/node'; import { APP_BASE_HREF } from '@angular/common'; -import { CommonEngine } from '@angular/ssr'; +import { CommonEngine } from '@angular/ssr/node'; import * as express from 'express'; import { existsSync } from 'node:fs'; import { join } from 'node:path'; diff --git a/tests/legacy-cli/e2e/assets/19-ssr-project-webpack/server.ts b/tests/legacy-cli/e2e/assets/19-ssr-project-webpack/server.ts index 129a75442957..d7a5e249ac41 100644 --- a/tests/legacy-cli/e2e/assets/19-ssr-project-webpack/server.ts +++ b/tests/legacy-cli/e2e/assets/19-ssr-project-webpack/server.ts @@ -1,7 +1,7 @@ import 'zone.js/node'; import { APP_BASE_HREF } from '@angular/common'; -import { CommonEngine } from '@angular/ssr'; +import { CommonEngine } from '@angular/ssr/node'; import * as express from 'express'; import { existsSync } from 'node:fs'; import { join } from 'node:path';