diff --git a/.changeset/honest-snakes-draw.md b/.changeset/honest-snakes-draw.md new file mode 100644 index 0000000000..e43a337576 --- /dev/null +++ b/.changeset/honest-snakes-draw.md @@ -0,0 +1,21 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: The new app build based on [Rspack](https://rspack.dev/) is now the default, and the `EXPERIMENTAL_RSPACK` flag has been removed. To revert to the old behavior, set the `LEGACY_WEBPACK_BUILD` environment flag and install the following optional dependencies: + +```json +{ + "dependencies": { + "@module-federation/enhanced": "^0.9.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "esbuild-loader": "^4.0.0", + "mini-css-extract-plugin": "^2.4.2", + "terser-webpack-plugin": "^5.1.3", + "webpack": "^5.94.0", + "webpack-dev-server": "^5.0.0" + } +} +``` + +If you do encounter a blocking issue that forces you to use the old WebPack build, please [open an issue](https://github.com/backstage/backstage/issues) explaining the problem. The WebPack build will be removed in a future release. diff --git a/packages/cli/package.json b/packages/cli/package.json index 8b77fb5fab..b05ea0cdec 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -58,20 +58,20 @@ "@backstage/release-manifests": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@module-federation/enhanced": "^0.9.0", "@octokit/graphql": "^5.0.0", "@octokit/graphql-schema": "^13.7.0", "@octokit/oauth-app": "^4.2.0", "@octokit/request": "^8.0.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@rollup/plugin-commonjs": "^26.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-yaml": "^4.0.0", + "@rspack/core": "^1.4.11", + "@rspack/dev-server": "^1.1.4", + "@rspack/plugin-react-refresh": "^1.4.3", "@spotify/eslint-config-base": "^15.0.0", "@spotify/eslint-config-react": "^15.0.0", "@spotify/eslint-config-typescript": "^15.0.0", - "@sucrase/webpack-loader": "^2.0.0", "@swc/core": "^1.3.46", "@swc/helpers": "^0.5.0", "@swc/jest": "^0.2.22", @@ -91,7 +91,6 @@ "css-loader": "^6.5.1", "ctrlc-windows": "^2.1.0", "esbuild": "^0.25.0", - "esbuild-loader": "^4.0.0", "eslint": "^8.6.0", "eslint-config-prettier": "^9.0.0", "eslint-formatter-friendly": "^7.0.0", @@ -120,7 +119,6 @@ "jest-runtime": "^29.0.2", "json-schema": "^0.4.0", "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.4.2", "minimatch": "^9.0.0", "node-stdlib-browser": "^1.3.1", "npm-packlist": "^5.0.0", @@ -144,12 +142,9 @@ "sucrase": "^3.20.2", "swc-loader": "^0.2.3", "tar": "^6.1.12", - "terser-webpack-plugin": "^5.1.3", "ts-morph": "^24.0.0", "undici": "^7.2.3", "util": "^0.12.3", - "webpack": "^5.94.0", - "webpack-dev-server": "^5.0.0", "yaml": "^2.0.0", "yargs": "^16.2.0", "yml-loader": "^2.1.0", @@ -174,9 +169,8 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/theme": "workspace:^", - "@rspack/core": "^1.3.9", - "@rspack/dev-server": "^1.1.1", - "@rspack/plugin-react-refresh": "^1.4.2", + "@module-federation/enhanced": "^0.9.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@types/cross-spawn": "^6.0.2", "@types/ejs": "^3.1.3", "@types/express": "^4.17.6", @@ -194,22 +188,43 @@ "@types/webpack-sources": "^3.2.3", "@types/yarnpkg__lockfile": "^1.1.4", "del": "^8.0.0", + "esbuild-loader": "^4.0.0", + "mini-css-extract-plugin": "^2.4.2", "msw": "^1.0.0", - "nodemon": "^3.0.1" + "nodemon": "^3.0.1", + "terser-webpack-plugin": "^5.1.3", + "webpack": "~5.96.0", + "webpack-dev-server": "^5.0.0" }, "peerDependencies": { - "@rspack/core": "^1.2.8", - "@rspack/dev-server": "^1.0.9", - "@rspack/plugin-react-refresh": "^1.0.0" + "@module-federation/enhanced": "^0.9.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "esbuild-loader": "^4.0.0", + "mini-css-extract-plugin": "^2.4.2", + "terser-webpack-plugin": "^5.1.3", + "webpack": "~5.96.0", + "webpack-dev-server": "^5.0.0" }, "peerDependenciesMeta": { - "@rspack/core": { + "@module-federation/enhanced": { "optional": true }, - "@rspack/dev-server": { + "@pmmmwh/react-refresh-webpack-plugin": { "optional": true }, - "@rspack/plugin-react-refresh": { + "esbuild-loader": { + "optional": true + }, + "mini-css-extract-plugin": { + "optional": true + }, + "terser-webpack-plugin": { + "optional": true + }, + "webpack": { + "optional": true + }, + "webpack-dev-server": { "optional": true } }, diff --git a/packages/cli/src/modules/build/commands/package/build/command.ts b/packages/cli/src/modules/build/commands/package/build/command.ts index d6a71b6fc1..378fd31896 100644 --- a/packages/cli/src/modules/build/commands/package/build/command.ts +++ b/packages/cli/src/modules/build/commands/package/build/command.ts @@ -25,8 +25,8 @@ import { isValidUrl } from '../../../lib/urls'; import chalk from 'chalk'; export async function command(opts: OptionValues): Promise { - const rspack = process.env.EXPERIMENTAL_RSPACK - ? (require('@rspack/core') as typeof import('@rspack/core').rspack) + const webpack = process.env.LEGACY_WEBPACK_BUILD + ? (require('webpack') as typeof import('webpack')) : undefined; const role = await findRoleFromCommand(opts); @@ -44,7 +44,7 @@ export async function command(opts: OptionValues): Promise { targetDir: paths.targetDir, configPaths, writeStats: Boolean(opts.stats), - rspack, + webpack, }); } return buildBackend({ @@ -67,7 +67,7 @@ export async function command(opts: OptionValues): Promise { configPaths: [], writeStats: Boolean(opts.stats), isModuleFederationRemote: true, - rspack, + webpack, }); } diff --git a/packages/cli/src/modules/build/commands/repo/build.ts b/packages/cli/src/modules/build/commands/repo/build.ts index d0b9de8867..beb19afb32 100644 --- a/packages/cli/src/modules/build/commands/repo/build.ts +++ b/packages/cli/src/modules/build/commands/repo/build.ts @@ -31,10 +31,9 @@ import { createScriptOptionsParser } from '../../../../lib/optionsParser'; export async function command(opts: OptionValues, cmd: Command): Promise { let packages = await PackageGraph.listTargetPackages(); - const shouldUseRspack = Boolean(process.env.EXPERIMENTAL_RSPACK); - const rspack = shouldUseRspack - ? (require('@rspack/core') as typeof import('@rspack/core').rspack) + const webpack = process.env.LEGACY_WEBPACK_BUILD + ? (require('webpack') as typeof import('webpack')) : undefined; if (opts.since) { @@ -116,7 +115,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { targetDir: pkg.dir, configPaths: (buildOptions.config as string[]) ?? [], writeStats: Boolean(buildOptions.stats), - rspack, + webpack, }); }, }); diff --git a/packages/cli/src/modules/build/lib/buildFrontend.ts b/packages/cli/src/modules/build/lib/buildFrontend.ts index 6ff04cdb53..fd6ce9c420 100644 --- a/packages/cli/src/modules/build/lib/buildFrontend.ts +++ b/packages/cli/src/modules/build/lib/buildFrontend.ts @@ -26,11 +26,11 @@ interface BuildAppOptions { writeStats: boolean; configPaths: string[]; isModuleFederationRemote?: true; - rspack?: typeof import('@rspack/core').rspack; + webpack?: typeof import('webpack'); } export async function buildFrontend(options: BuildAppOptions) { - const { targetDir, writeStats, configPaths, rspack } = options; + const { targetDir, writeStats, configPaths, webpack } = options; const packageJson = (await fs.readJson( resolvePath(targetDir, 'package.json'), )) as BackstagePackageJson; @@ -48,6 +48,6 @@ export async function buildFrontend(options: BuildAppOptions) { args: configPaths, fromPackage: packageJson.name, })), - rspack, + webpack, }); } diff --git a/packages/cli/src/modules/build/lib/bundler/bundle.ts b/packages/cli/src/modules/build/lib/bundler/bundle.ts index 7d88a2f97f..0bdec2d1c3 100644 --- a/packages/cli/src/modules/build/lib/bundler/bundle.ts +++ b/packages/cli/src/modules/build/lib/bundler/bundle.ts @@ -17,7 +17,7 @@ import yn from 'yn'; import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; -import webpack from 'webpack'; +import { rspack, Configuration, MultiStats } from '@rspack/core'; import { measureFileSizesBeforeBuild, printFileSizesAfterBuild, @@ -38,7 +38,7 @@ function applyContextToError(error: string, moduleName: string): string { } export async function buildBundle(options: BuildOptions) { - const { statsJsonEnabled, schema: configSchema, rspack } = options; + const { statsJsonEnabled, schema: configSchema, webpack } = options; const paths = resolveBundlingPaths(options); const publicPaths = await resolveOptionalBundlingPaths({ @@ -54,7 +54,7 @@ export async function buildBundle(options: BuildOptions) { getFrontendAppConfigs: () => options.frontendAppConfigs, }; - const configs: webpack.Configuration[] = []; + const configs: Configuration[] = []; if (options.moduleFederation?.mode === 'remote') { // Package detection is disabled for remote bundles @@ -119,13 +119,11 @@ export async function buildBundle(options: BuildOptions) { ); } - if (rspack) { - console.log( - chalk.yellow(`⚠️ WARNING: Using experimental RSPack bundler.`), - ); + if (webpack) { + console.log(chalk.yellow(`⚠️ WARNING: Using legacy WebPack bundler`)); } - const { stats } = await build(configs, isCi, rspack); + const { stats } = await build(configs, isCi, webpack); if (!stats) { throw new Error('No stats returned'); @@ -159,34 +157,32 @@ export async function buildBundle(options: BuildOptions) { } async function build( - configs: webpack.Configuration[], + configs: Configuration[], isCi: boolean, - rspack?: typeof import('@rspack/core').rspack, + webpack?: typeof import('webpack'), ) { - const bundler = (rspack ?? webpack) as typeof webpack; + const bundler = (webpack ?? rspack) as typeof rspack; - const stats = await new Promise( - (resolve, reject) => { - bundler(configs, (err, buildStats) => { - if (err) { - if (err.message) { - const { errors } = formatWebpackMessages({ - errors: [err.message], - warnings: new Array(), - _showErrors: true, - _showWarnings: true, - }); + const stats = await new Promise((resolve, reject) => { + bundler(configs, (err, buildStats) => { + if (err) { + if (err.message) { + const { errors } = formatWebpackMessages({ + errors: [err.message], + warnings: new Array(), + _showErrors: true, + _showWarnings: true, + }); - throw new Error(errors[0]); - } else { - reject(err); - } + throw new Error(errors[0]); } else { - resolve(buildStats); + reject(err); } - }); - }, - ); + } else { + resolve(buildStats); + } + }); + }); if (!stats) { throw new Error('Failed to compile: No stats provided'); diff --git a/packages/cli/src/modules/build/lib/bundler/config.ts b/packages/cli/src/modules/build/lib/bundler/config.ts index 655ce4fd8b..718c034b1a 100644 --- a/packages/cli/src/modules/build/lib/bundler/config.ts +++ b/packages/cli/src/modules/build/lib/bundler/config.ts @@ -16,16 +16,15 @@ import { BundlingOptions, ModuleFederationOptions } from './types'; import { resolve as resolvePath } from 'path'; -import webpack from 'webpack'; +import { rspack, Configuration } from '@rspack/core'; import { BundlingPaths } from './paths'; import { Config } from '@backstage/config'; import ESLintPlugin from 'eslint-webpack-plugin'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; -import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; -import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin'; +import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; import { paths as cliPaths } from '../../../../lib/paths'; import fs from 'fs-extra'; import { optimization as optimizationConfig } from './optimization'; @@ -113,14 +112,14 @@ async function readBuildInfo() { export async function createConfig( paths: BundlingPaths, options: BundlingOptions, -): Promise { +): Promise { const { checksEnabled, isDev, frontendConfig, moduleFederation, publicSubPath = '', - rspack, + webpack, } = options; const { plugins, loaders } = transforms(options); @@ -147,11 +146,12 @@ export async function createConfig( }, } as const; - if (rspack) { + if (webpack) { + const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); + plugins.push(new ReactRefreshPlugin(refreshOptions)); + } else { const RspackReactRefreshPlugin = require('@rspack/plugin-react-refresh'); plugins.push(new RspackReactRefreshPlugin(refreshOptions)); - } else { - plugins.push(new ReactRefreshPlugin(refreshOptions)); } } @@ -167,7 +167,7 @@ export async function createConfig( ); } - const bundler = rspack ? (rspack as unknown as typeof webpack) : webpack; + const bundler = webpack ? (webpack as unknown as typeof rspack) : rspack; // TODO(blam): process is no longer auto polyfilled by webpack in v5. // we use the provide plugin to provide this polyfill, but lets look @@ -190,7 +190,11 @@ export async function createConfig( config: frontendConfig, }, }; - if (rspack) { + if (webpack) { + // Config injection via index.html doesn't work across reloads with + // WebPack, so we rely on the APP_CONFIG injection instead + plugins.push(new HtmlWebpackPlugin(templateOptions)); + } else { // With Rspack we inject config via index.html, this is both because we // can't use APP_CONFIG due to the lack of support for runtime values, but // also because we are able to do it and it lines up better with what the @@ -204,10 +208,6 @@ export async function createConfig( options.getFrontendAppConfigs, ), ); - } else { - // Config injection via index.html doesn't work across reloads with - // WebPack, so we rely on the APP_CONFIG injection instead - plugins.push(new HtmlWebpackPlugin(templateOptions)); } plugins.push( new HtmlWebpackPlugin({ @@ -227,9 +227,9 @@ export async function createConfig( if (options.moduleFederation) { const isRemote = options.moduleFederation?.mode === 'remote'; - const AdaptedModuleFederationPlugin = rspack - ? (require('@module-federation/enhanced/rspack') - .ModuleFederationPlugin as typeof ModuleFederationPlugin) + const AdaptedModuleFederationPlugin = webpack + ? (require('@module-federation/enhanced/webpack') + .ModuleFederationPlugin as unknown as typeof ModuleFederationPlugin) : ModuleFederationPlugin; const exposes = options.moduleFederation?.exposes @@ -304,18 +304,28 @@ export async function createConfig( const buildInfo = await readBuildInfo(); plugins.push( - new bundler.DefinePlugin({ - 'process.env.BUILD_INFO': JSON.stringify(buildInfo), - 'process.env.APP_CONFIG': rspack - ? JSON.stringify([]) // Inject via index.html instead - : bundler.DefinePlugin.runtimeValue( + webpack + ? new webpack.DefinePlugin({ + 'process.env.BUILD_INFO': JSON.stringify(buildInfo), + 'process.env.APP_CONFIG': webpack.DefinePlugin.runtimeValue( () => JSON.stringify(options.getFrontendAppConfigs()), true, ), - // This allows for conditional imports of react-dom/client, since there's no way - // to check for presence of it in source code without module resolution errors. - 'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify(hasReactDomClient()), - }), + // This allows for conditional imports of react-dom/client, since there's no way + // to check for presence of it in source code without module resolution errors. + 'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify( + hasReactDomClient(), + ), + }) + : new bundler.DefinePlugin({ + 'process.env.BUILD_INFO': JSON.stringify(buildInfo), + 'process.env.APP_CONFIG': JSON.stringify([]), // Inject via index.html instead + // This allows for conditional imports of react-dom/client, since there's no way + // to check for presence of it in source code without module resolution errors. + 'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify( + hasReactDomClient(), + ), + }), ); if (options.linkedWorkspace) { @@ -330,9 +340,8 @@ export async function createConfig( // These files are required by the transpiled code when using React Refresh. // They need to be excluded to the module scope plugin which ensures that files // that exist in the package are required. - const reactRefreshFiles = rspack - ? [] - : [ + const reactRefreshFiles = webpack + ? [ require.resolve( '@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js', ), @@ -340,7 +349,8 @@ export async function createConfig( '@pmmmwh/react-refresh-webpack-plugin/overlay/index.js', ), require.resolve('react-refresh'), - ]; + ] + : []; const mode = isDev ? 'development' : 'production'; const optimization = optimizationConfig(options); @@ -388,7 +398,7 @@ export async function createConfig( util: require.resolve('util/'), }, // FIXME: see also https://github.com/web-infra-dev/rspack/issues/3408 - ...(!rspack && { + ...(webpack && { plugins: [ new ModuleScopePlugin( [paths.targetSrc, paths.targetDev], @@ -421,7 +431,7 @@ export async function createConfig( }, experiments: { lazyCompilation: yn(process.env.EXPERIMENTAL_LAZY_COMPILATION), - ...(rspack && { + ...(!webpack && { // We're still using `style-loader` for custom `insert` option css: false, }), diff --git a/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts b/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts index a2fe6cd25a..e2a9e7b6b3 100644 --- a/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts +++ b/packages/cli/src/modules/build/lib/bundler/linkWorkspaces.ts @@ -16,7 +16,7 @@ import { relative as relativePath } from 'path'; import { getPackages } from '@manypkg/get-packages'; -import webpack from 'webpack'; +import { rspack } from '@rspack/core'; import { paths } from '../../../../lib/paths'; /** @@ -26,7 +26,7 @@ import { paths } from '../../../../lib/paths'; * of react and react-dom which are always resolved from the target workspace. */ export async function createWorkspaceLinkingPlugins( - bundler: typeof webpack, + bundler: typeof rspack, workspace: string, ) { const { packages: linkedPackages, root: linkedRoot } = await getPackages( diff --git a/packages/cli/src/modules/build/lib/bundler/optimization.ts b/packages/cli/src/modules/build/lib/bundler/optimization.ts index 9f61043910..5d763ea4ac 100644 --- a/packages/cli/src/modules/build/lib/bundler/optimization.ts +++ b/packages/cli/src/modules/build/lib/bundler/optimization.ts @@ -14,19 +14,21 @@ * limitations under the License. */ -import { WebpackOptionsNormalized } from 'webpack'; import { BundlingOptions } from './types'; - -const { EsbuildPlugin } = require('esbuild-loader'); +import { + SwcJsMinimizerRspackPlugin, + LightningCssMinimizerRspackPlugin, + RspackOptionsNormalized, +} from '@rspack/core'; export const optimization = ( options: BundlingOptions, -): WebpackOptionsNormalized['optimization'] => { - const { isDev, rspack } = options; +): RspackOptionsNormalized['optimization'] => { + const { isDev, webpack } = options; - const MinifyPlugin = rspack - ? rspack.SwcJsMinimizerRspackPlugin - : EsbuildPlugin; + const MinifyPlugin = webpack + ? require('esbuild-loader').EsbuildPlugin + : SwcJsMinimizerRspackPlugin; return { minimize: !isDev, @@ -42,7 +44,7 @@ export const optimization = ( format: undefined, include: 'remoteEntry.js', }), - rspack && new rspack.LightningCssMinimizerRspackPlugin(), + webpack ? undefined : new LightningCssMinimizerRspackPlugin(), ], runtimeChunk: 'single', splitChunks: { @@ -74,7 +76,7 @@ export const optimization = ( priority: 10, minSize: 100000, minChunks: 1, - ...(!rspack && { + ...(webpack && { maxAsyncRequests: Infinity, maxInitialRequests: Infinity, }), diff --git a/packages/cli/src/modules/build/lib/bundler/server.ts b/packages/cli/src/modules/build/lib/bundler/server.ts index 9d4af5c0fe..76043e67c6 100644 --- a/packages/cli/src/modules/build/lib/bundler/server.ts +++ b/packages/cli/src/modules/build/lib/bundler/server.ts @@ -19,8 +19,8 @@ import chalk from 'chalk'; import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; import openBrowser from 'react-dev-utils/openBrowser'; -import webpack from 'webpack'; -import WebpackDevServer from 'webpack-dev-server'; +import { rspack } from '@rspack/core'; +import { RspackDevServer } from '@rspack/dev-server'; import { paths as libPaths } from '../../../../lib/paths'; import { loadCliConfig } from '../../../config/lib/config'; @@ -55,20 +55,20 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be resolvePath(options.targetDir ?? libPaths.targetDir, 'package.json'), ); - let webpackServer: WebpackDevServer | undefined = undefined; + let devServer: RspackDevServer | undefined = undefined; let latestFrontendAppConfigs: AppConfig[] = []; /** Triggers a full reload of all clients */ const triggerReload = () => { - if (webpackServer) { - webpackServer.invalidate(); + if (devServer) { + devServer.invalidate(); // For the Rspack server it's not enough to invalidate, we also need to // tell the browser to reload, which we do with a 'static-changed' message - if (process.env.EXPERIMENTAL_RSPACK) { - webpackServer.sendMessage( - webpackServer.webSocketServer?.clients ?? [], + if (!process.env.LEGACY_WEBPACK_BUILD) { + devServer.sendMessage( + devServer.webSocketServer?.clients ?? [], 'static-changed', ); } @@ -122,8 +122,8 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be }, }); - const rspack = process.env.EXPERIMENTAL_RSPACK - ? (require('@rspack/core') as typeof import('@rspack/core').rspack) + const webpack = process.env.LEGACY_WEBPACK_BUILD + ? (require('webpack') as typeof import('webpack')) : undefined; const commonConfigOptions = { @@ -132,7 +132,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be isDev: true, baseUrl: url, frontendConfig, - rspack, + webpack, getFrontendAppConfigs: () => { return latestFrontendAppConfigs; }, @@ -144,15 +144,13 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be moduleFederation: options.moduleFederation, }); - const bundler = (rspack ?? webpack) as typeof webpack; - const DevServer: typeof WebpackDevServer = rspack - ? require('@rspack/dev-server').RspackDevServer - : WebpackDevServer; + const bundler = (webpack ?? rspack) as typeof rspack; + const DevServer: typeof RspackDevServer = webpack + ? require('webpack-dev-server') + : RspackDevServer; - if (rspack) { - console.log( - chalk.yellow(`⚠️ WARNING: Using experimental RSPack dev server.`), - ); + if (webpack) { + console.log(chalk.yellow(`⚠️ WARNING: Using legacy WebPack dev server.`)); } const publicPaths = await resolveOptionalBundlingPaths({ @@ -170,7 +168,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be ? bundler([config, await createConfig(publicPaths, commonConfigOptions)]) : bundler(config); - webpackServer = new DevServer( + devServer = new DevServer( { hot: !process.env.CI, devMiddleware: { @@ -225,8 +223,8 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be ); await new Promise(async (resolve, reject) => { - if (webpackServer) { - webpackServer.startCallback((err?: Error) => { + if (devServer) { + devServer.startCallback((err?: Error) => { if (err) { reject(err); return; @@ -245,7 +243,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be const waitForExit = async () => { for (const signal of ['SIGINT', 'SIGTERM'] as const) { process.on(signal, () => { - webpackServer?.stop(); + devServer?.stop(); // exit instead of resolve. The process is shutting down and resolving a promise here logs an error process.exit(); }); diff --git a/packages/cli/src/modules/build/lib/bundler/transforms.ts b/packages/cli/src/modules/build/lib/bundler/transforms.ts index ec78311ee2..64e33b220e 100644 --- a/packages/cli/src/modules/build/lib/bundler/transforms.ts +++ b/packages/cli/src/modules/build/lib/bundler/transforms.ts @@ -14,26 +14,30 @@ * limitations under the License. */ -import { RuleSetRule, WebpackPluginInstance } from 'webpack'; -import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import { + RuleSetRule, + RspackPluginInstance, + CssExtractRspackPlugin, + WebpackPluginInstance, +} from '@rspack/core'; type Transforms = { loaders: RuleSetRule[]; - plugins: WebpackPluginInstance[]; + plugins: Array; }; type TransformOptions = { isDev: boolean; isBackend?: boolean; - rspack?: typeof import('@rspack/core').rspack; + webpack?: typeof import('webpack').webpack; }; export const transforms = (options: TransformOptions): Transforms => { - const { isDev, isBackend, rspack } = options; + const { isDev, isBackend, webpack } = options; - const CssExtractRspackPlugin: typeof MiniCssExtractPlugin = rspack - ? (rspack.CssExtractRspackPlugin as unknown as typeof MiniCssExtractPlugin) - : MiniCssExtractPlugin; + const CssExtractPlugin: typeof CssExtractRspackPlugin = webpack + ? (require('mini-css-extract-plugin') as unknown as typeof CssExtractRspackPlugin) + : CssExtractRspackPlugin; // This ensures that styles inserted from the style-loader and any // async style chunks are always given lower priority than JSS styles. @@ -58,7 +62,9 @@ export const transforms = (options: TransformOptions): Transforms => { exclude: /node_modules/, use: [ { - loader: rspack ? 'builtin:swc-loader' : require.resolve('swc-loader'), + loader: webpack + ? require.resolve('swc-loader') + : 'builtin:swc-loader', options: { jsc: { target: 'es2022', @@ -86,7 +92,9 @@ export const transforms = (options: TransformOptions): Transforms => { exclude: /node_modules/, use: [ { - loader: rspack ? 'builtin:swc-loader' : require.resolve('swc-loader'), + loader: webpack + ? require.resolve('swc-loader') + : 'builtin:swc-loader', options: { jsc: { target: 'es2022', @@ -161,7 +169,7 @@ export const transforms = (options: TransformOptions): Transforms => { insert: insertBeforeJssStyles, }, } - : CssExtractRspackPlugin.loader, + : CssExtractPlugin.loader, { loader: require.resolve('css-loader'), options: { @@ -172,11 +180,11 @@ export const transforms = (options: TransformOptions): Transforms => { }, ]; - const plugins = new Array(); + const plugins = new Array(); if (!isDev) { plugins.push( - new CssExtractRspackPlugin({ + new CssExtractPlugin({ filename: 'static/[name].[contenthash:8].css', chunkFilename: 'static/[name].[id].[contenthash:8].css', insert: insertBeforeJssStyles, // Only applies to async chunks diff --git a/packages/cli/src/modules/build/lib/bundler/types.ts b/packages/cli/src/modules/build/lib/bundler/types.ts index a018ccad93..59b0dcb70d 100644 --- a/packages/cli/src/modules/build/lib/bundler/types.ts +++ b/packages/cli/src/modules/build/lib/bundler/types.ts @@ -45,7 +45,7 @@ export type BundlingOptions = { // An external linked workspace to include in the bundling linkedWorkspace?: string; moduleFederation?: ModuleFederationOptions; - rspack?: typeof import('@rspack/core').rspack; + webpack?: typeof import('webpack'); }; export type ServeOptions = BundlingPathsOptions & { @@ -69,7 +69,7 @@ export type BuildOptions = BundlingPathsOptions & { frontendAppConfigs: AppConfig[]; fullConfig: Config; moduleFederation?: ModuleFederationOptions; - rspack?: typeof import('@rspack/core').rspack; + webpack?: typeof import('webpack'); }; export type BackendBundlingOptions = { @@ -79,5 +79,5 @@ export type BackendBundlingOptions = { inspectEnabled: boolean; inspectBrkEnabled: boolean; require?: string; - rspack?: typeof import('@rspack/core').rspack; + webpack?: typeof import('webpack'); }; diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 10a2490439..3ddef88689 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -58,8 +58,6 @@ declare module 'react-dev-utils/ModuleScopePlugin' { } declare module 'react-dev-utils/FileSizeReporter' { - import webpack = require('webpack'); - export interface OpaqueFileSizes { root: string; sizes: Record; @@ -81,7 +79,7 @@ declare module 'react-dev-utils/FileSizeReporter' { * the main bundle or a chunk exceeds the specified size (in bytes). */ export function printFileSizesAfterBuild( - webpackStats: webpack.Stats, + stats: import('@rspack/core').Stats, previousFileSizes: OpaqueFileSizes, buildFolder: string, maxBundleGzipSize?: number, diff --git a/yarn.lock b/yarn.lock index b15db89924..7360d7a559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2976,13 +2976,12 @@ __metadata: "@rollup/plugin-json": "npm:^6.0.0" "@rollup/plugin-node-resolve": "npm:^15.0.0" "@rollup/plugin-yaml": "npm:^4.0.0" - "@rspack/core": "npm:^1.3.9" - "@rspack/dev-server": "npm:^1.1.1" - "@rspack/plugin-react-refresh": "npm:^1.4.2" + "@rspack/core": "npm:^1.4.11" + "@rspack/dev-server": "npm:^1.1.4" + "@rspack/plugin-react-refresh": "npm:^1.4.3" "@spotify/eslint-config-base": "npm:^15.0.0" "@spotify/eslint-config-react": "npm:^15.0.0" "@spotify/eslint-config-typescript": "npm:^15.0.0" - "@sucrase/webpack-loader": "npm:^2.0.0" "@swc/core": "npm:^1.3.46" "@swc/helpers": "npm:^0.5.0" "@swc/jest": "npm:^0.2.22" @@ -3078,7 +3077,7 @@ __metadata: ts-morph: "npm:^24.0.0" undici: "npm:^7.2.3" util: "npm:^0.12.3" - webpack: "npm:^5.94.0" + webpack: "npm:~5.96.0" webpack-dev-server: "npm:^5.0.0" yaml: "npm:^2.0.0" yargs: "npm:^16.2.0" @@ -3087,15 +3086,27 @@ __metadata: zod: "npm:^3.22.4" zod-validation-error: "npm:^3.4.0" peerDependencies: - "@rspack/core": ^1.2.8 - "@rspack/dev-server": ^1.0.9 - "@rspack/plugin-react-refresh": ^1.0.0 + "@module-federation/enhanced": ^0.9.0 + "@pmmmwh/react-refresh-webpack-plugin": ^0.5.7 + esbuild-loader: ^4.0.0 + mini-css-extract-plugin: ^2.4.2 + terser-webpack-plugin: ^5.1.3 + webpack: ~5.96.0 + webpack-dev-server: ^5.0.0 peerDependenciesMeta: - "@rspack/core": + "@module-federation/enhanced": optional: true - "@rspack/dev-server": + "@pmmmwh/react-refresh-webpack-plugin": optional: true - "@rspack/plugin-react-refresh": + esbuild-loader: + optional: true + mini-css-extract-plugin: + optional: true + terser-webpack-plugin: + optional: true + webpack: + optional: true + webpack-dev-server: optional: true bin: backstage-cli: bin/backstage-cli @@ -16828,7 +16839,7 @@ __metadata: languageName: node linkType: hard -"@rspack/core@npm:^1.3.9": +"@rspack/core@npm:^1.4.11": version: 1.4.11 resolution: "@rspack/core@npm:1.4.11" dependencies: @@ -16844,7 +16855,7 @@ __metadata: languageName: node linkType: hard -"@rspack/dev-server@npm:^1.1.1": +"@rspack/dev-server@npm:^1.1.4": version: 1.1.4 resolution: "@rspack/dev-server@npm:1.1.4" dependencies: @@ -16866,7 +16877,7 @@ __metadata: languageName: node linkType: hard -"@rspack/plugin-react-refresh@npm:^1.4.2": +"@rspack/plugin-react-refresh@npm:^1.4.3": version: 1.4.3 resolution: "@rspack/plugin-react-refresh@npm:1.4.3" dependencies: @@ -18867,17 +18878,6 @@ __metadata: languageName: node linkType: hard -"@sucrase/webpack-loader@npm:^2.0.0": - version: 2.0.0 - resolution: "@sucrase/webpack-loader@npm:2.0.0" - dependencies: - loader-utils: "npm:^1.1.0" - peerDependencies: - sucrase: ^3 - checksum: 10/16578991b1b888ac5bec5628bd24db9e21651bbbe30de076aece8787f115d8971ac87a20bc75446187c73c3185851ec2233d5b6f18c4a2dd53fbbb1ed4e488b4 - languageName: node - linkType: hard - "@swagger-api/apidom-ast@npm:^1.0.0-beta.11": version: 1.0.0-beta.11 resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.11" @@ -49465,7 +49465,7 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5, webpack@npm:^5, webpack@npm:^5.94.0": +"webpack@npm:5, webpack@npm:^5, webpack@npm:~5.96.0": version: 5.96.1 resolution: "webpack@npm:5.96.1" dependencies: