diff --git a/packages/angular/migrations.json b/packages/angular/migrations.json index 69bd9bc2a7015..0d23b93525274 100644 --- a/packages/angular/migrations.json +++ b/packages/angular/migrations.json @@ -428,6 +428,12 @@ }, "description": "Update the @angular/cli package version to ~18.2.0.", "factory": "./src/migrations/update-19-6-0/update-angular-cli" + }, + "update-19-6-1-ensure-module-federation-target-defaults": { + "cli": "nx", + "version": "19.6.1-beta.0", + "description": "Ensure Target Defaults are set correctly for Module Federation.", + "factory": "./src/migrations/update-19-6-1/ensure-depends-on-for-mf" } }, "packageJsonUpdates": { diff --git a/packages/angular/src/generators/utils/add-mf-env-to-inputs.ts b/packages/angular/src/generators/utils/add-mf-env-to-inputs.ts index 68a77408ba50a..f87974dead556 100644 --- a/packages/angular/src/generators/utils/add-mf-env-to-inputs.ts +++ b/packages/angular/src/generators/utils/add-mf-env-to-inputs.ts @@ -11,6 +11,7 @@ export function addMfEnvToTargetDefaultInputs(tree: Tree) { 'production', '^production', ]; + nxJson.targetDefaults[webpackExecutor].dependsOn ??= ['^build']; let mfEnvVarExists = false; for (const input of nxJson.targetDefaults[webpackExecutor].inputs) { diff --git a/packages/angular/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts b/packages/angular/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts index 2495afbdbca59..3c005dd9156d1 100644 --- a/packages/angular/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts +++ b/packages/angular/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts @@ -25,6 +25,9 @@ describe('addMfEnvVarToTargetDefaults', () => { expect(nxJson.targetDefaults).toMatchInlineSnapshot(` { "@nx/angular:webpack-browser": { + "dependsOn": [ + "^build", + ], "inputs": [ "production", "^production", @@ -106,6 +109,9 @@ describe('addMfEnvVarToTargetDefaults', () => { expect(nxJson.targetDefaults).toMatchInlineSnapshot(` { "@nx/angular:webpack-browser": { + "dependsOn": [ + "^build", + ], "inputs": [ "^build", { diff --git a/packages/angular/src/migrations/update-19-6-1/ensure-depends-on-for-mf.spec.ts b/packages/angular/src/migrations/update-19-6-1/ensure-depends-on-for-mf.spec.ts new file mode 100644 index 0000000000000..ebef7640a821f --- /dev/null +++ b/packages/angular/src/migrations/update-19-6-1/ensure-depends-on-for-mf.spec.ts @@ -0,0 +1,154 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { + addProjectConfiguration, + readNxJson, + updateNxJson, + type Tree, +} from '@nx/devkit'; +import ensureDependsOnForMf from './ensure-depends-on-for-mf'; + +describe('ensure-depends-on-for-mf', () => { + it('should ensure targetDefault is added correctly if not exists', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/angular:webpack-browser'] = { + inputs: ['production', '^production'], + }; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/angular:webpack-browser']) + .toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); + + it('should ensure targetDefault is added correctly if there are no targetDefaults', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults = {}; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/angular:webpack-browser']) + .toMatchInlineSnapshot(` + { + "cache": true, + "dependsOn": [ + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); + + it('should ensure targetDefault is updated correctly if missing ^build', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/angular:webpack-browser'] = { + inputs: ['production', '^production'], + dependsOn: ['some-task'], + }; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/angular:webpack-browser']) + .toMatchInlineSnapshot(` + { + "dependsOn": [ + "some-task", + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); + + it('should do nothing if targetDefault is set up correctly', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/angular:webpack-browser'] = { + inputs: ['production', '^production', { env: 'NX_MF_DEV_REMOTES' }], + dependsOn: ['^build'], + }; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/angular:webpack-browser']) + .toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); +}); + +function addProject(tree: Tree) { + tree.write('app/webpack.config.ts', `withModuleFederation`); + addProjectConfiguration(tree, 'app', { + name: 'app', + root: 'app', + projectType: 'application', + targets: { + build: { + executor: '@nx/angular:webpack-browser', + options: { webpackConfig: 'app/webpack.config.ts' }, + }, + }, + }); +} diff --git a/packages/angular/src/migrations/update-19-6-1/ensure-depends-on-for-mf.ts b/packages/angular/src/migrations/update-19-6-1/ensure-depends-on-for-mf.ts new file mode 100644 index 0000000000000..529020e6f2b66 --- /dev/null +++ b/packages/angular/src/migrations/update-19-6-1/ensure-depends-on-for-mf.ts @@ -0,0 +1,68 @@ +import { formatFiles, readNxJson, type Tree, updateNxJson } from '@nx/devkit'; +import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils'; +import type { WebpackExecutorOptions } from '@nx/webpack'; + +export default async function (tree: Tree) { + let usesModuleFederation = false; + forEachExecutorOptions( + tree, + '@nx/angular:webpack-browser', + (options, projectName, targetName) => { + const webpackConfig: string = options.webpackConfig; + if (!webpackConfig) { + return; + } + + const webpackContents = tree.read(webpackConfig, 'utf-8'); + if ( + ['withModuleFederation', 'withModuleFederationForSSR'].some((p) => + webpackContents.includes(p) + ) + ) { + usesModuleFederation = true; + } + } + ); + + if (!usesModuleFederation) { + return; + } + + const nxJson = readNxJson(tree); + const nxMFDevRemotesEnvVar = 'NX_MF_DEV_REMOTES'; + if ( + !nxJson.targetDefaults || + !nxJson.targetDefaults?.['@nx/angular:webpack-browser'] + ) { + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/angular:webpack-browser'] = { + cache: true, + inputs: ['production', '^production', { env: nxMFDevRemotesEnvVar }], + dependsOn: ['^build'], + }; + } else { + nxJson.targetDefaults['@nx/angular:webpack-browser'].dependsOn ??= []; + if ( + !nxJson.targetDefaults['@nx/angular:webpack-browser'].dependsOn.includes( + '^build' + ) + ) { + nxJson.targetDefaults['@nx/angular:webpack-browser'].dependsOn.push( + '^build' + ); + } + + nxJson.targetDefaults['@nx/angular:webpack-browser'].inputs ??= []; + if ( + !nxJson.targetDefaults['@nx/angular:webpack-browser'].inputs.find((i) => + typeof i === 'string' ? false : i['env'] === nxMFDevRemotesEnvVar + ) + ) { + nxJson.targetDefaults['@nx/angular:webpack-browser'].inputs.push({ + env: nxMFDevRemotesEnvVar, + }); + } + } + updateNxJson(tree, nxJson); + await formatFiles(tree); +} diff --git a/packages/react/migrations.json b/packages/react/migrations.json index dfbd617e84e42..7a4034950f05e 100644 --- a/packages/react/migrations.json +++ b/packages/react/migrations.json @@ -53,6 +53,12 @@ "version": "19.6.0-beta.4", "description": "Update the server file for Module Federation SSR port value to be the same as the 'serve' target port value.", "factory": "./src/migrations/update-19-6-0/update-ssr-server-port" + }, + "update-19-6-1-ensure-module-federation-target-defaults": { + "cli": "nx", + "version": "19.6.1-beta.0", + "description": "Ensure Target Defaults are set correctly for Module Federation.", + "factory": "./src/migrations/update-19-6-1/ensure-depends-on-for-mf" } }, "packageJsonUpdates": { diff --git a/packages/react/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts b/packages/react/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts index a5164c0203414..b459725cf2161 100644 --- a/packages/react/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts +++ b/packages/react/src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults.spec.ts @@ -24,6 +24,9 @@ describe('addMfEnvVarToTargetDefaults', () => { expect(nxJson.targetDefaults).toMatchInlineSnapshot(` { "@nx/webpack:webpack": { + "dependsOn": [ + "^build", + ], "inputs": [ "production", "^production", @@ -106,6 +109,9 @@ describe('addMfEnvVarToTargetDefaults', () => { expect(nxJson.targetDefaults).toMatchInlineSnapshot(` { "@nx/webpack:webpack": { + "dependsOn": [ + "^build", + ], "inputs": [ "^build", { diff --git a/packages/react/src/migrations/update-19-6-1/ensure-depends-on-for-mf.spec.ts b/packages/react/src/migrations/update-19-6-1/ensure-depends-on-for-mf.spec.ts new file mode 100644 index 0000000000000..811c25347b586 --- /dev/null +++ b/packages/react/src/migrations/update-19-6-1/ensure-depends-on-for-mf.spec.ts @@ -0,0 +1,154 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { + addProjectConfiguration, + readNxJson, + updateNxJson, + type Tree, +} from '@nx/devkit'; +import ensureDependsOnForMf from './ensure-depends-on-for-mf'; + +describe('ensure-depends-on-for-mf', () => { + it('should ensure targetDefault is added correctly if not exists', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/webpack:webpack'] = { + inputs: ['production', '^production'], + }; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/webpack:webpack']) + .toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); + + it('should ensure targetDefault is added correctly if there are no targetDefaults', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults = {}; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/webpack:webpack']) + .toMatchInlineSnapshot(` + { + "cache": true, + "dependsOn": [ + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); + + it('should ensure targetDefault is updated correctly if missing ^build', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/webpack:webpack'] = { + inputs: ['production', '^production'], + dependsOn: ['some-task'], + }; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/webpack:webpack']) + .toMatchInlineSnapshot(` + { + "dependsOn": [ + "some-task", + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); + + it('should do nothing if targetDefault is set up correctly', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + const nxJson = readNxJson(tree); + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/webpack:webpack'] = { + inputs: ['production', '^production', { env: 'NX_MF_DEV_REMOTES' }], + dependsOn: ['^build'], + }; + updateNxJson(tree, nxJson); + addProject(tree); + + // ACT + await ensureDependsOnForMf(tree); + + // ASSERT + expect(readNxJson(tree).targetDefaults['@nx/webpack:webpack']) + .toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + ], + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_REMOTES", + }, + ], + } + `); + }); +}); + +function addProject(tree: Tree) { + tree.write('app/webpack.config.ts', `withModuleFederation`); + addProjectConfiguration(tree, 'app', { + name: 'app', + root: 'app', + projectType: 'application', + targets: { + build: { + executor: '@nx/webpack:webpack', + options: { webpackConfig: 'app/webpack.config.ts' }, + }, + }, + }); +} diff --git a/packages/react/src/migrations/update-19-6-1/ensure-depends-on-for-mf.ts b/packages/react/src/migrations/update-19-6-1/ensure-depends-on-for-mf.ts new file mode 100644 index 0000000000000..6a5b54c3b3ebb --- /dev/null +++ b/packages/react/src/migrations/update-19-6-1/ensure-depends-on-for-mf.ts @@ -0,0 +1,64 @@ +import { formatFiles, readNxJson, type Tree, updateNxJson } from '@nx/devkit'; +import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils'; +import type { WebpackExecutorOptions } from '@nx/webpack'; + +export default async function (tree: Tree) { + let usesModuleFederation = false; + forEachExecutorOptions( + tree, + '@nx/webpack:webpack', + (options, projectName, targetName) => { + const webpackConfig: string = options.webpackConfig; + if (!webpackConfig) { + return; + } + + const webpackContents = tree.read(webpackConfig, 'utf-8'); + if ( + ['withModuleFederation', 'withModuleFederationForSSR'].some((p) => + webpackContents.includes(p) + ) + ) { + usesModuleFederation = true; + } + } + ); + + if (!usesModuleFederation) { + return; + } + + const nxJson = readNxJson(tree); + const nxMFDevRemotesEnvVar = 'NX_MF_DEV_REMOTES'; + if ( + !nxJson.targetDefaults || + !nxJson.targetDefaults?.['@nx/webpack:webpack'] + ) { + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/webpack:webpack'] = { + cache: true, + inputs: ['production', '^production', { env: nxMFDevRemotesEnvVar }], + dependsOn: ['^build'], + }; + } else { + nxJson.targetDefaults['@nx/webpack:webpack'].dependsOn ??= []; + if ( + !nxJson.targetDefaults['@nx/webpack:webpack'].dependsOn.includes('^build') + ) { + nxJson.targetDefaults['@nx/webpack:webpack'].dependsOn.push('^build'); + } + + nxJson.targetDefaults['@nx/webpack:webpack'].inputs ??= []; + if ( + !nxJson.targetDefaults['@nx/webpack:webpack'].inputs.find((i) => + typeof i === 'string' ? false : i['env'] === nxMFDevRemotesEnvVar + ) + ) { + nxJson.targetDefaults['@nx/webpack:webpack'].inputs.push({ + env: nxMFDevRemotesEnvVar, + }); + } + } + updateNxJson(tree, nxJson); + await formatFiles(tree); +} diff --git a/packages/react/src/utils/add-mf-env-to-inputs.ts b/packages/react/src/utils/add-mf-env-to-inputs.ts index 5738317ac01eb..e4b117ed542ea 100644 --- a/packages/react/src/utils/add-mf-env-to-inputs.ts +++ b/packages/react/src/utils/add-mf-env-to-inputs.ts @@ -11,6 +11,7 @@ export function addMfEnvToTargetDefaultInputs(tree: Tree) { 'production', '^production', ]; + nxJson.targetDefaults[webpackExecutor].dependsOn ??= ['^build']; let mfEnvVarExists = false; for (const input of nxJson.targetDefaults[webpackExecutor].inputs) {