From 81df8485a1be38ff9e706d25ffb5518f9d37b5ed Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 12 Mar 2024 19:33:27 +0000 Subject: [PATCH] fix(module-federation): ensure targetDefaults for module federation executors are setup correctly (#22282) --- packages/angular/migrations.json | 6 + .../generators/utils/add-mf-env-to-inputs.ts | 5 +- .../add-mf-env-var-to-target-defaults.spec.ts | 2 + .../fix-target-defaults-inputs.spec.ts | 127 ++++++++++++++++++ .../fix-target-defaults-inputs.ts | 75 +++++++++++ packages/react/migrations.json | 6 + .../add-mf-env-var-to-target-defaults.spec.ts | 2 + .../fix-target-defaults-inputs.spec.ts | 127 ++++++++++++++++++ .../fix-target-defaults-inputs.ts | 75 +++++++++++ .../react/src/utils/add-mf-env-to-inputs.ts | 5 +- 10 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts create mode 100644 packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts create mode 100644 packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts create mode 100644 packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts diff --git a/packages/angular/migrations.json b/packages/angular/migrations.json index c6f295f5f6579..ab21ab71b1344 100644 --- a/packages/angular/migrations.json +++ b/packages/angular/migrations.json @@ -371,6 +371,12 @@ }, "description": "Update the @angular/cli package version to ~17.2.0.", "factory": "./src/migrations/update-18-1-0/update-angular-cli" + }, + "fix-target-defaults-for-webpack-browser": { + "cli": "nx", + "version": "18.1.1-beta.0", + "description": "Ensure targetDefaults inputs for task hashing when '@nx/angular:webpack-browser' is used are correct for Module Federation.", + "factory": "./src/migrations/update-18-1-1/fix-target-defaults-inputs" } }, "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 a989ca5c679de..ba8928796ac8e 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 @@ -7,7 +7,10 @@ export function addMfEnvToTargetDefaultInputs(tree: Tree) { nxJson.targetDefaults ??= {}; nxJson.targetDefaults[webpackExecutor] ??= {}; - nxJson.targetDefaults[webpackExecutor].inputs ??= []; + nxJson.targetDefaults[webpackExecutor].inputs ??= [ + 'production', + '^production', + ]; 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 78eecc67326dd..36e2ed5420537 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 @@ -26,6 +26,8 @@ describe('addMfEnvVarToTargetDefaults', () => { { "@nx/angular:webpack-browser": { "inputs": [ + "production", + "^production", { "env": "NX_MF_DEV_SERVER_STATIC_REMOTES", }, diff --git a/packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts b/packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts new file mode 100644 index 0000000000000..a9b3f77bbae1d --- /dev/null +++ b/packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts @@ -0,0 +1,127 @@ +import fixTargetDefaultInputs from './fix-target-defaults-inputs'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { addProjectConfiguration, readNxJson, updateNxJson } from '@nx/devkit'; + +describe('fixTargetDefaultsInputs', () => { + it('should add the executor and input when it does not exist', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test', { + root: '', + targets: { + build: { + executor: '@nx/angular:webpack-browser', + }, + }, + }); + + tree.write('module-federation.config.ts', ''); + + // ACT + await fixTargetDefaultInputs(tree); + + // ASSERT + const nxJson = readNxJson(tree); + expect(nxJson.targetDefaults).toMatchInlineSnapshot(` + { + "@nx/angular:webpack-browser": { + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_SERVER_STATIC_REMOTES", + }, + ], + }, + "build": { + "cache": true, + }, + "lint": { + "cache": true, + }, + } + `); + }); + + it('should not add the executor and input when no project uses it', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test', { + root: '', + targets: { + build: { + executor: '@nx/angular:module-federation-dev-server', + }, + }, + }); + + // ACT + await fixTargetDefaultInputs(tree); + + // ASSERT + const nxJson = readNxJson(tree); + expect(nxJson.targetDefaults).toMatchInlineSnapshot(` + { + "build": { + "cache": true, + }, + "lint": { + "cache": true, + }, + } + `); + }); + + it('should update the executor and input target default when it already exists', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test', { + root: '', + targets: { + build: { + executor: '@nx/angular:webpack-browser', + }, + }, + }); + tree.write('module-federation.config.ts', ''); + + let nxJson = readNxJson(tree); + nxJson = { + ...nxJson, + targetDefaults: { + ...nxJson.targetDefaults, + ['@nx/angular:webpack-browser']: { + inputs: ['^build'], + }, + }, + }; + + updateNxJson(tree, nxJson); + + // ACT + await fixTargetDefaultInputs(tree); + + // ASSERT + nxJson = readNxJson(tree); + expect(nxJson.targetDefaults).toMatchInlineSnapshot(` + { + "@nx/angular:webpack-browser": { + "inputs": [ + "^build", + "production", + "^production", + { + "env": "NX_MF_DEV_SERVER_STATIC_REMOTES", + }, + ], + }, + "build": { + "cache": true, + }, + "lint": { + "cache": true, + }, + } + `); + }); +}); diff --git a/packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts b/packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts new file mode 100644 index 0000000000000..19fc17b1117d7 --- /dev/null +++ b/packages/angular/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts @@ -0,0 +1,75 @@ +import { + getProjects, + type Tree, + type ProjectConfiguration, + joinPathFragments, + formatFiles, + readNxJson, + updateNxJson, +} from '@nx/devkit'; + +export default async function (tree: Tree) { + if (!isWebpackBrowserUsed(tree)) { + return; + } + ensureTargetDefaultsContainProductionInputs(tree); + + await formatFiles(tree); +} + +function ensureTargetDefaultsContainProductionInputs(tree: Tree) { + const nxJson = readNxJson(tree); + const webpackExecutor = '@nx/angular:webpack-browser'; + const mfEnvVar = 'NX_MF_DEV_SERVER_STATIC_REMOTES'; + + nxJson.targetDefaults[webpackExecutor] ??= {}; + + nxJson.targetDefaults[webpackExecutor].inputs ??= [ + 'production', + '^production', + { env: mfEnvVar }, + ]; + + if (!nxJson.targetDefaults[webpackExecutor].inputs.includes('production')) { + nxJson.targetDefaults[webpackExecutor].inputs.push('production'); + } + + if (!nxJson.targetDefaults[webpackExecutor].inputs.includes('^production')) { + nxJson.targetDefaults[webpackExecutor].inputs.push('^production'); + } + + let mfEnvVarExists = false; + for (const input of nxJson.targetDefaults[webpackExecutor].inputs) { + if (typeof input === 'object' && input['env'] === mfEnvVar) { + mfEnvVarExists = true; + break; + } + } + + if (!mfEnvVarExists) { + nxJson.targetDefaults[webpackExecutor].inputs.push({ env: mfEnvVar }); + } + + updateNxJson(tree, nxJson); +} + +function isWebpackBrowserUsed(tree: Tree) { + const projects = getProjects(tree); + for (const project of projects.values()) { + const targets = project.targets || {}; + for (const [_, target] of Object.entries(targets)) { + if ( + target.executor === '@nx/angular:webpack-browser' && + (tree.exists( + joinPathFragments(project.root, 'module-federation.config.ts') + ) || + tree.exists( + joinPathFragments(project.root, 'module-federation.config.js') + )) + ) { + return true; + } + } + } + return false; +} diff --git a/packages/react/migrations.json b/packages/react/migrations.json index 7cc691b277542..16efe10c5b4fe 100644 --- a/packages/react/migrations.json +++ b/packages/react/migrations.json @@ -53,6 +53,12 @@ "version": "18.0.0-beta.0", "description": "Add NX_MF_DEV_SERVER_STATIC_REMOTES to inputs for task hashing when '@nx/webpack:webpack' is used for Module Federation.", "factory": "./src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults" + }, + "fix-target-defaults-for-webpack": { + "cli": "nx", + "version": "18.1.1-beta.0", + "description": "Ensure targetDefaults inputs for task hashing when '@nx/webpack:webpack' is used are correct for Module Federation.", + "factory": "./src/migrations/update-18-1-1/fix-target-defaults-inputs" } }, "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 bd594493e69c8..8bc815a804645 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 @@ -25,6 +25,8 @@ describe('addMfEnvVarToTargetDefaults', () => { { "@nx/webpack:webpack": { "inputs": [ + "production", + "^production", { "env": "NX_MF_DEV_SERVER_STATIC_REMOTES", }, diff --git a/packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts b/packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts new file mode 100644 index 0000000000000..85e1b3042554d --- /dev/null +++ b/packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.spec.ts @@ -0,0 +1,127 @@ +import fixTargetDefaultsInputs from './fix-target-defaults-inputs'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { addProjectConfiguration, readNxJson, updateNxJson } from '@nx/devkit'; + +describe('fixTargetDefaultsInputs', () => { + it('should add the executor and input when it does not exist', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test', { + root: '', + targets: { + build: { + executor: '@nx/webpack:webpack', + }, + }, + }); + tree.write('module-federation.config.ts', ''); + + // ACT + await fixTargetDefaultsInputs(tree); + + // ASSERT + const nxJson = readNxJson(tree); + expect(nxJson.targetDefaults).toMatchInlineSnapshot(` + { + "@nx/webpack:webpack": { + "inputs": [ + "production", + "^production", + { + "env": "NX_MF_DEV_SERVER_STATIC_REMOTES", + }, + ], + }, + "build": { + "cache": true, + }, + "lint": { + "cache": true, + }, + } + `); + }); + + it('should not add the executor and input when no project uses it', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test', { + root: '', + targets: { + build: { + executor: '@nx/angular:module-federation-dev-server', + }, + }, + }); + + // ACT + await fixTargetDefaultsInputs(tree); + + // ASSERT + const nxJson = readNxJson(tree); + expect(nxJson.targetDefaults).toMatchInlineSnapshot(` + { + "build": { + "cache": true, + }, + "lint": { + "cache": true, + }, + } + `); + }); + + it('should update the executor and input target default when it already exists', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'test', { + root: '', + targets: { + build: { + executor: '@nx/webpack:webpack', + }, + }, + }); + + tree.write('module-federation.config.ts', ''); + + let nxJson = readNxJson(tree); + nxJson = { + ...nxJson, + targetDefaults: { + ...nxJson.targetDefaults, + ['@nx/webpack:webpack']: { + inputs: ['^build'], + }, + }, + }; + + updateNxJson(tree, nxJson); + + // ACT + await fixTargetDefaultsInputs(tree); + + // ASSERT + nxJson = readNxJson(tree); + expect(nxJson.targetDefaults).toMatchInlineSnapshot(` + { + "@nx/webpack:webpack": { + "inputs": [ + "^build", + "production", + "^production", + { + "env": "NX_MF_DEV_SERVER_STATIC_REMOTES", + }, + ], + }, + "build": { + "cache": true, + }, + "lint": { + "cache": true, + }, + } + `); + }); +}); diff --git a/packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts b/packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts new file mode 100644 index 0000000000000..ee2701bd71878 --- /dev/null +++ b/packages/react/src/migrations/update-18-1-1/fix-target-defaults-inputs.ts @@ -0,0 +1,75 @@ +import { + getProjects, + type Tree, + type ProjectConfiguration, + joinPathFragments, + formatFiles, + readNxJson, + updateNxJson, +} from '@nx/devkit'; + +export default async function (tree: Tree) { + if (!hasModuleFederationProject(tree)) { + return; + } + ensureTargetDefaultsContainProductionInputs(tree); + + await formatFiles(tree); +} + +function ensureTargetDefaultsContainProductionInputs(tree: Tree) { + const nxJson = readNxJson(tree); + const webpackExecutor = '@nx/webpack:webpack'; + const mfEnvVar = 'NX_MF_DEV_SERVER_STATIC_REMOTES'; + + nxJson.targetDefaults[webpackExecutor] ??= {}; + + nxJson.targetDefaults[webpackExecutor].inputs ??= [ + 'production', + '^production', + { env: mfEnvVar }, + ]; + + if (!nxJson.targetDefaults[webpackExecutor].inputs.includes('production')) { + nxJson.targetDefaults[webpackExecutor].inputs.push('production'); + } + + if (!nxJson.targetDefaults[webpackExecutor].inputs.includes('^production')) { + nxJson.targetDefaults[webpackExecutor].inputs.push('^production'); + } + + let mfEnvVarExists = false; + for (const input of nxJson.targetDefaults[webpackExecutor].inputs) { + if (typeof input === 'object' && input['env'] === mfEnvVar) { + mfEnvVarExists = true; + break; + } + } + + if (!mfEnvVarExists) { + nxJson.targetDefaults[webpackExecutor].inputs.push({ env: mfEnvVar }); + } + + updateNxJson(tree, nxJson); +} + +function hasModuleFederationProject(tree: Tree) { + const projects = getProjects(tree); + for (const project of projects.values()) { + const targets = project.targets || {}; + for (const [_, target] of Object.entries(targets)) { + if ( + target.executor === '@nx/webpack:webpack' && + (tree.exists( + joinPathFragments(project.root, 'module-federation.config.ts') + ) || + tree.exists( + joinPathFragments(project.root, 'module-federation.config.js') + )) + ) { + return true; + } + } + } + return false; +} 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 6786fb628289f..0a899eb95317d 100644 --- a/packages/react/src/utils/add-mf-env-to-inputs.ts +++ b/packages/react/src/utils/add-mf-env-to-inputs.ts @@ -7,7 +7,10 @@ export function addMfEnvToTargetDefaultInputs(tree: Tree) { nxJson.targetDefaults ??= {}; nxJson.targetDefaults[webpackExecutor] ??= {}; - nxJson.targetDefaults[webpackExecutor].inputs ??= []; + nxJson.targetDefaults[webpackExecutor].inputs ??= [ + 'production', + '^production', + ]; let mfEnvVarExists = false; for (const input of nxJson.targetDefaults[webpackExecutor].inputs) {