From 1408865a18a3f2b1c95fcc41eecc794ac89d73fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 15 Apr 2024 11:15:30 +0200 Subject: [PATCH] feat(angular): generate `public` directory instead of `assets` (#22801) --- .../angular/executors/application.json | 6 ++- .../angular/executors/browser-esbuild.json | 6 ++- .../angular/executors/webpack-browser.json | 6 ++- .../angular/executors/webpack-server.json | 6 ++- e2e/angular/src/misc.test.ts | 3 +- e2e/angular/src/ng-add.test.ts | 10 +---- .../module-federation-dev-ssr.impl.ts | 12 +++--- .../builders/utilities/module-federation.ts | 35 ++++++++++++++---- .../src/builders/webpack-browser/schema.json | 3 +- .../src/builders/webpack-server/schema.json | 3 +- .../src/executors/application/schema.json | 3 +- .../src/executors/browser-esbuild/schema.json | 3 +- .../module-federation-dev-server.impl.ts | 15 ++++---- .../__snapshots__/application.spec.ts.snap | 26 ++++++++----- .../application/application.spec.ts | 17 ++++++++- .../{base/src => base-18+/public}/favicon.ico | Bin .../src/assets/.gitkeep__tpl__ | 0 .../files/base-pre18/src/favicon.ico | Bin 0 -> 15086 bytes .../application/lib/create-files.ts | 16 ++++++++ .../application/lib/create-project.ts | 23 +++++++++--- .../builders/angular-devkit-karma.migrator.ts | 5 ++- .../generators/ng-add/migrators/migrator.ts | 22 +++++------ .../ng-add/migrators/projects/app.migrator.ts | 6 ++- .../__snapshots__/setup-mf.spec.ts.snap | 4 +- .../setup-mf/lib/add-remote-to-host.ts | 30 +++++++++------ .../generators/setup-mf/lib/fix-bootstrap.ts | 26 ++++++++----- .../setup-mf/lib/setup-host-if-dynamic.ts | 16 ++++++-- .../src/generators/setup-mf/setup-mf.spec.ts | 12 +++--- .../src/generators/setup-mf/setup-mf.ts | 4 +- .../__snapshots__/setup-ssr.spec.ts.snap | 12 ++++-- 30 files changed, 218 insertions(+), 112 deletions(-) rename packages/angular/src/generators/application/files/{base/src => base-18+/public}/favicon.ico (100%) rename packages/angular/src/generators/application/files/{base => base-pre18}/src/assets/.gitkeep__tpl__ (100%) create mode 100644 packages/angular/src/generators/application/files/base-pre18/src/favicon.ico diff --git a/docs/generated/packages/angular/executors/application.json b/docs/generated/packages/angular/executors/application.json index 825934a2631784..0502b28c4f81e7 100644 --- a/docs/generated/packages/angular/executors/application.json +++ b/docs/generated/packages/angular/executors/application.json @@ -38,11 +38,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] @@ -663,11 +664,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] diff --git a/docs/generated/packages/angular/executors/browser-esbuild.json b/docs/generated/packages/angular/executors/browser-esbuild.json index a5d920682f6de1..4be0a96478db72 100644 --- a/docs/generated/packages/angular/executors/browser-esbuild.json +++ b/docs/generated/packages/angular/executors/browser-esbuild.json @@ -38,11 +38,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] @@ -572,11 +573,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] diff --git a/docs/generated/packages/angular/executors/webpack-browser.json b/docs/generated/packages/angular/executors/webpack-browser.json index 7bac4ebe54623d..b1c8cc786080b5 100644 --- a/docs/generated/packages/angular/executors/webpack-browser.json +++ b/docs/generated/packages/angular/executors/webpack-browser.json @@ -56,11 +56,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] @@ -592,11 +593,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] diff --git a/docs/generated/packages/angular/executors/webpack-server.json b/docs/generated/packages/angular/executors/webpack-server.json index e42ed8a3c0fe80..990a28a11183e7 100644 --- a/docs/generated/packages/angular/executors/webpack-server.json +++ b/docs/generated/packages/angular/executors/webpack-server.json @@ -38,11 +38,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] @@ -323,11 +324,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" } ] diff --git a/e2e/angular/src/misc.test.ts b/e2e/angular/src/misc.test.ts index 9cf0222c5aeed9..4c0853a5a7d0f7 100644 --- a/e2e/angular/src/misc.test.ts +++ b/e2e/angular/src/misc.test.ts @@ -42,7 +42,7 @@ describe('Move Angular Project', () => { expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`); expect(moveOutput).toContain(`CREATE ${newPath}/.eslintrc.json`); - expect(moveOutput).toContain(`CREATE ${newPath}/src/favicon.ico`); + expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`); expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`); expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/styles.css`); @@ -52,7 +52,6 @@ describe('Move Angular Project', () => { ); expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.component.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.config.ts`); - expect(moveOutput).toContain(`CREATE ${newPath}/src/assets/.gitkeep`); }); /** diff --git a/e2e/angular/src/ng-add.test.ts b/e2e/angular/src/ng-add.test.ts index 54a9ca0274252b..fa3160dcf57e70 100644 --- a/e2e/angular/src/ng-add.test.ts +++ b/e2e/angular/src/ng-add.test.ts @@ -186,10 +186,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => { browser: `apps/${project}/src/main.ts`, polyfills: [`zone.js`], tsConfig: `apps/${project}/tsconfig.app.json`, - assets: [ - `apps/${project}/src/favicon.ico`, - `apps/${project}/src/assets`, - ], + assets: [{ glob: '**/*', input: `apps/${project}/public` }], styles: [`apps/${project}/src/styles.css`], scripts: [`apps/${project}/src/scripts.js`], }, @@ -230,10 +227,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => { options: { polyfills: [`zone.js`, `zone.js/testing`], tsConfig: `apps/${project}/tsconfig.spec.json`, - assets: [ - `apps/${project}/src/favicon.ico`, - `apps/${project}/src/assets`, - ], + assets: [{ glob: '**/*', input: `apps/${project}/public` }], styles: [`apps/${project}/src/styles.css`], scripts: [`apps/${project}/src/scripts.js`], }, diff --git a/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts b/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts index a8699529bae8b1..0371e8630e72b5 100644 --- a/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts +++ b/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts @@ -13,6 +13,7 @@ import { from } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils'; import { + getDynamicMfManifestFile, getDynamicRemotes, getStaticRemotes, validateDevRemotes, @@ -29,11 +30,7 @@ export function executeModuleFederationDevSSRBuilder( readProjectsConfigurationFromProjectGraph(projectGraph); const project = workspaceProjects[context.target.project]; - let pathToManifestFile = join( - context.workspaceRoot, - project.sourceRoot, - 'assets/module-federation.manifest.json' - ); + let pathToManifestFile: string; if (options.pathToManifestFile) { const userPathToManifestFile = join( context.workspaceRoot, @@ -50,6 +47,11 @@ export function executeModuleFederationDevSSRBuilder( } pathToManifestFile = userPathToManifestFile; + } else { + pathToManifestFile = getDynamicMfManifestFile( + project, + context.workspaceRoot + ); } validateDevRemotes(options, workspaceProjects); diff --git a/packages/angular/src/builders/utilities/module-federation.ts b/packages/angular/src/builders/utilities/module-federation.ts index 8a125b041d0d2d..7dfead3ba1a992 100644 --- a/packages/angular/src/builders/utilities/module-federation.ts +++ b/packages/angular/src/builders/utilities/module-federation.ts @@ -1,4 +1,4 @@ -import { basename, dirname, join } from 'path'; +import { join } from 'path'; import { existsSync, readFileSync } from 'fs'; import { logger, ProjectConfiguration } from '@nx/devkit'; import { registerTsProject } from '@nx/js/src/internal'; @@ -8,17 +8,18 @@ export function getDynamicRemotes( context: import('@angular-devkit/architect').BuilderContext, workspaceProjects: Record, remotesToSkip: Set, - pathToManifestFile = join( - context.workspaceRoot, - project.sourceRoot, - 'assets/module-federation.manifest.json' - ) + pathToManifestFile: string | undefined ): string[] { + pathToManifestFile ??= getDynamicMfManifestFile( + project, + context.workspaceRoot + ); + // check for dynamic remotes // we should only check for dynamic based on what we generate // and fallback to empty array - if (!existsSync(pathToManifestFile)) { + if (!pathToManifestFile || !existsSync(pathToManifestFile)) { return []; } @@ -182,3 +183,23 @@ export function validateDevRemotes( ); } } + +export function getDynamicMfManifestFile( + project: ProjectConfiguration, + workspaceRoot: string +): string | undefined { + // {sourceRoot}/assets/module-federation.manifest.json was the generated + // path for the manifest file in the past. We now generate the manifest + // file at {root}/public/module-federation.manifest.json. This check + // ensures that we can still support the old path for backwards + // compatibility since old projects may still have the manifest file + // at the old path. + return [ + join(workspaceRoot, project.root, 'public/module-federation.manifest.json'), + join( + workspaceRoot, + project.sourceRoot, + 'assets/module-federation.manifest.json' + ), + ].find((path) => existsSync(path)); +} diff --git a/packages/angular/src/builders/webpack-browser/schema.json b/packages/angular/src/builders/webpack-browser/schema.json index 9a91136502c222..fc1ed5e928cc0e 100644 --- a/packages/angular/src/builders/webpack-browser/schema.json +++ b/packages/angular/src/builders/webpack-browser/schema.json @@ -498,11 +498,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" diff --git a/packages/angular/src/builders/webpack-server/schema.json b/packages/angular/src/builders/webpack-server/schema.json index 1aced7b1964408..01d3042d7507a6 100644 --- a/packages/angular/src/builders/webpack-server/schema.json +++ b/packages/angular/src/builders/webpack-server/schema.json @@ -271,11 +271,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" diff --git a/packages/angular/src/executors/application/schema.json b/packages/angular/src/executors/application/schema.json index 1d124fa0742778..3dc52639114619 100644 --- a/packages/angular/src/executors/application/schema.json +++ b/packages/angular/src/executors/application/schema.json @@ -600,11 +600,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" diff --git a/packages/angular/src/executors/browser-esbuild/schema.json b/packages/angular/src/executors/browser-esbuild/schema.json index 8bf09295d1c3af..6b8c33a04eb806 100644 --- a/packages/angular/src/executors/browser-esbuild/schema.json +++ b/packages/angular/src/executors/browser-esbuild/schema.json @@ -502,11 +502,12 @@ }, "output": { "type": "string", + "default": "", "description": "Absolute path within the output." } }, "additionalProperties": false, - "required": ["glob", "input", "output"] + "required": ["glob", "input"] }, { "type": "string" diff --git a/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts index af7e8b1932eaad..6cea689118810a 100644 --- a/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -25,7 +25,10 @@ import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter'; import { executeDevServerBuilder } from '../../builders/dev-server/dev-server.impl'; -import { validateDevRemotes } from '../../builders/utilities/module-federation'; +import { + getDynamicMfManifestFile, + validateDevRemotes, +} from '../../builders/utilities/module-federation'; import { extname, join } from 'path'; import { existsSync } from 'fs'; @@ -74,12 +77,10 @@ export async function* moduleFederationDevServerExecutor( return yield* currIter; } - let pathToManifestFile = join( - context.root, - project.sourceRoot, - 'assets/module-federation.manifest.json' - ); - if (options.pathToManifestFile) { + let pathToManifestFile: string; + if (!options.pathToManifestFile) { + pathToManifestFile = getDynamicMfManifestFile(project, context.root); + } else { const userPathToManifestFile = join( context.root, options.pathToManifestFile diff --git a/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap index 8791a99d52b971..44a5b290870394 100644 --- a/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap @@ -243,8 +243,10 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh "executor": "@angular-devkit/build-angular:application", "options": { "assets": [ - "apps/my-dir/my-app/src/favicon.ico", - "apps/my-dir/my-app/src/assets", + { + "glob": "**/*", + "input": "apps/my-dir/my-app/public", + }, ], "browser": "apps/my-dir/my-app/src/main.ts", "index": "apps/my-dir/my-app/src/index.html", @@ -444,8 +446,10 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh "executor": "@angular-devkit/build-angular:application", "options": { "assets": [ - "apps/my-app/src/favicon.ico", - "apps/my-app/src/assets", + { + "glob": "**/*", + "input": "apps/my-app/public", + }, ], "browser": "apps/my-app/src/main.ts", "index": "apps/my-app/src/index.html", @@ -830,7 +834,7 @@ exports[`app --strict should enable strict type checking: e2e tsconfig.json 1`] } `; -exports[`app angular v15 support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = ` +exports[`app angular compat support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = ` "import { ApplicationConfig } from '@angular/platform-browser'; import { provideRouter } from '@angular/router'; import { appRoutes } from './app.routes'; @@ -951,8 +955,10 @@ exports[`app nested should create project configs 1`] = ` "executor": "@angular-devkit/build-angular:application", "options": { "assets": [ - "my-dir/my-app/src/favicon.ico", - "my-dir/my-app/src/assets", + { + "glob": "**/*", + "input": "my-dir/my-app/public", + }, ], "browser": "my-dir/my-app/src/main.ts", "index": "my-dir/my-app/src/index.html", @@ -1065,8 +1071,10 @@ exports[`app not nested should create project configs 1`] = ` "executor": "@angular-devkit/build-angular:application", "options": { "assets": [ - "my-app/src/favicon.ico", - "my-app/src/assets", + { + "glob": "**/*", + "input": "my-app/public", + }, ], "browser": "my-app/src/main.ts", "index": "my-app/src/index.html", diff --git a/packages/angular/src/generators/application/application.spec.ts b/packages/angular/src/generators/application/application.spec.ts index d9c36aa54bb0ac..a631d8c8aa8df9 100644 --- a/packages/angular/src/generators/application/application.spec.ts +++ b/packages/angular/src/generators/application/application.spec.ts @@ -1224,7 +1224,7 @@ describe('app', () => { }); }); - describe('angular v15 support', () => { + describe('angular compat support', () => { beforeEach(() => { appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); updateJson(appTree, 'package.json', (json) => ({ @@ -1298,6 +1298,21 @@ describe('app', () => { .esModuleInterop ).toBeUndefined(); }); + + it('should configure the correct assets for versions lower than v18', async () => { + updateJson(appTree, 'package.json', (json) => ({ + ...json, + dependencies: { ...json.dependencies, '@angular/core': '~17.0.0' }, + })); + + await generateApp(appTree, 'my-app', { rootProject: true }); + + const project = readProjectConfiguration(appTree, 'my-app'); + expect(project.targets.build.options.assets).toStrictEqual([ + './src/favicon.ico', + './src/assets', + ]); + }); }); }); diff --git a/packages/angular/src/generators/application/files/base/src/favicon.ico b/packages/angular/src/generators/application/files/base-18+/public/favicon.ico similarity index 100% rename from packages/angular/src/generators/application/files/base/src/favicon.ico rename to packages/angular/src/generators/application/files/base-18+/public/favicon.ico diff --git a/packages/angular/src/generators/application/files/base/src/assets/.gitkeep__tpl__ b/packages/angular/src/generators/application/files/base-pre18/src/assets/.gitkeep__tpl__ similarity index 100% rename from packages/angular/src/generators/application/files/base/src/assets/.gitkeep__tpl__ rename to packages/angular/src/generators/application/files/base-pre18/src/assets/.gitkeep__tpl__ diff --git a/packages/angular/src/generators/application/files/base-pre18/src/favicon.ico b/packages/angular/src/generators/application/files/base-pre18/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 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 18) { + generateFiles( + tree, + joinPathFragments(__dirname, '../files/base-18+'), + options.appProjectRoot, + substitutions + ); + } else { + generateFiles( + tree, + joinPathFragments(__dirname, '../files/base-pre18'), + options.appProjectRoot, + substitutions + ); + } + if (options.standalone) { generateFiles( tree, diff --git a/packages/angular/src/generators/application/lib/create-project.ts b/packages/angular/src/generators/application/lib/create-project.ts index cf31041020ea43..5451263d0a9cc7 100644 --- a/packages/angular/src/generators/application/lib/create-project.ts +++ b/packages/angular/src/generators/application/lib/create-project.ts @@ -1,4 +1,4 @@ -import { addProjectConfiguration, Tree } from '@nx/devkit'; +import { addProjectConfiguration, joinPathFragments, Tree } from '@nx/devkit'; import type { AngularProjectConfiguration } from '../../../utils/types'; import { getInstalledAngularVersionInfo } from '../../utils/version-utils'; import type { NormalizedSchema } from './normalized-schema'; @@ -64,12 +64,23 @@ export function createProject(tree: Tree, options: NormalizedSchema) { index: `${options.appProjectSourceRoot}/index.html`, [buildMainOptionName]: `${options.appProjectSourceRoot}/main.ts`, polyfills: ['zone.js'], - tsConfig: `${options.appProjectRoot}/tsconfig.app.json`, + tsConfig: joinPathFragments( + options.appProjectRoot, + 'tsconfig.app.json' + ), inlineStyleLanguage, - assets: [ - `${options.appProjectSourceRoot}/favicon.ico`, - `${options.appProjectSourceRoot}/assets`, - ], + assets: + angularMajorVersion >= 18 + ? [ + { + glob: '**/*', + input: joinPathFragments(options.appProjectRoot, 'public'), + }, + ] + : [ + `${options.appProjectSourceRoot}/favicon.ico`, + `${options.appProjectSourceRoot}/assets`, + ], styles: [`${options.appProjectSourceRoot}/styles.${options.style}`], scripts: [], }, diff --git a/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts b/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts index 6c6a254bc973b0..f91ccd7f821f3a 100644 --- a/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts +++ b/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts @@ -73,8 +73,9 @@ export class AngularDevkitKarmaMigrator extends BuilderMigrator { target.options.main = target.options.main && this.convertAsset(target.options.main); target.options.polyfills = Array.isArray(target.options.polyfills) - ? target.options.polyfills.map((p) => this.convertAsset(p)) - : target.options.polyfills && this.convertAsset(target.options.polyfills); + ? target.options.polyfills.map((p) => this.convertSourceRootPath(p)) + : target.options.polyfills && + this.convertSourceRootPath(target.options.polyfills); target.options.tsConfig = target.options.tsConfig && joinPathFragments( diff --git a/packages/angular/src/generators/ng-add/migrators/migrator.ts b/packages/angular/src/generators/ng-add/migrators/migrator.ts index dd9a2fd3d140cb..a4503c7cb0df91 100644 --- a/packages/angular/src/generators/ng-add/migrators/migrator.ts +++ b/packages/angular/src/generators/ng-add/migrators/migrator.ts @@ -36,9 +36,9 @@ export abstract class Migrator { protected convertAsset(asset: string | any): string | any { if (typeof asset === 'string') { - return this.convertSourceRootPath(asset); + return this.convertRootPath(asset); } else { - return { ...asset, input: this.convertSourceRootPath(asset.input) }; + return { ...asset, input: this.convertRootPath(asset.input) }; } } @@ -51,6 +51,15 @@ export abstract class Migrator { : originalPath; } + protected convertSourceRootPath(originalPath: string): string { + return originalPath?.startsWith(this.project.oldSourceRoot) + ? joinPathFragments( + this.project.newSourceRoot, + originalPath.replace(this.project.oldSourceRoot, '') + ) + : originalPath; + } + protected moveFile(from: string, to: string, required: boolean = true): void { if (!this.tree.exists(from)) { if (required) { @@ -120,15 +129,6 @@ export abstract class Migrator { }); } - private convertSourceRootPath(originalPath: string): string { - return originalPath?.startsWith(this.project.oldSourceRoot) - ? joinPathFragments( - this.project.newSourceRoot, - originalPath.replace(this.project.oldSourceRoot, '') - ) - : originalPath; - } - private getTargetValuesForOption( target: TargetConfiguration, optionPath: string diff --git a/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts b/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts index 34cae6c5d1c58b..b96acdc9c974bb 100644 --- a/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts +++ b/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts @@ -257,8 +257,10 @@ export class AppMigrator extends ProjectMigrator { buildOptions.polyfills = buildOptions.polyfills && (Array.isArray(buildOptions.polyfills) - ? buildOptions.polyfills.map((asset) => this.convertAsset(asset)) - : this.convertAsset(buildOptions.polyfills as string)); + ? buildOptions.polyfills.map((asset) => + this.convertSourceRootPath(asset) + ) + : this.convertSourceRootPath(buildOptions.polyfills)); buildOptions.tsConfig = buildOptions.tsConfig && joinPathFragments(this.project.newRoot, basename(buildOptions.tsConfig)); diff --git a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap index 9feb7270fd29c4..5797e19c460738 100644 --- a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap +++ b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap @@ -3,7 +3,7 @@ exports[`Init MF --federationType=dynamic should create a host with the correct configurations 1`] = ` "import { setRemoteDefinitions } from '@nx/angular/mf'; -fetch('/assets/module-federation.manifest.json') +fetch('/module-federation.manifest.json') .then((res) => res.json()) .then(definitions => setRemoteDefinitions(definitions)) .then(() => import('./bootstrap').catch(err => console.error(err)));" @@ -12,7 +12,7 @@ fetch('/assets/module-federation.manifest.json') exports[`Init MF --federationType=dynamic should create a host with the correct configurations when --typescriptConfiguration=true 1`] = ` "import { setRemoteDefinitions } from '@nx/angular/mf'; -fetch('/assets/module-federation.manifest.json') +fetch('/module-federation.manifest.json') .then((res) => res.json()) .then(definitions => setRemoteDefinitions(definitions)) .then(() => import('./bootstrap').catch(err => console.error(err)));" diff --git a/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts b/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts index ffb94f3806d126..64899403357022 100644 --- a/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts +++ b/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts @@ -32,14 +32,8 @@ export function checkIsCommaNeeded(mfRemoteText: string) { export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) { if (options.host) { const hostProject = readProjectConfiguration(tree, options.host); - const pathToMFManifest = joinPathFragments( - hostProject.sourceRoot, - 'assets/module-federation.manifest.json' - ); - const hostFederationType = determineHostFederationType( - tree, - pathToMFManifest - ); + const pathToMFManifest = getDynamicManifestFile(tree, hostProject); + const hostFederationType = !!pathToMFManifest ? 'dynamic' : 'static'; const isHostUsingTypescriptConfig = tree.exists( joinPathFragments(hostProject.root, 'module-federation.config.ts') @@ -60,11 +54,23 @@ export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) { } } -function determineHostFederationType( +function getDynamicManifestFile( tree: Tree, - pathToMfManifest: string -): 'dynamic' | 'static' { - return tree.exists(pathToMfManifest) ? 'dynamic' : 'static'; + project: ProjectConfiguration +): string | undefined { + // {sourceRoot}/assets/module-federation.manifest.json was the generated + // path for the manifest file in the past. We now generate the manifest + // file at {root}/public/module-federation.manifest.json. This check + // ensures that we can still support the old path for backwards + // compatibility since old projects may still have the manifest file + // at the old path. + return [ + joinPathFragments(project.root, 'public/module-federation.manifest.json'), + joinPathFragments( + project.sourceRoot, + 'assets/module-federation.manifest.json' + ), + ].find((path) => tree.exists(path)); } function addRemoteToStaticHost( diff --git a/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts b/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts index 78246735d822d8..9dca07307885a9 100644 --- a/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts +++ b/packages/angular/src/generators/setup-mf/lib/fix-bootstrap.ts @@ -1,5 +1,4 @@ -import type { Tree } from '@nx/devkit'; -import { joinPathFragments } from '@nx/devkit'; +import { joinPathFragments, type Tree } from '@nx/devkit'; import type { Schema } from '../schema'; export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) { @@ -12,20 +11,27 @@ export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) { } const bootstrapImportCode = `import('./bootstrap').catch(err => console.error(err))`; + if (options.mfType === 'remote' || options.federationType === 'static') { + tree.write(mainFilePath, `${bootstrapImportCode};`); + } else { + let manifestPath = '/assets/module-federation.manifest.json'; + if ( + tree.exists( + joinPathFragments(appRoot, 'public/module-federation.manifest.json') + ) + ) { + manifestPath = '/module-federation.manifest.json'; + } - const fetchMFManifestCode = `import { setRemoteDefinitions } from '@nx/angular/mf'; + const fetchMFManifestCode = `import { setRemoteDefinitions } from '@nx/angular/mf'; -fetch('/assets/module-federation.manifest.json') +fetch('${manifestPath}') .then((res) => res.json()) .then(definitions => setRemoteDefinitions(definitions)) .then(() => ${bootstrapImportCode});`; - tree.write( - mainFilePath, - options.mfType === 'host' && options.federationType === 'dynamic' - ? fetchMFManifestCode - : `${bootstrapImportCode};` - ); + tree.write(mainFilePath, fetchMFManifestCode); + } } const standaloneBootstrapCode = diff --git a/packages/angular/src/generators/setup-mf/lib/setup-host-if-dynamic.ts b/packages/angular/src/generators/setup-mf/lib/setup-host-if-dynamic.ts index 5717e019318544..d76ae48c3e94a5 100644 --- a/packages/angular/src/generators/setup-mf/lib/setup-host-if-dynamic.ts +++ b/packages/angular/src/generators/setup-mf/lib/setup-host-if-dynamic.ts @@ -5,17 +5,25 @@ import { updateProjectConfiguration, } from '@nx/devkit'; import type { Schema } from '../schema'; +import { getInstalledAngularVersionInfo } from '../../utils/version-utils'; export function setupHostIfDynamic(tree: Tree, options: Schema) { if (options.federationType === 'static') { return; } + const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree); const project = readProjectConfiguration(tree, options.appName); - const pathToMFManifest = joinPathFragments( - project.sourceRoot, - 'assets/module-federation.manifest.json' - ); + const pathToMFManifest = + angularMajorVersion >= 18 + ? joinPathFragments( + project.root, + 'public/module-federation.manifest.json' + ) + : joinPathFragments( + project.sourceRoot, + 'assets/module-federation.manifest.json' + ); if (!tree.exists(pathToMFManifest)) { tree.write(pathToMFManifest, '{}'); diff --git a/packages/angular/src/generators/setup-mf/setup-mf.spec.ts b/packages/angular/src/generators/setup-mf/setup-mf.spec.ts index 5a7491a203e0ea..322a557c11d220 100644 --- a/packages/angular/src/generators/setup-mf/setup-mf.spec.ts +++ b/packages/angular/src/generators/setup-mf/setup-mf.spec.ts @@ -518,7 +518,7 @@ describe('Init MF', () => { 'remotes: []' ); expect( - tree.exists('app1/src/assets/module-federation.manifest.json') + tree.exists('app1/public/module-federation.manifest.json') ).toBeTruthy(); expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot(); }); @@ -540,7 +540,7 @@ describe('Init MF', () => { 'remotes: []' ); expect( - tree.exists('app1/src/assets/module-federation.manifest.json') + tree.exists('app1/public/module-federation.manifest.json') ).toBeTruthy(); expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot(); }); @@ -571,7 +571,7 @@ describe('Init MF', () => { 'remotes: []' ); expect( - readJson(tree, 'app1/src/assets/module-federation.manifest.json') + readJson(tree, 'app1/public/module-federation.manifest.json') ).toEqual({ remote1: 'http://localhost:4201', }); @@ -606,7 +606,7 @@ describe('Init MF', () => { 'remotes: []' ); expect( - readJson(tree, 'app1/src/assets/module-federation.manifest.json') + readJson(tree, 'app1/public/module-federation.manifest.json') ).toEqual({ remote1: 'http://localhost:4201', }); @@ -645,7 +645,7 @@ describe('Init MF', () => { 'remotes: []' ); expect( - readJson(tree, 'app1/src/assets/module-federation.manifest.json') + readJson(tree, 'app1/public/module-federation.manifest.json') ).toEqual({ remote1: 'http://localhost:4201', }); @@ -681,7 +681,7 @@ describe('Init MF', () => { 'remotes: []' ); expect( - readJson(tree, 'app1/src/assets/module-federation.manifest.json') + readJson(tree, 'app1/public/module-federation.manifest.json') ).toEqual({ remote1: 'http://localhost:4201', }); diff --git a/packages/angular/src/generators/setup-mf/setup-mf.ts b/packages/angular/src/generators/setup-mf/setup-mf.ts index bf3f547c27b4e6..11ee73929b5ff8 100644 --- a/packages/angular/src/generators/setup-mf/setup-mf.ts +++ b/packages/angular/src/generators/setup-mf/setup-mf.ts @@ -56,8 +56,6 @@ export async function setupMf(tree: Tree, rawOptions: Schema) { updateTsConfig(tree, options); setupServeTarget(tree, options); - fixBootstrap(tree, projectConfig.root, options); - if (options.mfType === 'host') { setupHostIfDynamic(tree, options); updateHostAppRoutes(tree, options); @@ -78,6 +76,8 @@ export async function setupMf(tree: Tree, rawOptions: Schema) { } } + fixBootstrap(tree, projectConfig.root, options); + if (!options.skipE2E) { addCypressOnErrorWorkaround(tree, options); } diff --git a/packages/angular/src/generators/setup-ssr/__snapshots__/setup-ssr.spec.ts.snap b/packages/angular/src/generators/setup-ssr/__snapshots__/setup-ssr.spec.ts.snap index 5c6b3045c1c984..654e6295bbe9ca 100644 --- a/packages/angular/src/generators/setup-ssr/__snapshots__/setup-ssr.spec.ts.snap +++ b/packages/angular/src/generators/setup-ssr/__snapshots__/setup-ssr.spec.ts.snap @@ -91,8 +91,10 @@ exports[`setupSSR with application builder should create the files correctly for "executor": "@angular-devkit/build-angular:application", "options": { "assets": [ - "app1/src/favicon.ico", - "app1/src/assets", + { + "glob": "**/*", + "input": "app1/public", + }, ], "browser": "app1/src/main.ts", "index": "app1/src/index.html", @@ -208,8 +210,10 @@ exports[`setupSSR with application builder should create the files correctly for "executor": "@angular-devkit/build-angular:application", "options": { "assets": [ - "app1/src/favicon.ico", - "app1/src/assets", + { + "glob": "**/*", + "input": "app1/public", + }, ], "browser": "app1/src/main.ts", "index": "app1/src/index.html",