From 2d210b8d0e92e2c6e491e9f472fcc3f56be83cd3 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Tue, 1 Apr 2025 21:16:05 -0400 Subject: [PATCH] fix(bundling): webpack and rspack builds respect output.clean config option (#30573) This PR fixes and issue where the standard `output.clean` option is ignored and replaced by the Nx-specific `deleteOutputPath` option on the `NxAppWebpackPlugin` and `NxAppRspackPlugin` plugins. We want to allow users to use standards over our own features, so if we see that `output.clean` is set in webpack/rspack config, then we use that value. For example, an Rspack config could be: ```js const { NxAppRspackPlugin } = require("@nx/rspack/app-plugin"); const { join } = require("path"); module.exports = { output: { path: join(__dirname, "dist/demo"), clean: false, // <-- THIS DOES NOT WORK! }, plugins: [ new NxAppRspackPlugin({ // ... }), ], }; ``` But even though `output.clean` is `false`, each build will still delete `dist`. The only way to disable that behavior is to use the Nx-specific option like this: ```js const { NxAppRspackPlugin } = require("@nx/rspack/app-plugin"); const { join } = require("path"); module.exports = { output: { path: join(__dirname, "dist/demo"), }, plugins: [ new NxAppRspackPlugin({ deleteOutputPath: false, // ... }), ], }; ``` ## Current Behavior Setting `output.clean` in Webpack/Rspack config does nothing, and we always default our own `deleteOutputPath` to `true`. ## Expected Behavior Setting `output.clean` standard option is respected. ## Related Issue(s) Fixes # --- docs/shared/packages/webpack/webpack-plugins.md | 4 ++++ e2e/rspack/tests/rspack.spec.ts | 7 +++++++ e2e/webpack/src/webpack.test.ts | 9 ++++++++- .../plugins/nx-app-rspack-plugin/nx-app-rspack-plugin.ts | 6 ++++++ packages/rspack/src/plugins/utils/apply-base-config.ts | 2 +- packages/rspack/src/plugins/utils/models.ts | 2 ++ .../nx-webpack-plugin/nx-app-webpack-plugin-options.ts | 2 ++ .../plugins/nx-webpack-plugin/nx-app-webpack-plugin.ts | 5 +++++ 8 files changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/shared/packages/webpack/webpack-plugins.md b/docs/shared/packages/webpack/webpack-plugins.md index 40fb33bc59..ae9c012eb4 100644 --- a/docs/shared/packages/webpack/webpack-plugins.md +++ b/docs/shared/packages/webpack/webpack-plugins.md @@ -95,6 +95,10 @@ Type: `boolean` Delete the output path before building. +**`Deprecated`** + +Use [`output.clean`](https://webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder) instead. + ##### deployUrl Type: `string` diff --git a/e2e/rspack/tests/rspack.spec.ts b/e2e/rspack/tests/rspack.spec.ts index d97b61b814..cbe7b82f32 100644 --- a/e2e/rspack/tests/rspack.spec.ts +++ b/e2e/rspack/tests/rspack.spec.ts @@ -57,6 +57,8 @@ describe('rspack e2e', () => { module.exports = { output: { path: join(__dirname, '../../dist/${appName}'), + // do not remove dist, so files between builds will remain + clean: false, }, devServer: { port: 4200, @@ -91,6 +93,11 @@ describe('rspack e2e', () => { expect(result).toContain( `Successfully ran target build for project ${appName}` ); + + // Ensure dist is not removed between builds since output.clean === false + createFile(`dist/apps/${appName}/extra.js`); + runCLI(`build ${appName} --skip-nx-cache`); + checkFilesExist(`dist/apps/${appName}/extra.js`); }); it('should support a standard function that returns a config object', () => { diff --git a/e2e/webpack/src/webpack.test.ts b/e2e/webpack/src/webpack.test.ts index 0bb2a15bec..367ee724c9 100644 --- a/e2e/webpack/src/webpack.test.ts +++ b/e2e/webpack/src/webpack.test.ts @@ -153,7 +153,9 @@ describe('Webpack Plugin', () => { module.exports = { target: 'node', output: { - path: path.join(__dirname, '../../dist/apps/${appName}') + path: path.join(__dirname, '../../dist/apps/${appName}'), + // do not remove dist, so files between builds will remain + clean: false, }, plugins: [ new NxAppWebpackPlugin({ @@ -179,6 +181,11 @@ describe('Webpack Plugin', () => { expect(runCommand(`node dist/apps/${appName}/main.js`)).toMatch(/Hello/); expect(runCommand(`node dist/apps/${appName}/foo.js`)).toMatch(/Foo/); expect(runCommand(`node dist/apps/${appName}/bar.js`)).toMatch(/Bar/); + + // Ensure dist is not removed between builds since output.clean === false + createFile(`dist/apps/${appName}/extra.js`); + runCLI(`build ${appName} --skip-nx-cache`); + checkFilesExist(`dist/apps/${appName}/extra.js`); }, 500_000); it('should bundle in NX_PUBLIC_ environment variables', () => { diff --git a/packages/rspack/src/plugins/nx-app-rspack-plugin/nx-app-rspack-plugin.ts b/packages/rspack/src/plugins/nx-app-rspack-plugin/nx-app-rspack-plugin.ts index a20f114cb9..9a0c04f590 100644 --- a/packages/rspack/src/plugins/nx-app-rspack-plugin/nx-app-rspack-plugin.ts +++ b/packages/rspack/src/plugins/nx-app-rspack-plugin/nx-app-rspack-plugin.ts @@ -42,6 +42,12 @@ export class NxAppRspackPlugin { ) { compiler.options.entry = {}; } + + // Prefer `clean` option from Rspack config over our own. + if (typeof compiler.options.output.clean !== 'undefined') { + this.options.deleteOutputPath = false; + } + applyBaseConfig(this.options, compiler.options, { useNormalizedEntry: true, }); diff --git a/packages/rspack/src/plugins/utils/apply-base-config.ts b/packages/rspack/src/plugins/utils/apply-base-config.ts index c5f86fa2db..d37ab15094 100644 --- a/packages/rspack/src/plugins/utils/apply-base-config.ts +++ b/packages/rspack/src/plugins/utils/apply-base-config.ts @@ -121,7 +121,7 @@ function applyNxIndependentConfig( hashFunction: config.output?.hashFunction ?? 'xxhash64', // Disabled for performance pathinfo: config.output?.pathinfo ?? false, - clean: options.deleteOutputPath, + clean: config.output?.clean ?? options.deleteOutputPath, }; config.watch = options.watch; diff --git a/packages/rspack/src/plugins/utils/models.ts b/packages/rspack/src/plugins/utils/models.ts index e61967cd08..1d7317c049 100644 --- a/packages/rspack/src/plugins/utils/models.ts +++ b/packages/rspack/src/plugins/utils/models.ts @@ -74,7 +74,9 @@ export interface NxAppRspackPluginOptions { /** * Delete the output path before building. + * @deprecated Use the `output.clean` option in Rspack. https://rspack.dev/config/output#outputclean */ + // TODO(v22): Add migration to remove this option and remove it. deleteOutputPath?: boolean; /** * The deploy path for the application. e.g. `/my-app/` diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin-options.ts b/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin-options.ts index dd3018fb3d..a5d8147a5a 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin-options.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin-options.ts @@ -82,7 +82,9 @@ export interface NxAppWebpackPluginOptions { crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; /** * Delete the output path before building. + * @deprecated Use the `output.clean` option in Webpack. https://webpack.js.org/guides/output-management/#cleaning-up-the-dist-folder */ + // TODO(v22): Add migration to remove this option and remove it. deleteOutputPath?: boolean; /** * The deploy path for the application. e.g. `/my-app/` diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.ts b/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.ts index 31c0937f72..beafc52b28 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.ts @@ -36,6 +36,11 @@ export class NxAppWebpackPlugin { this.options.target = target; } + // Prefer `clean` option from Webpack config over our own. + if (typeof compiler.options.output?.clean !== 'undefined') { + this.options.deleteOutputPath = false; + } + applyBaseConfig(this.options, compiler.options, { useNormalizedEntry: true, });