Skip to content

Commit

Permalink
feat(webpack, rspack): support multiple configurations (#29691)
Browse files Browse the repository at this point in the history
This pull request includes changes to support multi-configuration mode
for both Rspack and Webpack.

## Currently
Currently our plugin only supports single configurations
```js
module.exports =  { 
  ...config
}
```
Which works in most cases but some applications can have mutliple
configs that serve different platforms.

## Changes
With these changes, the Webpack and Rspack plugins will also support
multi-configuration.
```js
module.exports = [ 
   { ...clientConfig },
   { ...serverConfig }
 ]
  • Loading branch information
ndcunningham authored Jan 23, 2025
1 parent 123602c commit 7524356
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 87 deletions.
197 changes: 197 additions & 0 deletions e2e/webpack/src/webpack.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
checkFilesExist,
cleanupProject,
createFile,
fileExists,
listFiles,
newProject,
Expand Down Expand Up @@ -406,6 +407,202 @@ describe('Webpack Plugin', () => {
`Successfully ran target build for project ${appName}`
);
});

describe('config types', () => {
it('should support a standard config object', () => {
const appName = uniq('app');

runCLI(
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
);

updateFile(
`apps/${appName}/webpack.config.js`,
`
const path = require('path');
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
module.exports = {
target: 'node',
output: {
path: path.join(__dirname, '../../dist/${appName}')
},
plugins: [
new NxAppWebpackPlugin({
compiler: 'babel',
main: './src/main.tsx',
tsConfig: './tsconfig.app.json',
outputHashing: 'none',
optimization: false,
})
]
};`
);

const result = runCLI(`build ${appName}`);

expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
});

it('should support a standard function that returns a config object', () => {
const appName = uniq('app');

runCLI(
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
);

updateFile(
`apps/${appName}/webpack.config.js`,
`
const path = require('path');
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
module.exports = () => {
return {
target: 'node',
output: {
path: path.join(__dirname, '../../dist/${appName}')
},
plugins: [
new NxAppWebpackPlugin({
compiler: 'tsc',
main: './src/main.tsx',
tsConfig: './tsconfig.app.json',
outputHashing: 'none',
optimization: false,
})
]
};
};`
);

const result = runCLI(`build ${appName}`);
expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
});

it('should support an array of standard config objects', () => {
const appName = uniq('app');
const serverName = uniq('server');

runCLI(
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
);

// Create server index file
createFile(
`apps/${serverName}/index.js`,
`console.log('Hello from ${serverName}');\n`
);

updateFile(
`apps/${appName}/webpack.config.js`,
`
const path = require('path');
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
module.exports = [
{
name: 'client',
target: 'node',
output: {
path: path.join(__dirname, '../../dist/${appName}')
},
plugins: [
new NxAppWebpackPlugin({
compiler: 'tsc',
main: './src/main.tsx',
tsConfig: './tsconfig.app.json',
outputHashing: 'none',
optimization: false,
})
]
}, {
name: 'server',
target: 'node',
entry: '../${serverName}/index.js',
output: {
path: path.join(__dirname, '../../dist/${serverName}'),
filename: 'index.js',
},
}
];
`
);

const result = runCLI(`build ${appName}`);

checkFilesExist(`dist/${appName}/main.js`);
checkFilesExist(`dist/${serverName}/index.js`);

expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
});

it('should support a function that returns an array of standard config objects', () => {
const appName = uniq('app');
const serverName = uniq('server');

runCLI(
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
);

// Create server index file
createFile(
`apps/${serverName}/index.js`,
`console.log('Hello from ${serverName}');\n`
);

updateFile(
`apps/${appName}/webpack.config.js`,
`
const path = require('path');
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
module.exports = () => {
return [
{
name: 'client',
target: 'node',
output: {
path: path.join(__dirname, '../../dist/${appName}')
},
plugins: [
new NxAppWebpackPlugin({
compiler: 'tsc',
main: './src/main.tsx',
tsConfig: './tsconfig.app.json',
outputHashing: 'none',
optimization: false,
})
]
},
{
name: 'server',
target: 'node',
entry: '../${serverName}/index.js',
output: {
path: path.join(__dirname, '../../dist/${serverName}'),
filename: 'index.js',
}
}
];
};`
);
const result = runCLI(`build ${appName}`);

checkFilesExist(`dist/${serverName}/index.js`);
checkFilesExist(`dist/${appName}/main.js`);

expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
});
});
});

function readMainFile(dir: string): string {
Expand Down
12 changes: 7 additions & 5 deletions packages/rspack/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,12 @@ async function createRspackTargets(

const rspackOptions = await readRspackOptions(rspackConfig);

const outputPath = normalizeOutputPath(
rspackOptions.output?.path,
projectRoot
);
const outputs = [];
for (const config of rspackOptions) {
if (config.output?.path) {
outputs.push(normalizeOutputPath(config.output.path, projectRoot));
}
}

const targets = {};

Expand All @@ -177,7 +179,7 @@ async function createRspackTargets(
externalDependencies: ['@rspack/cli'],
},
],
outputs: [outputPath],
outputs,
};

targets[options.serveTargetName] = {
Expand Down
117 changes: 76 additions & 41 deletions packages/rspack/src/utils/read-rspack-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,88 @@ import { readNxJsonFromDisk } from 'nx/src/devkit-internals';

/**
* Reads the Rspack options from a give Rspack configuration. The configuration can be:
* 1. A standard config object
* 2. A standard function that returns a config object
* 3. A Nx-specific composable function that takes Nx context, rspack config, and returns the config object.
* 1. A single standard config object
* 2. A standard function that returns a config object (standard Rspack)
* 3. An array of standard config objects (multi-configuration mode)
* 4. A Nx-specific composable function that takes Nx context, rspack config, and returns the config object.
*
* @param rspackConfig
*/
export async function readRspackOptions(
rspackConfig: unknown
): Promise<Configuration> {
let config: Configuration;
if (isNxRspackComposablePlugin(rspackConfig)) {
config = await rspackConfig(
{},
{
// These values are only used during build-time, so passing stubs here just to read out
// the returned config object.
options: {
root: workspaceRoot,
projectRoot: '',
sourceRoot: '',
outputFileName: '',
assets: [],
main: '',
tsConfig: '',
outputPath: '',
rspackConfig: '',
useTsconfigPaths: undefined,
},
context: {
root: workspaceRoot,
cwd: undefined,
isVerbose: false,
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
projectGraph: null,
projectsConfigurations: null,
): Promise<Configuration[]> {
const configs: Configuration[] = [];

const resolveConfig = async (
config: unknown
): Promise<Configuration | Configuration[]> => {
let resolvedConfig: Configuration;
if (isNxRspackComposablePlugin(config)) {
resolvedConfig = await config(
{},
{
// These values are only used during build-time, so passing stubs here just to read out
// the returned config object.
options: {
root: workspaceRoot,
projectRoot: '',
sourceRoot: '',
outputFileName: '',
assets: [],
main: '',
tsConfig: '',
outputPath: '',
rspackConfig: '',
useTsconfigPaths: undefined,
},
context: {
root: workspaceRoot,
cwd: undefined,
isVerbose: false,
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
projectGraph: null,
projectsConfigurations: null,
},
}
);
} else if (typeof config === 'function') {
const resolved = await config(
{
production: true, // we want the production build options
},
}
);
} else if (typeof rspackConfig === 'function') {
config = await rspackConfig(
{
production: true, // we want the production build options
},
{}
);
{}
);
// If the resolved configuration is an array, resolve each configuration
return Array.isArray(resolved)
? await Promise.all(resolved.map(resolveConfig))
: resolved;
} else if (Array.isArray(config)) {
// If the config passed is an array, resolve each configuration
const resolvedConfigs = await Promise.all(config.map(resolveConfig));
return resolvedConfigs.flat();
} else {
return config as Configuration;
}
};

// Since configs can have nested arrays, we need to flatten them
const flattenConfigs = (
resolvedConfigs: Configuration | Configuration[]
): Configuration[] => {
return Array.isArray(resolvedConfigs)
? resolvedConfigs.flatMap((cfg) => flattenConfigs(cfg))
: [resolvedConfigs];
};

if (Array.isArray(rspackConfig)) {
for (const config of rspackConfig) {
const resolved = await resolveConfig(config);
configs.push(...flattenConfigs(resolved));
}
} else {
config = rspackConfig;
const resolved = await resolveConfig(rspackConfig);
configs.push(...flattenConfigs(resolved));
}
return config;

return configs;
}
12 changes: 7 additions & 5 deletions packages/webpack/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,12 @@ async function createWebpackTargets(

const webpackOptions = await readWebpackOptions(webpackConfig);

const outputPath = normalizeOutputPath(
webpackOptions.output?.path,
projectRoot
);
const outputs = [];
for (const config of webpackOptions) {
if (config.output?.path) {
outputs.push(normalizeOutputPath(config.output.path, projectRoot));
}
}

const targets: Record<string, TargetConfiguration> = {};

Expand All @@ -198,7 +200,7 @@ async function createWebpackTargets(
externalDependencies: ['webpack-cli'],
},
],
outputs: [outputPath],
outputs,
metadata: {
technologies: ['webpack'],
description: 'Runs Webpack build',
Expand Down
Loading

0 comments on commit 7524356

Please sign in to comment.