diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index 30be445ba5c2fa..5ee5d6c53863f2 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -165,6 +165,17 @@ export async function applicationGeneratorInternal( ); tasks.push(ensureDependencies(host, { uiFramework: 'react' })); } + } else if (options.bundler === 'rspack') { + const { rspackInitGenerator } = ensurePackage( + '@nx/rspack', + nxRspackVersion + ); + const rspackInitTask = await rspackInitGenerator(host, { + ...options, + addPlugin: false, + skipFormat: true, + }); + tasks.push(rspackInitTask); } if (!options.rootProject) { @@ -227,28 +238,28 @@ export async function applicationGeneratorInternal( false ); } else if (options.bundler === 'rspack') { - const { configurationGenerator } = ensurePackage( - '@nx/rspack', - nxRspackVersion - ); - const rspackTask = await configurationGenerator(host, { - 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); + // const { configurationGenerator } = ensurePackage( + // '@nx/rspack', + // nxRspackVersion + // ); + // const rspackTask = await configurationGenerator(host, { + // 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); addProjectRootToRspackPluginExcludesIfExists(host, options.appProjectRoot); } diff --git a/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ b/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ new file mode 100644 index 00000000000000..7d59123572b76e --- /dev/null +++ b/packages/react/src/generators/application/files/base-rspack/rspack.config.js__tmpl__ @@ -0,0 +1,53 @@ +<%_ if (rspackPluginOptions) { _%> +const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin'); +const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin'); +const { join } = require('path'); + +module.exports = { + output: { + path: join(__dirname, '<%= offsetFromRoot %><%= rspackPluginOptions.outputPath %>'), + }, + devServer: { + port: 4200, + historyApiFallback: { + index: '/index.html', + disableDotRule: true, + htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], + }, + }, + plugins: [ + new NxAppRspackPlugin({ + tsConfig: '<%= rspackPluginOptions.tsConfig %>', + main: '<%= rspackPluginOptions.main %>', + index: '<%= rspackPluginOptions.index %>', + baseHref: '<%= rspackPluginOptions.baseHref %>', + assets: <%- JSON.stringify(rspackPluginOptions.assets) %>, + styles: <%- JSON.stringify(rspackPluginOptions.styles) %>, + 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 + }), + ], +}; +<%_ } else { _%> +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. + // e.g. `config.plugins.push(new MyPlugin())` + return config; + } +); +<%_ } _%> diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 7f7d798043dbf4..dbc60719337030 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -21,6 +21,7 @@ import { getNxCloudAppOnBoardingUrl, createNxCloudOnboardingURLForWelcomeApp, } from 'nx/src/nx-cloud/utilities/onboarding'; +import { hasRspackPlugin } from '../../../utils/has-rspack-plugin'; export async function createApplicationFiles( host: Tree, @@ -145,7 +146,12 @@ export async function createApplicationFiles( host, join(__dirname, '../files/base-rspack'), options.appProjectRoot, - templateVariables + { + ...templateVariables, + rspackPluginOptions: hasRspackPlugin(host) + ? createNxRspackPluginOptions(options) + : null, + } ); } @@ -226,3 +232,36 @@ function createNxWebpackPluginOptions( ], }; } + +function createNxRspackPluginOptions( + options: NormalizedSchema +): WithNxOptions & WithReactOptions { + return { + target: 'web', + outputPath: joinPathFragments( + 'dist', + options.appProjectRoot != '.' + ? options.appProjectRoot + : options.projectName + ), + index: './src/index.html', + baseHref: '/', + main: maybeJs( + { + js: options.js, + useJsx: true, + }, + `./src/main.tsx` + ), + tsConfig: './tsconfig.app.json', + assets: ['./src/favicon.ico', './src/assets'], + styles: + options.styledModule || !options.hasStyles + ? [] + : [ + `./src/styles.${ + options.style !== 'tailwind' ? options.style : 'css' + }`, + ], + }; +} diff --git a/packages/react/src/utils/has-rspack-plugin.ts b/packages/react/src/utils/has-rspack-plugin.ts new file mode 100644 index 00000000000000..857ea6fefffa16 --- /dev/null +++ b/packages/react/src/utils/has-rspack-plugin.ts @@ -0,0 +1,10 @@ +import { readNxJson, Tree } from '@nx/devkit'; + +export function hasRspackPlugin(tree: Tree) { + const nxJson = readNxJson(tree); + return !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/rspack/plugin' + : p.plugin === '@nx/rspack/plugin' + ); +} diff --git a/packages/rspack/src/utils/generator-utils.ts b/packages/rspack/src/utils/generator-utils.ts index 06e226185e0adf..bdc8c08c6ec8f4 100644 --- a/packages/rspack/src/utils/generator-utils.ts +++ b/packages/rspack/src/utils/generator-utils.ts @@ -1,20 +1,25 @@ import { joinPathFragments, logger, + offsetFromRoot, readProjectConfiguration, TargetConfiguration, Tree, updateProjectConfiguration, } from '@nx/devkit'; import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'; -import { RspackExecutorSchema } from '../executors/rspack/schema'; +import { type RspackExecutorSchema } from '../executors/rspack/schema'; import { ConfigurationSchema } from '../generators/configuration/schema'; import { Framework } from '../generators/init/schema'; +import { hasPlugin } from './has-plugin'; export type Target = 'build' | 'serve'; export type TargetFlags = Partial>; export type UserProvidedTargetName = Partial>; export type ValidFoundTargetName = Partial>; +type ConfigurationWithStylePreprocessorOptions = ConfigurationSchema & { + stylePreprocessorOptions?: { includePaths?: string[] }; +}; export function findExistingTargetsInProject( targets: { @@ -235,92 +240,203 @@ export function writeRspackConfigFile( tree.write( joinPathFragments(project.root, 'rspack.config.js'), - createConfig(options, stylePreprocessorOptions) + createConfig(tree, { ...options, stylePreprocessorOptions }) ); } function createConfig( - options: ConfigurationSchema, - stylePreprocessorOptions?: { includePaths?: string[] } + tree: Tree, + options: ConfigurationSchema & { + stylePreprocessorOptions?: { includePaths?: string[] }; + } ) { - if (options.framework === 'react') { - return ` - const { composePlugins, withNx, withReact } = require('@nx/rspack'); - - module.exports = composePlugins(withNx(), withReact(${ - stylePreprocessorOptions - ? ` - { - stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)}, - } - ` - : '' - }), (config) => { - return config; - }); - `; - } else if (options.framework === 'web' || options.target === 'web') { - return ` - const { composePlugins, withNx, withWeb } = require('@nx/rspack'); - - module.exports = composePlugins(withNx(), withWeb(${ - stylePreprocessorOptions - ? ` - { - stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)}, - } - ` - : '' - }), (config) => { - return config; - }); - `; + const project = readProjectConfiguration(tree, options.project); + const buildOptions = createBuildOptions(tree, options, project); + + const defaultConfig = generateDefaultConfig(project, buildOptions); + + if (isWebFramework(options)) { + return generateWebConfig(tree, options, defaultConfig); } else if (options.framework === 'nest') { - return ` - const { composePlugins, withNx } = require('@nx/rspack'); - const rspack = require('@rspack/core'); - - module.exports = composePlugins(withNx(), (config) => { - config.optimization = { - minimizer: [ - new rspack.SwcJsMinimizerRspackPlugin({ - minimizerOptions: { - // We need to disable mangling and compression for class names and function names for Nest.js to work properly - // The execution context class returns a reference to the class/handler function, which is for example used for applying metadata using decorators - // https://docs.nestjs.com/fundamentals/execution-context#executioncontext-class - compress: { - keep_classnames: true, - keep_fnames: true, - }, - mangle: { - keep_classnames: true, - keep_fnames: true, - }, - }, - }), - ], - }; - return config; - }); - `; + return generateNestConfig(tree, options, project, buildOptions); } else { + return generateGenericConfig(tree, options, defaultConfig); + } +} + +function createBuildOptions( + tree: Tree, + options: ConfigurationSchema, + project: any +): RspackExecutorSchema { + return { + target: options.target ?? 'web', + outputPath: joinPathFragments( + 'dist', + project.root === '.' ? project.name : project.root + ), + main: determineMain(tree, options), + tsConfig: determineTsConfig(tree, options), + rspackConfig: joinPathFragments(project.root, 'rspack.config.js'), + }; +} + +function generateDefaultConfig( + project: any, + buildOptions: RspackExecutorSchema +): string { + return ` +const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin'); +const { join } = require('path'); + +module.exports = { + output: { + path: join(__dirname, '${offsetFromRoot(project.root)}${ + buildOptions.outputPath + }'), + }, + plugins: [ + new NxAppRspackPlugin({ + target: '${buildOptions.target}', + tsConfig: '${buildOptions.tsConfig}', + main: '${buildOptions.main}', + outputHashing: '${buildOptions.target !== 'web' ? 'none' : 'all'}', + }) + ] +}`; +} + +function isWebFramework( + options: ConfigurationWithStylePreprocessorOptions +): boolean { + return options.framework === 'web' || options.target === 'web'; +} + +function generateWebConfig( + tree: Tree, + options: ConfigurationWithStylePreprocessorOptions, + defaultConfig: string +): string { + if (hasPlugin(tree)) { + return defaultConfig; + } + + return ` +const { composePlugins, withNx, withWeb } = require('@nx/rspack'); +module.exports = composePlugins(withNx(), withWeb(${ + options.stylePreprocessorOptions + ? ` + { + stylePreprocessorOptions: ${JSON.stringify( + options.stylePreprocessorOptions + )}, + } + ` + : '' + }), (config) => { + return config; + }); +`; +} + +function generateNestConfig( + tree: Tree, + options: ConfigurationSchema, + project: any, + buildOptions: RspackExecutorSchema +): string { + if (hasPlugin(tree)) { return ` - const { composePlugins, withNx${ - stylePreprocessorOptions ? ', withWeb' : '' - } } = require('@nx/rspack'); - - module.exports = composePlugins(withNx()${ - stylePreprocessorOptions - ? `, - withWeb({ - stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)}, - })` - : '' - }, (config) => { - return config; - }); - `; +const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin'); +const rspack = require('@rspack/core'); +const { join } = require('path'); + +module.exports = { + output: { + path: join(__dirname, '${offsetFromRoot(project.root)}${ + buildOptions.outputPath + }'), + }, + optimization: { + minimizer: [ + new rspack.SwcJsMinimizerRspackPlugin({ + minimizerOptions: { + compress: { + keep_classnames: true, + keep_fnames: true, + }, + mangle: { + keep_classnames: true, + keep_fnames: true, + }, + }, + }), + ], + }, + plugins: [ + new NxAppRspackPlugin({ + target: '${buildOptions.target}', + tsConfig: '${buildOptions.tsConfig}', + main: '${buildOptions.main}', + outputHashing: '${buildOptions.target !== 'web' ? 'none' : 'all'}', + }) + ] +}`; + } + + return ` +const { composePlugins, withNx } = require('@nx/rspack'); +const rspack = require('@rspack/core'); + +module.exports = composePlugins(withNx(), (config) => { + config.optimization = { + minimizer: [ + new rspack.SwcJsMinimizerRspackPlugin({ + minimizerOptions: { + compress: { + keep_classnames: true, + keep_fnames: true, + }, + mangle: { + keep_classnames: true, + keep_fnames: true, + }, + }, + }), + ], + }; + return config; +}); +`; +} + +function generateGenericConfig( + tree: Tree, + options: ConfigurationWithStylePreprocessorOptions, + defaultConfig: string +): string { + if (hasPlugin(tree)) { + return defaultConfig; } + + return ` +const { composePlugins, withNx${ + options.stylePreprocessorOptions ? ', withWeb' : '' + } } = require('@nx/rspack'); + +module.exports = composePlugins(withNx()${ + options.stylePreprocessorOptions + ? `, + withWeb({ + stylePreprocessorOptions: ${JSON.stringify( + options.stylePreprocessorOptions + )}, + })` + : '' + }, (config) => { + return config; + }); +`; } export function deleteWebpackConfig( diff --git a/packages/rspack/src/utils/has-plugin.ts b/packages/rspack/src/utils/has-plugin.ts new file mode 100644 index 00000000000000..175cff0335027a --- /dev/null +++ b/packages/rspack/src/utils/has-plugin.ts @@ -0,0 +1,10 @@ +import { readNxJson, Tree } from '@nx/devkit'; + +export function hasPlugin(tree: Tree) { + const nxJson = readNxJson(tree); + return !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/rspack/plugin' + : p.plugin === '@nx/rspack/plugin' + ); +}