diff --git a/packages/angular/migrations.json b/packages/angular/migrations.json index 7b44c14620ab5..7ae7a4aef5c69 100644 --- a/packages/angular/migrations.json +++ b/packages/angular/migrations.json @@ -191,6 +191,15 @@ "version": "16.0.0-beta.6", "description": "Remove karma as default unitTestRunner from nxJson and project configurations", "implementation": "./src/migrations/update-16-0-0/remove-karma-defaults" + }, + "remove-render-module-platform-server-exports": { + "cli": "nx", + "version": "16.1.0-beta.0", + "requires": { + "@angular/core": ">=15.0.0" + }, + "description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.", + "factory": "./src/migrations/update-16-1-0/remove-render-module-platform-server-exports" } }, "packageJsonUpdates": { diff --git a/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap b/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap index e203117311d2c..042823034dbf7 100644 --- a/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap +++ b/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap @@ -41,17 +41,7 @@ if (document.readyState !== 'loading') { `; exports[`Host App Generator --ssr should generate the correct files 3`] = ` -"/*************************************************************************************************** - * Initialize the server environment - for example, adding DOM built-in types to the global scope. - * - * NOTE: - * This import must come before any imports (direct or transitive) that rely on DOM built-ins being - * available, such as \`@angular/elements\`. - */ -import '@angular/platform-server/init'; - -export { AppServerModule } from './app/app.server.module'; -export { renderModule } from '@angular/platform-server'; +"export { AppServerModule } from './app/app.server.module'; " `; diff --git a/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap b/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap index f52c4a1d81b53..1f929fee5ac15 100644 --- a/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap +++ b/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap @@ -41,17 +41,7 @@ platformBrowserDynamic() `; exports[`MF Remote App Generator --ssr should generate the correct files 3`] = ` -"/*************************************************************************************************** - * Initialize the server environment - for example, adding DOM built-in types to the global scope. - * - * NOTE: - * This import must come before any imports (direct or transitive) that rely on DOM built-ins being - * available, such as \`@angular/elements\`. - */ -import '@angular/platform-server/init'; - -export { AppServerModule } from './app/app.server.module'; -export { renderModule } from '@angular/platform-server'; +"export { AppServerModule } from './app/app.server.module'; " `; diff --git a/packages/angular/src/generators/setup-ssr/files/__serverFileName__ b/packages/angular/src/generators/setup-ssr/files/base/__serverFileName__ similarity index 100% rename from packages/angular/src/generators/setup-ssr/files/__serverFileName__ rename to packages/angular/src/generators/setup-ssr/files/base/__serverFileName__ diff --git a/packages/angular/src/generators/setup-ssr/files/base/src/__main__ b/packages/angular/src/generators/setup-ssr/files/base/src/__main__ new file mode 100644 index 0000000000000..80cb99598bd4d --- /dev/null +++ b/packages/angular/src/generators/setup-ssr/files/base/src/__main__ @@ -0,0 +1 @@ +export { <%= rootModuleClassName %> } from './app/<%= rootModuleFileName.slice(0, -3) %>'; diff --git a/packages/angular/src/generators/setup-ssr/files/src/app/__rootModuleFileName__ b/packages/angular/src/generators/setup-ssr/files/base/src/app/__rootModuleFileName__ similarity index 100% rename from packages/angular/src/generators/setup-ssr/files/src/app/__rootModuleFileName__ rename to packages/angular/src/generators/setup-ssr/files/base/src/app/__rootModuleFileName__ diff --git a/packages/angular/src/generators/setup-ssr/files/src/main.ts__tpl__ b/packages/angular/src/generators/setup-ssr/files/base/src/main.ts__tpl__ similarity index 100% rename from packages/angular/src/generators/setup-ssr/files/src/main.ts__tpl__ rename to packages/angular/src/generators/setup-ssr/files/base/src/main.ts__tpl__ diff --git a/packages/angular/src/generators/setup-ssr/files/tsconfig.server.json__tpl__ b/packages/angular/src/generators/setup-ssr/files/base/tsconfig.server.json__tpl__ similarity index 100% rename from packages/angular/src/generators/setup-ssr/files/tsconfig.server.json__tpl__ rename to packages/angular/src/generators/setup-ssr/files/base/tsconfig.server.json__tpl__ diff --git a/packages/angular/src/generators/setup-ssr/files/src/__main__ b/packages/angular/src/generators/setup-ssr/files/v14/src/__main__ similarity index 100% rename from packages/angular/src/generators/setup-ssr/files/src/__main__ rename to packages/angular/src/generators/setup-ssr/files/v14/src/__main__ diff --git a/packages/angular/src/generators/setup-ssr/lib/generate-files.ts b/packages/angular/src/generators/setup-ssr/lib/generate-files.ts index 8c32826e7e4e6..dc5f28a4d7a55 100644 --- a/packages/angular/src/generators/setup-ssr/lib/generate-files.ts +++ b/packages/angular/src/generators/setup-ssr/lib/generate-files.ts @@ -4,12 +4,26 @@ import { joinPathFragments, readProjectConfiguration, } from '@nx/devkit'; +import { getInstalledAngularMajorVersion } from '../../utils/version-utils'; import type { Schema } from '../schema'; export function generateSSRFiles(tree: Tree, schema: Schema) { const projectRoot = readProjectConfiguration(tree, schema.project).root; - const pathToFiles = joinPathFragments(__dirname, '../', 'files'); + generateFiles( + tree, + joinPathFragments(__dirname, '..', 'files', 'base'), + projectRoot, + { ...schema, tpl: '' } + ); - generateFiles(tree, pathToFiles, projectRoot, { ...schema, tpl: '' }); + const angularMajorVersion = getInstalledAngularMajorVersion(tree); + if (angularMajorVersion < 15) { + generateFiles( + tree, + joinPathFragments(__dirname, '..', 'files', 'v14'), + projectRoot, + { ...schema, tpl: '' } + ); + } } diff --git a/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts b/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts index 2e61b64cc03b9..ac8ba0c7feccd 100644 --- a/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts +++ b/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts @@ -30,17 +30,7 @@ describe('setupSSR', () => { expect(tree.read('apps/app1/server.ts', 'utf-8')).toMatchSnapshot(); expect(tree.read('apps/app1/src/main.server.ts', 'utf-8')) .toMatchInlineSnapshot(` - "/*************************************************************************************************** - * Initialize the server environment - for example, adding DOM built-in types to the global scope. - * - * NOTE: - * This import must come before any imports (direct or transitive) that rely on DOM built-ins being - * available, such as \`@angular/elements\`. - */ - import '@angular/platform-server/init'; - - export { AppServerModule } from './app/app.server.module'; - export { renderModule } from '@angular/platform-server'; + "export { AppServerModule } from './app/app.server.module'; " `); expect(tree.read('apps/app1/src/main.ts', 'utf-8')).toMatchInlineSnapshot(` @@ -167,30 +157,68 @@ describe('setupSSR', () => { ).toMatchSnapshot(); }); - it('should install the correct versions when using older versions of Angular', async () => { - // ARRANGE - const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + describe('compat', () => { + it('should install the correct versions when using older versions of Angular', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); - await generateTestApplication(tree, { - name: 'app1', - }); - - updateJson(tree, 'package.json', (json) => ({ - ...json, - dependencies: { - '@angular/core': '14.2.0', - }, - })); + await generateTestApplication(tree, { + name: 'app1', + }); - // ACT - await setupSsr(tree, { project: 'app1' }); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + '@angular/core': '14.2.0', + }, + })); + + // ACT + await setupSsr(tree, { project: 'app1' }); + + // ASSERT + const pkgJson = readJson(tree, 'package.json'); + expect(pkgJson.dependencies['@angular/platform-server']).toEqual( + '~14.2.0' + ); + expect(pkgJson.dependencies['@nguniversal/express-engine']).toEqual( + '~14.2.0' + ); + expect(pkgJson.devDependencies['@nguniversal/builders']).toEqual( + '~14.2.0' + ); + }); - // ASSERT - const pkgJson = readJson(tree, 'package.json'); - expect(pkgJson.dependencies['@angular/platform-server']).toEqual('~14.2.0'); - expect(pkgJson.dependencies['@nguniversal/express-engine']).toEqual( - '~14.2.0' - ); - expect(pkgJson.devDependencies['@nguniversal/builders']).toEqual('~14.2.0'); + it('should create the main.server.ts file correctly for Angular v14', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + await generateTestApplication(tree, { + name: 'app1', + }); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { '@angular/core': '14.2.0' }, + })); + + // ACT + await setupSsr(tree, { project: 'app1' }); + + // ASSERT + expect(tree.read('apps/app1/src/main.server.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "/*************************************************************************************************** + * Initialize the server environment - for example, adding DOM built-in types to the global scope. + * + * NOTE: + * This import must come before any imports (direct or transitive) that rely on DOM built-ins being + * available, such as \`@angular/elements\`. + */ + import '@angular/platform-server/init'; + + export { AppServerModule } from './app/app.server.module'; + export { renderModule } from '@angular/platform-server'; + " + `); + }); }); }); diff --git a/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.spec.ts b/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.spec.ts new file mode 100644 index 0000000000000..7b8b16868757c --- /dev/null +++ b/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.spec.ts @@ -0,0 +1,85 @@ +import type { Tree } from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing-pre16'; +import migration from './remove-render-module-platform-server-exports'; + +describe('remove-render-module-platform-server-exports migration', () => { + let tree: Tree; + const testTypeScriptFilePath = 'test.ts'; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + describe(`Migration to remove '@angular/platform-server' exports`, () => { + it(`should delete '@angular/platform-server' export when 'renderModule' is the only exported symbol`, async () => { + tree.write( + testTypeScriptFilePath, + ` + import { Path, join } from '@angular-devkit/core'; + export { renderModule } from '@angular/platform-server'; + ` + ); + + await migration(tree); + + const content = tree.read(testTypeScriptFilePath, 'utf-8'); + expect(content).not.toContain('@angular/platform-server'); + expect(content).toContain( + `import { Path, join } from '@angular-devkit/core';` + ); + }); + + it(`should delete only 'renderModule' when there are additional exports`, async () => { + tree.write( + testTypeScriptFilePath, + ` + import { Path, join } from '@angular-devkit/core'; + export { renderModule, ServerModule } from '@angular/platform-server'; + ` + ); + + await migration(tree); + + const content = tree.read(testTypeScriptFilePath, 'utf-8'); + + expect(content).toContain( + `import { Path, join } from '@angular-devkit/core';` + ); + expect(content).toContain( + `export { ServerModule } from '@angular/platform-server';` + ); + }); + + it(`should not delete 'renderModule' when it's exported from another module`, async () => { + tree.write( + testTypeScriptFilePath, + ` + export { renderModule } from '@angular/core'; + ` + ); + + await migration(tree); + + const content = tree.read(testTypeScriptFilePath, 'utf-8'); + expect(content).toContain( + `export { renderModule } from '@angular/core';` + ); + }); + + it(`should not delete 'renderModule' when it's imported from '@angular/platform-server'`, async () => { + tree.write( + testTypeScriptFilePath, + ` + import { renderModule } from '@angular/platform-server'; + ` + ); + + await migration(tree); + + const content = tree.read(testTypeScriptFilePath, 'utf-8'); + expect(content).toContain( + `import { renderModule } from '@angular/platform-server'` + ); + }); + }); +}); diff --git a/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.ts b/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.ts new file mode 100644 index 0000000000000..60bcf1f813863 --- /dev/null +++ b/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.ts @@ -0,0 +1,87 @@ +import type { Tree } from '@nx/devkit'; +import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit'; +import * as ts from 'typescript'; +import { FileChangeRecorder } from '../../utils/file-change-recorder'; + +export default async function (tree: Tree) { + visitNotIgnoredFiles(tree, '/', (path) => { + if (path.endsWith('.ts') && !path.endsWith('.d.ts')) { + const content = tree.read(path, 'utf8'); + if ( + content.includes('@angular/platform-server') && + content.includes('renderModule') + ) { + const source = ts.createSourceFile( + path, + content.toString().replace(/^\uFEFF/, ''), + ts.ScriptTarget.Latest, + true + ); + + let recorder: FileChangeRecorder | undefined; + let printer: ts.Printer | undefined; + + ts.forEachChild(source, function analyze(node) { + if ( + !( + ts.isExportDeclaration(node) && + node.moduleSpecifier && + ts.isStringLiteral(node.moduleSpecifier) && + node.moduleSpecifier.text === '@angular/platform-server' && + node.exportClause && + ts.isNamedExports(node.exportClause) + ) + ) { + // Not a @angular/platform-server named export. + return; + } + + const exportClause = node.exportClause; + const newElements: ts.ExportSpecifier[] = []; + for (const element of exportClause.elements) { + if (element.name.text !== 'renderModule') { + newElements.push(element); + } + } + + if (newElements.length === exportClause.elements.length) { + // No changes + return; + } + + recorder ??= new FileChangeRecorder(tree, path); + + if (newElements.length) { + // Update named exports as there are leftovers. + const newExportClause = ts.factory.updateNamedExports( + exportClause, + newElements + ); + printer ??= ts.createPrinter(); + const fix = printer.printNode( + ts.EmitHint.Unspecified, + newExportClause, + source + ); + + const index = exportClause.getStart(); + const length = exportClause.getWidth(); + recorder.remove(index, index + length); + recorder.insertLeft(index, fix); + } else { + // Delete export as no exports remain. + recorder.remove(node.getStart(), node.getStart() + node.getWidth()); + } + + ts.forEachChild(node, analyze); + }); + + if (recorder) { + recorder.applyChanges(); + } + } + } + }); + + await formatFiles(tree); +}