fix(angular): convert-to-rspack correctly migrating existing custom webpack configs (#30778)

## Current Behavior
The `createConfig` helper from `@nx/angular-rspack` became an async
function.
This meant that the handling of custom webpack configs in the migration
done by `convert-to-rspack` was incorrect.

## Expected Behavior
Ensure the migration is handled correctly.
Ensure that Module Federation migrations work correctly.

## Related Issues
Fixes https://github.com/nrwl/angular-rspack/issues/53
This commit is contained in:
Colum Ferry 2025-04-22 15:54:51 +01:00 committed by GitHub
parent 3f2a40ffec
commit 745abdaecf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 115 additions and 93 deletions

View File

@ -36,7 +36,7 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack and
} }
}, { }, {
production: { "production": {
options: { options: {
"budgets": [ "budgets": [
@ -57,7 +57,7 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack and
} }
}, },
development: { "development": {
options: { options: {
"optimization": false, "optimization": false,
@ -68,9 +68,7 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack and
"devServer": {} "devServer": {}
} }
}}); }});"
"
`; `;
exports[`app --minimal should generate a correct setup when --bundler=rspack and ssr 2`] = ` exports[`app --minimal should generate a correct setup when --bundler=rspack and ssr 2`] = `
@ -181,7 +179,7 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack inc
} }
}, { }, {
production: { "production": {
options: { options: {
"budgets": [ "budgets": [
@ -202,7 +200,7 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack inc
} }
}, },
development: { "development": {
options: { options: {
"optimization": false, "optimization": false,
@ -213,9 +211,7 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack inc
"devServer": {} "devServer": {}
} }
}}); }});"
"
`; `;
exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing 1`] = ` exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing 1`] = `

View File

@ -429,7 +429,8 @@ describe('convert-to-rspack', () => {
import baseWebpackConfig from './webpack.config'; import baseWebpackConfig from './webpack.config';
import webpackMerge from 'webpack-merge'; import webpackMerge from 'webpack-merge';
const baseConfig = createConfig({ export default async () => {
const baseConfig = await createConfig({
options: { options: {
root: __dirname, root: __dirname,
@ -452,13 +453,16 @@ describe('convert-to-rspack', () => {
scripts: [], scripts: [],
}, },
}); });
return webpackMerge(baseConfig[0], baseWebpackConfig);
export default webpackMerge(baseConfig[0], baseWebpackConfig); };
" "
`); `);
expect(tree.read('apps/app/webpack.config.js', 'utf-8')) expect(tree.read('apps/app/webpack.config.js', 'utf-8'))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { NxModuleFederationPlugin } = require('@nx/module-federation/rspack'); "const {
NxModuleFederationPlugin,
NxModuleFederationDevServerPlugin,
} = require('@nx/module-federation/rspack');
const config = require('./module-federation.config'); const config = require('./module-federation.config');
module.exports = { module.exports = {
@ -469,6 +473,7 @@ describe('convert-to-rspack', () => {
dts: false, dts: false,
} }
), ),
new NxModuleFederationDevServerPlugin({ config }),
], ],
}; };
" "
@ -549,7 +554,8 @@ describe('convert-to-rspack', () => {
import baseWebpackConfig from './webpack.config'; import baseWebpackConfig from './webpack.config';
import webpackMerge from 'webpack-merge'; import webpackMerge from 'webpack-merge';
const baseConfig = createConfig({ export default async () => {
const baseConfig = await createConfig({
options: { options: {
root: __dirname, root: __dirname,
@ -572,8 +578,8 @@ describe('convert-to-rspack', () => {
scripts: [], scripts: [],
}, },
}); });
return webpackMerge(baseConfig[0], baseWebpackConfig);
export default webpackMerge(baseConfig[0], baseWebpackConfig); };
" "
`); `);
}); });

View File

