fix(rspack): should be inferred by default (#29736)

This PR updates the Rspack Plugin to be inferred by default.

### Currently
When you generate a project using rspack it would not be inferred and
would add the executor to `project.json`.

### After
Generating a project using rspack will add it to inferred plugins inside
`nx.json` and update the `rspack.config.js` to be a standard config and
use Nx plugins.
This commit is contained in:
Nicholas Cunningham 2025-02-12 07:24:39 -07:00 committed by GitHub
parent 7c5fcf3566
commit 4e04979a36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 385 additions and 150 deletions

View File

@ -8,7 +8,6 @@ import {
listFiles, listFiles,
newProject, newProject,
readFile, readFile,
readJson,
runCLI, runCLI,
runCLIAsync, runCLIAsync,
runE2ETests, runE2ETests,

View File

@ -0,0 +1,121 @@
import {
cleanupProject,
newProject,
uniq,
updateFile,
runCLI,
updateJson,
} from '@nx/e2e/utils';
describe('rspack e2e legacy', () => {
let originalEnv;
// Setting up individual workspaces per
// test can cause e2e runs to take a long time.
// For this reason, we recommend each suite only
// consumes 1 workspace. The tests should each operate
// on a unique project in the workspace, such that they
// are not dependant on one another.
beforeAll(() => {
newProject({ packages: ['@nx/rspack', '@nx/react'] });
originalEnv = process.env.NODE_ENV;
});
afterAll(() => {
process.env.NODE_ENV = originalEnv;
cleanupProject();
});
it('should support a standard config object', () => {
const appName = uniq('non-inferred-app');
runCLI(
`generate @nx/react:app --directory=apps/${appName} --bundler=rspack --e2eTestRunner=none`,
{ env: { NX_ADD_PLUGINS: 'false' } }
);
updateJson(`apps/${appName}/project.json`, (json) => {
json.targets.build.options.standardRspackConfigFunction = true;
return json;
});
updateFile(
`apps/${appName}/rspack.config.js`,
`
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../../dist/${appName}'),
},
devServer: {
port: 4200,
historyApiFallback: {
index: '/index.html',
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
},
},
plugins: [
new NxAppRspackPlugin({
tsConfig: './tsconfig.app.json',
main: './src/main.tsx',
index: './src/index.html',
baseHref: '/',
assets: ['./src/favicon.ico', './src/assets'],
styles: ['./src/styles.scss'],
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
optimization: process.env['NODE_ENV'] === 'production',
}),
new NxReactRspackPlugin({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
// svgr: false
}),
],
};`
);
const result = runCLI(`build ${appName}`);
expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
});
it('should support Nx config function that returns a config object', () => {
const appName = uniq('non-inferred-app');
runCLI(
`generate @nx/react:application --directory=apps/${appName} --bundler=rspack --e2eTestRunner=none --style=css --no-interactive`,
{ env: { NX_ADD_PLUGINS: 'false' } }
);
updateFile(
`apps/${appName}/rspack.config.js`,
`
const { composePlugins, withNx, withReact } = require('@nx/rspack');
// Nx plugins for rspack.
module.exports = composePlugins(
withNx(),
withReact({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
// svgr: false
}),
(config) => {
// Update the rspack config as needed here.
return config;
}
);`
);
const result = runCLI(`build ${appName}`);
expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
});
});

View File

@ -1,18 +1,14 @@
import { getPackageManagerCommand } from '@nx/devkit';
import { import {
checkFilesExist, checkFilesExist,
cleanupProject, cleanupProject,
listFiles,
newProject, newProject,
tmpProjPath,
uniq, uniq,
updateFile, updateFile,
runCLI, runCLI,
runCommand, createFile,
readJson,
updateJson,
} from '@nx/e2e/utils'; } from '@nx/e2e/utils';
import { execSync } from 'child_process';
import { writeFileSync } from 'fs';
import { join } from 'path';
describe('rspack e2e', () => { describe('rspack e2e', () => {
let proj: string; let proj: string;
@ -24,126 +20,282 @@ describe('rspack e2e', () => {
// on a unique project in the workspace, such that they // on a unique project in the workspace, such that they
// are not dependant on one another. // are not dependant on one another.
beforeAll(() => { beforeAll(() => {
proj = newProject({ packages: ['@nx/rspack'] }); proj = newProject({ packages: ['@nx/rspack', '@nx/react'] });
}); });
afterAll(() => cleanupProject()); afterAll(() => cleanupProject());
it('should create rspack root project and additional apps', async () => { it('should be inferred (crystal) by default', async () => {
const project = uniq('myapp'); const appName = uniq('app');
runCLI( runCLI(
`generate @nx/rspack:preset ${project} --framework=react --unitTestRunner=jest --e2eTestRunner=cypress --verbose` `generate @nx/react:application --directory=apps/${appName} --bundler=rspack --e2eTestRunner=none`
); );
// Added this so that the nx-ecosystem-ci tests don't throw jest error const nxJSON = readJson('nx.json');
writeFileSync( const rspackPlugin = nxJSON.plugins.find(
join(tmpProjPath(), '.babelrc'), (plugin) => plugin.plugin === '@nx/rspack/plugin'
` );
{
"presets": [ expect(rspackPlugin).toBeDefined();
"@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript", });
[
"@nx/react/babel", describe('config types', () => {
{ it('should support a standard config object', () => {
"runtime": "automatic" const appName = uniq('app');
}
] runCLI(
], `generate @nx/react:application --directory=apps/${appName} --bundler=rspack --e2eTestRunner=none`
"plugins": ["@babel/plugin-transform-runtime"] );
updateFile(
`apps/${appName}/rspack.config.js`,
`
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '../../dist/${appName}'),
},
devServer: {
port: 4200,
historyApiFallback: {
index: '/index.html',
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
},
},
plugins: [
new NxAppRspackPlugin({
tsConfig: './tsconfig.app.json',
main: './src/main.tsx',
index: './src/index.html',
baseHref: '/',
assets: ['./src/favicon.ico', './src/assets'],
styles: ['./src/styles.scss'],
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
optimization: process.env['NODE_ENV'] === 'production',
}),
new NxReactRspackPlugin({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
// svgr: 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=rspack --e2eTestRunner=none`
);
updateFile(
`apps/${appName}/rspack.config.js`,
`
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
const { join } = require('path');
module.exports = () => {
return {
output: {
path: join(__dirname, '../../dist/${appName}'),
},
devServer: {
port: 4200,
historyApiFallback: {
index: '/index.html',
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
},
},
plugins: [
new NxAppRspackPlugin({
tsConfig: './tsconfig.app.json',
main: './src/main.tsx',
index: './src/index.html',
baseHref: '/',
assets: ['./src/favicon.ico', './src/assets'],
styles: ['./src/styles.scss'],
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
optimization: process.env['NODE_ENV'] === 'production',
}),
new NxReactRspackPlugin({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
// svgr: 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=rspack --e2eTestRunner=none`
);
// Create server index file
createFile(
`apps/${serverName}/index.js`,
`console.log('Hello from ${serverName}');\n`
);
updateFile(
`apps/${appName}/rspack.config.js`,
`
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
const { join } = require('path');
module.exports = [
{
name: 'client',
output: {
path: join(__dirname, '../../dist/${appName}'),
},
devServer: {
port: 4200,
historyApiFallback: {
index: '/index.html',
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
},
},
plugins: [
new NxAppRspackPlugin({
tsConfig: './tsconfig.app.json',
main: './src/main.tsx',
index: './src/index.html',
baseHref: '/',
assets: ['./src/favicon.ico', './src/assets'],
styles: ['./src/styles.scss'],
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
optimization: process.env['NODE_ENV'] === 'production',
}),
new NxReactRspackPlugin({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
// svgr: false
}),
],
}, {
name: 'server',
target: 'node',
entry: '../${serverName}/index.js',
output: {
path: join(__dirname, '../../dist/${serverName}'),
filename: 'index.js',
},
} }
` ];
); `
);
const pm = getPackageManagerCommand(); const result = runCLI(`build ${appName}`);
runCommand(
pm.addDev +
' @babel/preset-react @babel/preset-env @babel/preset-typescript'
);
let result = runCLI(`build ${project}`, { checkFilesExist(`dist/${appName}/main.js`);
env: { NODE_ENV: 'production' }, checkFilesExist(`dist/${serverName}/index.js`);
expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
}); });
expect(result).toContain('Successfully ran target build');
// Make sure expected files are present.
/**
* The files that are generated are:
* ["assets", "favicon.ico", "index.html", "main.bf7851e6.js", "runtime.e4294127.js"]
*/
expect(listFiles(`dist/${project}`)).toHaveLength(5);
result = runCLI(`test ${project}`); it('should support a function that returns an array of standard config objects', () => {
expect(result).toContain('Successfully ran target test'); const appName = uniq('app');
const serverName = uniq('server');
// TODO(Colum): re-enable when cypress issue is resolved runCLI(
// result = runCLI(`e2e e2e`); `generate @nx/react:application --directory=apps/${appName} --bundler=rspack --e2eTestRunner=none`
// expect(result.stdout).toContain('Successfully ran target e2e'); );
// Update app and make sure previous dist files are not present. // Create server index file
updateFile(`src/app/app.tsx`, (content) => { createFile(
return `${content}\nconsole.log('hello'); `apps/${serverName}/index.js`,
`; `console.log('Hello from ${serverName}');\n`
}); );
result = runCLI(`build ${project}`, {
env: { NODE_ENV: 'production' },
});
expect(result).toContain('Successfully ran target build');
expect(listFiles(`dist/${project}`)).toHaveLength(5); // same length as before
// Generate a new app and check that the files are correct updateFile(
const app2 = uniq('app2'); `apps/${appName}/rspack.config.js`,
runCLI( `
`generate @nx/rspack:app ${app2} --framework=react --unitTestRunner=jest --e2eTestRunner=cypress --style=css` const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
); const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
checkFilesExist(`${app2}/project.json`, `${app2}-e2e/project.json`); const { join } = require('path');
// Added this so that the nx-ecosystem-ci tests don't throw jest error module.exports = () => {
writeFileSync( return [
join(tmpProjPath(), app2, '.babelrc'),
`
{
"presets": [
"@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript",
[
"@nx/react/babel",
{ {
"runtime": "automatic" name: 'client',
output: {
path: join(__dirname, '../../dist/${appName}'),
},
devServer: {
port: 4200,
historyApiFallback: {
index: '/index.html',
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
},
},
plugins: [
new NxAppRspackPlugin({
tsConfig: './tsconfig.app.json',
main: './src/main.tsx',
index: './src/index.html',
baseHref: '/',
assets: ['./src/favicon.ico', './src/assets'],
styles: ['./src/styles.scss'],
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
optimization: process.env['NODE_ENV'] === 'production',
}),
new NxReactRspackPlugin({
// Uncomment this line if you don't want to use SVGR
// See: https://react-svgr.com/
// svgr: false
}),
],
},
{
name: 'server',
target: 'node',
entry: '../${serverName}/index.js',
output: {
path: join(__dirname, '../../dist/${serverName}'),
filename: 'index.js',
}
} }
] ];
], };`
"plugins": ["@babel/plugin-transform-runtime"] );
} const result = runCLI(`build ${appName}`);
`
);
result = runCLI(`build ${app2}`, { checkFilesExist(`dist/${serverName}/index.js`);
env: { NODE_ENV: 'production' }, checkFilesExist(`dist/${appName}/main.js`);
expect(result).toContain(
`Successfully ran target build for project ${appName}`
);
}); });
expect(result).toContain('Successfully ran target build'); });
// Make sure expected files are present.
expect(listFiles(`dist/${app2}`)).toHaveLength(5);
result = runCLI(`test ${app2}`);
expect(result).toContain('Successfully ran target test');
// TODO(Colum): re-enable when cypress issue is resolved
// result = runCLI(`e2e ${app2}-e2e`);
// expect(result.stdout).toContain('Successfully ran target e2e');
// Generate a Nest app and verify build output
const app3 = uniq('app3');
runCLI(
`generate @nx/rspack:app ${app3} --framework=nest --unitTestRunner=jest --no-interactive`
);
checkFilesExist(`${app3}/project.json`);
result = runCLI(`build ${app3}`);
expect(result).toContain('Successfully ran target build');
// Make sure expected files are present.
expect(listFiles(`dist/${app3}`)).toHaveLength(2);
result = runCLI(`build ${app3} --generatePackageJson=true`);
expect(result).toContain('Successfully ran target build');
// Make sure expected files are present.
expect(listFiles(`dist/${app3}`)).toHaveLength(4);
}, 200_000);
}); });

View File

@ -1632,7 +1632,7 @@ describe('app', () => {
linter: Linter.EsLint, linter: Linter.EsLint,
style: 'none', style: 'none',
e2eTestRunner: 'none', e2eTestRunner: 'none',
addPlugin: true, addPlugin: false,
skipFormat: true, skipFormat: true,
}); });

View File

@ -32,7 +32,6 @@ import { initWebpack } from './lib/bundlers/add-webpack';
import { import {
handleStyledJsxForRspack, handleStyledJsxForRspack,
initRspack, initRspack,
setupRspackConfiguration,
} from './lib/bundlers/add-rspack'; } from './lib/bundlers/add-rspack';
import { import {
initRsbuild, initRsbuild,
@ -125,8 +124,6 @@ export async function applicationGeneratorInternal(
if (options.bundler === 'vite') { if (options.bundler === 'vite') {
await setupViteConfiguration(tree, options, tasks); await setupViteConfiguration(tree, options, tasks);
} else if (options.bundler === 'rspack') {
await setupRspackConfiguration(tree, options, tasks);
} else if (options.bundler === 'rsbuild') { } else if (options.bundler === 'rsbuild') {
await setupRsbuildConfiguration(tree, options, tasks); await setupRsbuildConfiguration(tree, options, tasks);
} }

View File

@ -8,7 +8,6 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import * as pc from 'picocolors'; import * as pc from 'picocolors';
import { babelLoaderVersion, nxVersion } from '../../../../utils/versions'; import { babelLoaderVersion, nxVersion } from '../../../../utils/versions';
import { maybeJs } from '../../../../utils/maybe-js';
import { NormalizedSchema, Schema } from '../../schema'; import { NormalizedSchema, Schema } from '../../schema';
export async function initRspack( export async function initRspack(
@ -19,38 +18,11 @@ export async function initRspack(
const { rspackInitGenerator } = ensurePackage('@nx/rspack', nxVersion); const { rspackInitGenerator } = ensurePackage('@nx/rspack', nxVersion);
const rspackInitTask = await rspackInitGenerator(tree, { const rspackInitTask = await rspackInitGenerator(tree, {
...options, ...options,
addPlugin: false,
skipFormat: true, skipFormat: true,
}); });
tasks.push(rspackInitTask); tasks.push(rspackInitTask);
} }
export async function setupRspackConfiguration(
tree: Tree,
options: NormalizedSchema<Schema>,
tasks: any[]
) {
const { configurationGenerator } = ensurePackage('@nx/rspack', nxVersion);
const rspackTask = await configurationGenerator(tree, {
project: options.projectName,
main: joinPathFragments(
options.appProjectRoot,
maybeJs(
{
js: options.js,
useJsx: true,
},
`src/main.tsx`
)
),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
target: 'web',
newProject: true,
framework: 'react',
});
tasks.push(rspackTask);
}
export function handleStyledJsxForRspack( export function handleStyledJsxForRspack(
tasks: any[], tasks: any[],
tree: Tree, tree: Tree,

View File

@ -12,9 +12,6 @@ export default async function (
const tasks = []; const tasks = [];
const initTask = await rspackInitGenerator(tree, { const initTask = await rspackInitGenerator(tree, {
..._options, ..._options,
// TODO: Crystalize the default rspack.config.js file.
// The default setup isn't crystalized so don't add plugin.
addPlugin: false,
}); });
tasks.push(initTask); tasks.push(initTask);

View File

@ -28,9 +28,6 @@ export async function configurationGenerator(
) { ) {
const task = await rspackInitGenerator(tree, { const task = await rspackInitGenerator(tree, {
...options, ...options,
// TODO: Crystalize the default rspack.config.js file.
// The default setup isn't crystalized so don't add plugin.
addPlugin: false,
}); });
const { targets, root, projectType } = readProjectConfiguration( const { targets, root, projectType } = readProjectConfiguration(
tree, tree,