feat(webpack, rspack): support multiple configurations (#29691)
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 }
]
This commit is contained in:
parent
123602c0d6
commit
7524356180
@ -1,6 +1,7 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
createFile,
|
||||
fileExists,
|
||||
listFiles,
|
||||
newProject,
|
||||
@ -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 {
|
||||
|
||||
@ -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 = {};
|
||||
|
||||
@ -177,7 +179,7 @@ async function createRspackTargets(
|
||||
externalDependencies: ['@rspack/cli'],
|
||||
},
|
||||
],
|
||||
outputs: [outputPath],
|
||||
outputs,
|
||||
};
|
||||
|
||||
targets[options.serveTargetName] = {
|
||||
|
||||
@ -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,
|
||||
): 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
|
||||
},
|
||||
context: {
|
||||
root: workspaceRoot,
|
||||
cwd: undefined,
|
||||
isVerbose: false,
|
||||
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
|
||||
projectGraph: null,
|
||||
projectsConfigurations: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -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> = {};
|
||||
|
||||
@ -198,7 +200,7 @@ async function createWebpackTargets(
|
||||
externalDependencies: ['webpack-cli'],
|
||||
},
|
||||
],
|
||||
outputs: [outputPath],
|
||||
outputs,
|
||||
metadata: {
|
||||
technologies: ['webpack'],
|
||||
description: 'Runs Webpack build',
|
||||
|
||||
@ -7,48 +7,83 @@ import { readNxJsonFromDisk } from 'nx/src/devkit-internals';
|
||||
* Reads the webpack options from a give webpack configuration. The configuration can be:
|
||||
* 1. A standard config object
|
||||
* 2. A standard function that returns a config object (webpack.js.org/configuration/configuration-types/#exporting-a-function)
|
||||
* 3. A Nx-specific composable function that takes Nx context, webpack config, and returns the config object.
|
||||
* 3. An array of standard config objects (multi-configuration mode)
|
||||
* 4. A Nx-specific composable function that takes Nx context, webpack config, and returns the config object.
|
||||
*
|
||||
* @param webpackConfig
|
||||
*/
|
||||
export async function readWebpackOptions(
|
||||
webpackConfig: unknown
|
||||
): Promise<Configuration> {
|
||||
let config: Configuration;
|
||||
if (isNxWebpackComposablePlugin(webpackConfig)) {
|
||||
config = await webpackConfig(
|
||||
{},
|
||||
{
|
||||
// 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: undefined,
|
||||
outputPath: undefined,
|
||||
assets: undefined,
|
||||
useTsconfigPaths: undefined,
|
||||
): Promise<Configuration[]> {
|
||||
const configs: Configuration[] = [];
|
||||
|
||||
const resolveConfig = async (
|
||||
config: unknown
|
||||
): Promise<Configuration | Configuration[]> => {
|
||||
if (isNxWebpackComposablePlugin(config)) {
|
||||
return await config(
|
||||
{},
|
||||
{
|
||||
// These values are only used during build-time, so passing stubs here just to read out
|
||||
options: {
|
||||
root: workspaceRoot,
|
||||
projectRoot: '',
|
||||
sourceRoot: '',
|
||||
outputFileName: undefined,
|
||||
outputPath: undefined,
|
||||
assets: undefined,
|
||||
useTsconfigPaths: undefined,
|
||||
},
|
||||
context: {
|
||||
root: workspaceRoot,
|
||||
cwd: undefined,
|
||||
isVerbose: false,
|
||||
projectsConfigurations: null,
|
||||
projectGraph: null,
|
||||
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
|
||||
},
|
||||
}
|
||||
);
|
||||
} else if (typeof config === 'function') {
|
||||
const resolved = await config(
|
||||
{
|
||||
production: true, // we want the production build options
|
||||
},
|
||||
context: {
|
||||
root: workspaceRoot,
|
||||
cwd: undefined,
|
||||
isVerbose: false,
|
||||
projectsConfigurations: null,
|
||||
projectGraph: null,
|
||||
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
|
||||
},
|
||||
}
|
||||
);
|
||||
} else if (typeof webpackConfig === 'function') {
|
||||
config = await webpackConfig(
|
||||
{
|
||||
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 plain configuration
|
||||
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(webpackConfig)) {
|
||||
for (const config of webpackConfig) {
|
||||
const resolved = await resolveConfig(config);
|
||||
configs.push(...flattenConfigs(resolved));
|
||||
}
|
||||
} else {
|
||||
config = webpackConfig;
|
||||
const resolved = await resolveConfig(webpackConfig);
|
||||
configs.push(...flattenConfigs(resolved));
|
||||
}
|
||||
return config;
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user