@ -43,7 +43,14 @@ const RENAMED_OPTIONS = {
ngswConfigPath: 'serviceWorker', ngswConfigPath: 'serviceWorker',
}; };
const REMOVED_OPTIONS = ['buildOptimizer', 'buildTarget', 'browserTarget']; const DEFAULT_PORT = 4200;
const REMOVED_OPTIONS = [
'buildOptimizer',
'buildTarget',
'browserTarget',
'publicHost',
];
function normalizeFromProjectRoot( function normalizeFromProjectRoot(
tree: Tree, tree: Tree,
@ -345,6 +352,8 @@ export async function convertToRspack(
validateSupportedBuildExecutor(Object.values(project.targets)); validateSupportedBuildExecutor(Object.values(project.targets));
let projectServePort = DEFAULT_PORT;
for (const [targetName, target] of Object.entries(project.targets)) { for (const [targetName, target] of Object.entries(project.targets)) {
if ( if (
target.executor === '@angular-devkit/build-angular:browser' || target.executor === '@angular-devkit/build-angular:browser' ||
@ -395,6 +404,10 @@ export async function convertToRspack(
createConfigOptions.devServer, createConfigOptions.devServer,
project.root project.root
); );
if (target.options.port !== DEFAULT_PORT) {
projectServePort = target.options.port;
}
} }
if (target.configurations) { if (target.configurations) {
for (const [configurationName, configuration] of Object.entries( for (const [configurationName, configuration] of Object.entries(
@ -431,6 +444,12 @@ export async function convertToRspack(
delete project.targets[targetName]; delete project.targets[targetName];
} }
if (projectServePort !== DEFAULT_PORT) {
project.targets.serve ??= {};
project.targets.serve.options ??= {};
project.targets.serve.options.port = projectServePort;
}
updateProjectConfiguration(tree, projectName, project); updateProjectConfiguration(tree, projectName, project);
const { rspackInitGenerator } = ensurePackage<typeof import('@nx/rspack')>( const { rspackInitGenerator } = ensurePackage<typeof import('@nx/rspack')>(

View File

@ -49,9 +49,7 @@ describe('createConfig', () => {
"skipTypeChecking": false "skipTypeChecking": false
} }
}); });"
"
`); `);
}); });
@ -83,16 +81,14 @@ describe('createConfig', () => {
} }
}, { }, {
production: { "production": {
options: { options: {
"index": "src/index.prod.html", "index": "src/index.prod.html",
"browser": "src/main.prod.ts" "browser": "src/main.prod.ts"
} }
}}); }});"
"
`); `);
}); });
}); });

View File

