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)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Jack Hsu 2025-04-01 21:16:05 -04:00 committed by GitHub
parent 75521bb64d
commit 2d210b8d0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 35 additions and 2 deletions

View File

@ -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`

View File

@ -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', () => {

View File

@ -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', () => {

View File

@ -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,
});

View File

@ -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;

View File

@ -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/`

View File

@ -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/`

View File

@ -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,
});