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 {
|
import {
|
||||||
checkFilesExist,
|
checkFilesExist,
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
|
createFile,
|
||||||
fileExists,
|
fileExists,
|
||||||
listFiles,
|
listFiles,
|
||||||
newProject,
|
newProject,
|
||||||
@ -406,6 +407,202 @@ describe('Webpack Plugin', () => {
|
|||||||
`Successfully ran target build for project ${appName}`
|
`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 {
|
function readMainFile(dir: string): string {
|
||||||
|
|||||||
@ -149,10 +149,12 @@ async function createRspackTargets(
|
|||||||
|
|
||||||
const rspackOptions = await readRspackOptions(rspackConfig);
|
const rspackOptions = await readRspackOptions(rspackConfig);
|
||||||
|
|
||||||
const outputPath = normalizeOutputPath(
|
const outputs = [];
|
||||||
rspackOptions.output?.path,
|
for (const config of rspackOptions) {
|
||||||
projectRoot
|
if (config.output?.path) {
|
||||||
);
|
outputs.push(normalizeOutputPath(config.output.path, projectRoot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const targets = {};
|
const targets = {};
|
||||||
|
|
||||||
@ -177,7 +179,7 @@ async function createRspackTargets(
|
|||||||
externalDependencies: ['@rspack/cli'],
|
externalDependencies: ['@rspack/cli'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outputs: [outputPath],
|
outputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
targets[options.serveTargetName] = {
|
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:
|
* Reads the Rspack options from a give Rspack configuration. The configuration can be:
|
||||||
* 1. A standard config object
|
* 1. A single standard config object
|
||||||
* 2. A standard function that returns a config object
|
* 2. A standard function that returns a config object (standard Rspack)
|
||||||
* 3. A Nx-specific composable function that takes Nx context, rspack 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, rspack config, and returns the config object.
|
||||||
*
|
*
|
||||||
* @param rspackConfig
|
* @param rspackConfig
|
||||||
*/
|
*/
|
||||||
export async function readRspackOptions(
|
export async function readRspackOptions(
|
||||||
rspackConfig: unknown
|
rspackConfig: unknown
|
||||||
): Promise<Configuration> {
|
): Promise<Configuration[]> {
|
||||||
let config: Configuration;
|
const configs: Configuration[] = [];
|
||||||
if (isNxRspackComposablePlugin(rspackConfig)) {
|
|
||||||
config = await rspackConfig(
|
const resolveConfig = async (
|
||||||
{},
|
config: unknown
|
||||||
{
|
): Promise<Configuration | Configuration[]> => {
|
||||||
// These values are only used during build-time, so passing stubs here just to read out
|
let resolvedConfig: Configuration;
|
||||||
// the returned config object.
|
if (isNxRspackComposablePlugin(config)) {
|
||||||
options: {
|
resolvedConfig = await config(
|
||||||
root: workspaceRoot,
|
{},
|
||||||
projectRoot: '',
|
{
|
||||||
sourceRoot: '',
|
// These values are only used during build-time, so passing stubs here just to read out
|
||||||
outputFileName: '',
|
// the returned config object.
|
||||||
assets: [],
|
options: {
|
||||||
main: '',
|
root: workspaceRoot,
|
||||||
tsConfig: '',
|
projectRoot: '',
|
||||||
outputPath: '',
|
sourceRoot: '',
|
||||||
rspackConfig: '',
|
outputFileName: '',
|
||||||
useTsconfigPaths: undefined,
|
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,
|
// If the resolved configuration is an array, resolve each configuration
|
||||||
isVerbose: false,
|
return Array.isArray(resolved)
|
||||||
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
|
? await Promise.all(resolved.map(resolveConfig))
|
||||||
projectGraph: null,
|
: resolved;
|
||||||
projectsConfigurations: null,
|
} 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 if (typeof rspackConfig === 'function') {
|
} else {
|
||||||
config = await rspackConfig(
|
return config as Configuration;
|
||||||
{
|
}
|
||||||
production: true, // we want the production build options
|
};
|
||||||
},
|
|
||||||
{}
|
// 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 {
|
} 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 webpackOptions = await readWebpackOptions(webpackConfig);
|
||||||
|
|
||||||
const outputPath = normalizeOutputPath(
|
const outputs = [];
|
||||||
webpackOptions.output?.path,
|
for (const config of webpackOptions) {
|
||||||
projectRoot
|
if (config.output?.path) {
|
||||||
);
|
outputs.push(normalizeOutputPath(config.output.path, projectRoot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const targets: Record<string, TargetConfiguration> = {};
|
const targets: Record<string, TargetConfiguration> = {};
|
||||||
|
|
||||||
@ -198,7 +200,7 @@ async function createWebpackTargets(
|
|||||||
externalDependencies: ['webpack-cli'],
|
externalDependencies: ['webpack-cli'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outputs: [outputPath],
|
outputs,
|
||||||
metadata: {
|
metadata: {
|
||||||
technologies: ['webpack'],
|
technologies: ['webpack'],
|
||||||
description: 'Runs Webpack build',
|
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:
|
* Reads the webpack options from a give webpack configuration. The configuration can be:
|
||||||
* 1. A standard config object
|
* 1. A standard config object
|
||||||
* 2. A standard function that returns a config object (webpack.js.org/configuration/configuration-types/#exporting-a-function)
|
* 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
|
* @param webpackConfig
|
||||||
*/
|
*/
|
||||||
export async function readWebpackOptions(
|
export async function readWebpackOptions(
|
||||||
webpackConfig: unknown
|
webpackConfig: unknown
|
||||||
): Promise<Configuration> {
|
): Promise<Configuration[]> {
|
||||||
let config: Configuration;
|
const configs: Configuration[] = [];
|
||||||
if (isNxWebpackComposablePlugin(webpackConfig)) {
|
|
||||||
config = await webpackConfig(
|
const resolveConfig = async (
|
||||||
{},
|
config: unknown
|
||||||
{
|
): Promise<Configuration | Configuration[]> => {
|
||||||
// These values are only used during build-time, so passing stubs here just to read out
|
if (isNxWebpackComposablePlugin(config)) {
|
||||||
// the returned config object.
|
return await config(
|
||||||
options: {
|
{},
|
||||||
root: workspaceRoot,
|
{
|
||||||
projectRoot: '',
|
// These values are only used during build-time, so passing stubs here just to read out
|
||||||
sourceRoot: '',
|
options: {
|
||||||
outputFileName: undefined,
|
root: workspaceRoot,
|
||||||
outputPath: undefined,
|
projectRoot: '',
|
||||||
assets: undefined,
|
sourceRoot: '',
|
||||||
useTsconfigPaths: undefined,
|
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,
|
// If the resolved configuration is an array, resolve each configuration
|
||||||
projectsConfigurations: null,
|
return Array.isArray(resolved)
|
||||||
projectGraph: null,
|
? await Promise.all(resolved.map(resolveConfig))
|
||||||
nxJsonConfiguration: readNxJsonFromDisk(workspaceRoot),
|
: resolved;
|
||||||
},
|
} else if (Array.isArray(config)) {
|
||||||
}
|
// If the config passed is an array, resolve each configuration
|
||||||
);
|
const resolvedConfigs = await Promise.all(config.map(resolveConfig));
|
||||||
} else if (typeof webpackConfig === 'function') {
|
return resolvedConfigs.flat();
|
||||||
config = await webpackConfig(
|
} else {
|
||||||
{
|
// Return plain configuration
|
||||||
production: true, // we want the production build options
|
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 {
|
} 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