@ -13,7 +13,7 @@ export function createConfig(
? Object.entries(configurationOptions) ? Object.entries(configurationOptions)
.map(([configurationName, configurationOptions]) => { .map(([configurationName, configurationOptions]) => {
return ` return `
${configurationName}: { "${configurationName}": {
options: { options: {
${JSON.stringify(configurationOptions, undefined, 2).slice(1, -1)} ${JSON.stringify(configurationOptions, undefined, 2).slice(1, -1)}
} }
@ -21,6 +21,14 @@ export function createConfig(
}) })
.join(',\n') .join(',\n')
: ''; : '';
const createConfigContents = `createConfig({
options: {
root: __dirname,
${JSON.stringify(createConfigOptions, undefined, 2).slice(1, -1)}
}
}${hasConfigurations ? `, {${expandedConfigurationOptions}}` : ''});`;
const configContents = ` const configContents = `
import { createConfig }from '@nx/angular-rspack'; import { createConfig }from '@nx/angular-rspack';
${ ${
@ -34,29 +42,18 @@ export function createConfig(
: '' : ''
} }
${
existingWebpackConfigPath ? 'const baseConfig = ' : 'export default '
}createConfig({
options: {
root: __dirname,
${JSON.stringify(createConfigOptions, undefined, 2).slice(1, -1)}
}
}${hasConfigurations ? `, {${expandedConfigurationOptions}}` : ''});
${ ${
existingWebpackConfigPath existingWebpackConfigPath
? ` ? `export default async () => {
export default ${ const baseConfig = await ${createConfigContents}
${
isExistingWebpackConfigFunction isExistingWebpackConfigFunction
? `async function (env, argv) { ? `const oldConfig = await baseWebpackConfig;
const oldConfig = await baseWebpackConfig;
const browserConfig = baseConfig[0]; const browserConfig = baseConfig[0];
return oldConfig(browserConfig); return oldConfig(browserConfig);`
}` : 'return webpackMerge(baseConfig[0], baseWebpackConfig);'
: 'webpackMerge(baseConfig[0], baseWebpackConfig);' }};`
} : `export default ${createConfigContents}`
` }`;
: ''
}
`;
tree.write(joinPathFragments(root, 'rspack.config.ts'), configContents); tree.write(joinPathFragments(root, 'rspack.config.ts'), configContents);
} }

View File

@ -16,15 +16,16 @@ describe('convertconvertWebpackConfigToUseNxModuleFederationPlugin', () => {
// ASSERT // ASSERT
expect(newWebpackConfigContents).toMatchInlineSnapshot(` expect(newWebpackConfigContents).toMatchInlineSnapshot(`
" "
import { NxModuleFederationPlugin } from '@nx/module-federation/rspack'; import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
import config from './module-federation.config'; import config from './module-federation.config';
export default { export default {
plugins: [ plugins: [
new NxModuleFederationPlugin(config, { new NxModuleFederationPlugin({ config }, {
dts: false, dts: false,
}), }),
new NxModuleFederationDevServerPlugin({ config }),
] ]
} }
" "
@ -46,7 +47,7 @@ describe('convertconvertWebpackConfigToUseNxModuleFederationPlugin', () => {
// ASSERT // ASSERT
expect(newWebpackConfigContents).toMatchInlineSnapshot(` expect(newWebpackConfigContents).toMatchInlineSnapshot(`
" "
const { NxModuleFederationPlugin } = require('@nx/module-federation/rspack'); const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
const config = require('./module-federation.config'); const config = require('./module-federation.config');
@ -55,6 +56,7 @@ describe('convertconvertWebpackConfigToUseNxModuleFederationPlugin', () => {
new NxModuleFederationPlugin({ config }, { new NxModuleFederationPlugin({ config }, {
dts: false, dts: false,
}), }),
new NxModuleFederationDevServerPlugin({ config }),
] ]
} }
" "

View File

@ -56,7 +56,7 @@ export function convertWebpackConfigToUseNxModuleFederationPlugin(
newWebpackConfigContents = `${webpackConfigContents.slice( newWebpackConfigContents = `${webpackConfigContents.slice(
0, 0,
withModuleFederationImportNode.getStart() withModuleFederationImportNode.getStart()
)}import { NxModuleFederationPlugin } from '@nx/module-federation/rspack';${webpackConfigContents.slice( )}import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';${webpackConfigContents.slice(
withModuleFederationImportNode.getEnd() withModuleFederationImportNode.getEnd()
)}`; )}`;
@ -76,9 +76,10 @@ export function convertWebpackConfigToUseNxModuleFederationPlugin(
)} )}
export default { export default {
plugins: [ plugins: [
new NxModuleFederationPlugin(config, { new NxModuleFederationPlugin({ config }, {
dts: false, dts: false,
}), }),
new NxModuleFederationDevServerPlugin({ config }),
] ]
} }
`; `;
@ -98,7 +99,7 @@ export function convertWebpackConfigToUseNxModuleFederationPlugin(
newWebpackConfigContents = `${webpackConfigContents.slice( newWebpackConfigContents = `${webpackConfigContents.slice(
0, 0,
withModuleFederationRequireNode.getStart() withModuleFederationRequireNode.getStart()
)}const { NxModuleFederationPlugin } = require('@nx/module-federation/rspack');${webpackConfigContents.slice( )}const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');${webpackConfigContents.slice(
withModuleFederationRequireNode.getEnd() withModuleFederationRequireNode.getEnd()
)}`; )}`;
@ -121,6 +122,7 @@ export function convertWebpackConfigToUseNxModuleFederationPlugin(
new NxModuleFederationPlugin({ config }, { new NxModuleFederationPlugin({ config }, {
dts: false, dts: false,
}), }),
new NxModuleFederationDevServerPlugin({ config }),
] ]
} }
`; `;

View File

@ -23,6 +23,10 @@ export class NxModuleFederationPlugin implements RspackPluginInstance {
compiler.options.optimization ??= {}; compiler.options.optimization ??= {};
compiler.options.optimization.runtimeChunk = false; compiler.options.optimization.runtimeChunk = false;
compiler.options.output.uniqueName = this._options.config.name; compiler.options.output.uniqueName = this._options.config.name;
if (compiler.options.output.scriptType === 'module') {
compiler.options.output.scriptType = undefined;
compiler.options.output.module = undefined;
}
if (this._options.isServer) { if (this._options.isServer) {
compiler.options.target = 'async-node'; compiler.options.target = 'async-node';
compiler.options.output.library ??= { compiler.options.output.library ??= {