From 29afb43f0f2d9464060592d9acacad577f88809d Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 5 Nov 2024 16:55:40 +0000 Subject: [PATCH 1/9] feat(rspack): move logic for withWeb to applyWebConfig and bring in line with webpack --- .../packages/rspack/executors/rspack.json | 10 +- packages/rspack/.eslintrc.json | 3 +- packages/rspack/package.json | 19 +- .../rspack/src/executors/rspack/schema.d.ts | 19 +- .../rspack/src/executors/rspack/schema.json | 10 +- .../src/plugins/utils/apply-web-config.ts | 459 ++++++++++++++++++ .../utils/get-css-module-local-ident.ts | 31 ++ .../rspack/src/plugins/utils/hash-format.ts | 26 + .../utils/instantiate-script-plugins.ts | 55 +++ .../utils/loaders/stylesheet-loaders.ts | 145 ++++++ packages/rspack/src/plugins/utils/models.ts | 256 ++++++++++ .../src/plugins/utils/normalize-entry.ts | 30 ++ .../utils/plugins/postcss-cli-resources.ts | 198 ++++++++ .../utils/plugins/scripts-rspack-plugin.ts | 127 +++++ packages/rspack/src/utils/config.ts | 88 +++- packages/rspack/src/utils/generator-utils.ts | 1 + packages/rspack/src/utils/model.ts | 22 +- .../with-module-federation-ssr.ts | 4 +- .../with-module-federation.ts | 4 +- .../rspack/src/utils/read-rspack-options.ts | 6 + packages/rspack/src/utils/with-nx.ts | 4 +- packages/rspack/src/utils/with-react.ts | 4 +- packages/rspack/src/utils/with-web.ts | 150 ++---- 23 files changed, 1513 insertions(+), 158 deletions(-) create mode 100644 packages/rspack/src/plugins/utils/apply-web-config.ts create mode 100644 packages/rspack/src/plugins/utils/get-css-module-local-ident.ts create mode 100644 packages/rspack/src/plugins/utils/hash-format.ts create mode 100644 packages/rspack/src/plugins/utils/instantiate-script-plugins.ts create mode 100644 packages/rspack/src/plugins/utils/loaders/stylesheet-loaders.ts create mode 100644 packages/rspack/src/plugins/utils/models.ts create mode 100644 packages/rspack/src/plugins/utils/normalize-entry.ts create mode 100644 packages/rspack/src/plugins/utils/plugins/postcss-cli-resources.ts create mode 100644 packages/rspack/src/plugins/utils/plugins/scripts-rspack-plugin.ts diff --git a/docs/generated/packages/rspack/executors/rspack.json b/docs/generated/packages/rspack/executors/rspack.json index b5ef6176cbd7f..3a8bc36d6dbfb 100644 --- a/docs/generated/packages/rspack/executors/rspack.json +++ b/docs/generated/packages/rspack/executors/rspack.json @@ -26,15 +26,13 @@ "type": "string", "description": "The tsconfig file to build the project." }, - "typeCheck": { + "skipTypeChecking": { + "alias": "typeCheck", "type": "boolean", - "description": "Skip the type checking." - }, - "indexHtml": { - "type": "string", - "description": "The path to the index.html file." + "description": "Skip the type checking. Default is `false`." }, "index": { + "alias": "indexHtml", "type": "string", "description": "HTML File which will be contain the application.", "x-completion-type": "file", diff --git a/packages/rspack/.eslintrc.json b/packages/rspack/.eslintrc.json index 0b0be7016299b..86f10996231c6 100644 --- a/packages/rspack/.eslintrc.json +++ b/packages/rspack/.eslintrc.json @@ -39,7 +39,8 @@ "@nx/workspace", // Imported types only "@module-federation/sdk", - "@module-federation/enhanced" + "@module-federation/enhanced", + "css-loader" ] } ] diff --git a/packages/rspack/package.json b/packages/rspack/package.json index bfa6709c491ed..12e6f7fed0622 100644 --- a/packages/rspack/package.json +++ b/packages/rspack/package.json @@ -28,20 +28,27 @@ "@nx/devkit": "file:../devkit", "@nx/web": "file:../web", "@phenomnomnominal/tsquery": "~5.0.1", + "@rspack/core": "^1.0.4", + "@rspack/dev-server": "^1.0.4", + "@rspack/plugin-react-refresh": "^1.0.0", + "autoprefixer": "^10.4.9", + "chalk": "~4.1.0", + "css-loader": "^6.4.0", "enquirer": "~2.3.6", "express": "^4.19.2", "http-proxy-middleware": "^3.0.3", "less-loader": "11.1.0", "license-webpack-plugin": "^4.0.2", + "loader-utils": "^2.0.3", + "sass": "^1.42.1", "sass-loader": "^12.2.0", - "stylus-loader": "^7.1.0", + "style-loader": "^3.3.0", + "postcss-import": "~14.1.0", "postcss-loader": "^8.1.1", - "@rspack/core": "^1.0.4", - "@rspack/dev-server": "^1.0.4", - "@rspack/plugin-react-refresh": "^1.0.0", - "chalk": "~4.1.0", + "postcss": "^8.4.38", "tsconfig-paths": "^4.1.2", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "webpack-subresource-integrity": "^5.1.0" }, "peerDependencies": { "@module-federation/enhanced": "~0.6.0", diff --git a/packages/rspack/src/executors/rspack/schema.d.ts b/packages/rspack/src/executors/rspack/schema.d.ts index 0aa8ef19f7367..4256c33711f2d 100644 --- a/packages/rspack/src/executors/rspack/schema.d.ts +++ b/packages/rspack/src/executors/rspack/schema.d.ts @@ -6,8 +6,10 @@ export interface RspackExecutorSchema { index?: string; tsConfig?: string; typeCheck?: boolean; + skipTypeChecking?: boolean; outputPath?: string; outputFileName?: string; + index?: string; indexHtml?: string; mode?: Mode; watch?: boolean; @@ -16,13 +18,20 @@ export interface RspackExecutorSchema { rspackConfig: string; optimization?: boolean | OptimizationOptions; - sourceMap?: boolean | string; + sourceMap?: boolean | 'hidden'; assets?: any[]; extractLicenses?: boolean; fileReplacements?: FileReplacement[]; generatePackageJson?: boolean; } +export interface AssetGlobPattern { + glob: string; + input: string; + output: string; + ignore?: string[]; +} + export interface FileReplacement { replace: string; with: string; @@ -32,3 +41,11 @@ export interface OptimizationOptions { scripts: boolean; styles: boolean; } + +export interface NormalizedRspackExecutorSchema extends RspackExecutorSchema { + outputFileName: string; + assets: AssetGlobPattern[]; + root: string; + projectRoot: string; + sourceRoot: string; +} diff --git a/packages/rspack/src/executors/rspack/schema.json b/packages/rspack/src/executors/rspack/schema.json index d711a8f12427e..9d2410269b22a 100644 --- a/packages/rspack/src/executors/rspack/schema.json +++ b/packages/rspack/src/executors/rspack/schema.json @@ -26,15 +26,13 @@ "type": "string", "description": "The tsconfig file to build the project." }, - "typeCheck": { + "skipTypeChecking": { + "alias": "typeCheck", "type": "boolean", - "description": "Skip the type checking." - }, - "indexHtml": { - "type": "string", - "description": "The path to the index.html file." + "description": "Skip the type checking. Default is `false`." }, "index": { + "alias": "indexHtml", "type": "string", "description": "HTML File which will be contain the application.", "x-completion-type": "file", diff --git a/packages/rspack/src/plugins/utils/apply-web-config.ts b/packages/rspack/src/plugins/utils/apply-web-config.ts new file mode 100644 index 0000000000000..fce9de94706b3 --- /dev/null +++ b/packages/rspack/src/plugins/utils/apply-web-config.ts @@ -0,0 +1,459 @@ +import { + type RspackPluginInstance, + type Configuration, + type RuleSetRule, + LightningCssMinimizerRspackPlugin, + DefinePlugin, + HtmlRspackPlugin, + CssExtractRspackPlugin, + EnvironmentPlugin, +} from '@rspack/core'; +import { instantiateScriptPlugins } from './instantiate-script-plugins'; +import path, { join } from 'path'; +import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity'; +import { getOutputHashFormat } from './hash-format'; +import { normalizeExtraEntryPoints } from './normalize-entry'; +import { + getCommonLoadersForCssModules, + getCommonLoadersForGlobalCss, + getCommonLoadersForGlobalStyle, +} from './loaders/stylesheet-loaders'; +import { NormalizedNxAppRspackPluginOptions } from './models'; + +export function applyWebConfig( + options: NormalizedNxAppRspackPluginOptions, + config: Configuration = {}, + { + useNormalizedEntry, + }: { + // rspack.Configuration allows arrays to be set on a single entry + // rspack then normalizes them to { import: "..." } objects + // This option allows use to preserve existing composePlugins behavior where entry.main is an array. + useNormalizedEntry?: boolean; + } = {} +): void { + if (!process.env['NX_TASK_TARGET_PROJECT']) return; + + // Defaults that was applied from executor schema previously. + options.runtimeChunk ??= true; // need this for HMR and other things to work + options.extractCss ??= true; + options.generateIndexHtml ??= true; + options.index = options.index + ? join(options.root, options.index) + : join( + options.root, + options.projectGraph.nodes[options.projectName].data.sourceRoot, + 'index.html' + ); + options.styles ??= []; + options.scripts ??= []; + + const isProd = + process.env.NODE_ENV === 'production' || options.mode === 'production'; + + const plugins: RspackPluginInstance[] = [ + new EnvironmentPlugin({ + NODE_ENV: isProd ? 'production' : 'development', + }), + ]; + + const stylesOptimization = + typeof options.optimization === 'object' + ? options.optimization.styles + : options.optimization; + + if (Array.isArray(options.scripts)) { + plugins.push(...instantiateScriptPlugins(options)); + } + if (options.index && options.generateIndexHtml) { + plugins.push( + new HtmlRspackPlugin({ + template: options.index, + sri: options.subresourceIntegrity ? 'sha256' : undefined, + ...(options.baseHref ? { base: { href: options.baseHref } } : {}), + }) + ); + } + + if (options.subresourceIntegrity) { + plugins.push(new SubresourceIntegrityPlugin() as any); + } + + const minimizer: RspackPluginInstance[] = []; + if (stylesOptimization) { + minimizer.push( + new LightningCssMinimizerRspackPlugin({ + test: /\.(?:css|scss|sass|less|styl)$/, + }) + ); + } + if (!options.ssr) { + plugins.push( + new DefinePlugin(getClientEnvironment(process.env.NODE_ENV).stringified) + ); + } + + const entries: { [key: string]: { import: string[] } } = {}; + const globalStylePaths: string[] = []; + + // Determine hashing format. + const hashFormat = getOutputHashFormat(options.outputHashing as string); + + const includePaths: string[] = []; + if (options?.stylePreprocessorOptions?.includePaths?.length > 0) { + options.stylePreprocessorOptions.includePaths.forEach( + (includePath: string) => + includePaths.push(path.resolve(options.root, includePath)) + ); + } + + let lessPathOptions: { paths?: string[] } = {}; + + if (includePaths.length > 0) { + lessPathOptions = { + paths: includePaths, + }; + } + + // Process global styles. + if (options.styles.length > 0) { + normalizeExtraEntryPoints(options.styles, 'styles').forEach((style) => { + const resolvedPath = style.input.startsWith('.') + ? style.input + : path.resolve(options.root, style.input); + // Add style entry points. + if (entries[style.bundleName]) { + entries[style.bundleName].import.push(resolvedPath); + } else { + entries[style.bundleName] = { import: [resolvedPath] }; + } + + // Add global css paths. + globalStylePaths.push(resolvedPath); + }); + } + + const cssModuleRules: RuleSetRule[] = [ + { + test: /\.module\.css$/, + exclude: globalStylePaths, + use: getCommonLoadersForCssModules(options, includePaths), + }, + { + test: /\.module\.(scss|sass)$/, + exclude: globalStylePaths, + use: [ + ...getCommonLoadersForCssModules(options, includePaths), + { + loader: require.resolve('sass-loader'), + options: { + implementation: require('sass'), + sassOptions: { + fiber: false, + precision: 8, + includePaths, + }, + }, + }, + ], + }, + { + test: /\.module\.less$/, + exclude: globalStylePaths, + use: [ + ...getCommonLoadersForCssModules(options, includePaths), + { + loader: require.resolve('less-loader'), + options: { + lessOptions: { + paths: includePaths, + }, + }, + }, + ], + }, + { + test: /\.module\.styl$/, + exclude: globalStylePaths, + use: [ + ...getCommonLoadersForCssModules(options, includePaths), + { + loader: path.join( + __dirname, + '../../../utils/webpack/deprecated-stylus-loader.js' + ), + options: { + stylusOptions: { + include: includePaths, + }, + }, + }, + ], + }, + ]; + + const globalCssRules: RuleSetRule[] = [ + { + test: /\.css$/, + exclude: globalStylePaths, + use: getCommonLoadersForGlobalCss(options, includePaths), + }, + { + test: /\.scss$|\.sass$/, + exclude: globalStylePaths, + use: [ + ...getCommonLoadersForGlobalCss(options, includePaths), + { + loader: require.resolve('sass-loader'), + options: { + implementation: require('sass'), + sourceMap: !!options.sourceMap, + sassOptions: { + fiber: false, + // bootstrap-sass requires a minimum precision of 8 + precision: 8, + includePaths, + }, + }, + }, + ], + }, + { + test: /\.less$/, + exclude: globalStylePaths, + use: [ + ...getCommonLoadersForGlobalCss(options, includePaths), + { + loader: require.resolve('less-loader'), + options: { + sourceMap: !!options.sourceMap, + lessOptions: { + javascriptEnabled: true, + ...lessPathOptions, + }, + }, + }, + ], + }, + { + test: /\.styl$/, + exclude: globalStylePaths, + use: [ + ...getCommonLoadersForGlobalCss(options, includePaths), + { + loader: path.join( + __dirname, + '../../../utils/webpack/deprecated-stylus-loader.js' + ), + options: { + sourceMap: !!options.sourceMap, + stylusOptions: { + include: includePaths, + }, + }, + }, + ], + }, + ]; + + const globalStyleRules: RuleSetRule[] = [ + { + test: /\.css$/, + include: globalStylePaths, + use: getCommonLoadersForGlobalStyle(options, includePaths), + }, + { + test: /\.scss$|\.sass$/, + include: globalStylePaths, + use: [ + ...getCommonLoadersForGlobalStyle(options, includePaths), + { + loader: require.resolve('sass-loader'), + options: { + implementation: require('sass'), + sourceMap: !!options.sourceMap, + sassOptions: { + fiber: false, + // bootstrap-sass requires a minimum precision of 8 + precision: 8, + includePaths, + }, + }, + }, + ], + }, + { + test: /\.less$/, + include: globalStylePaths, + use: [ + ...getCommonLoadersForGlobalStyle(options, includePaths), + { + loader: require.resolve('less-loader'), + options: { + sourceMap: !!options.sourceMap, + lessOptions: { + javascriptEnabled: true, + ...lessPathOptions, + }, + }, + }, + ], + }, + { + test: /\.styl$/, + include: globalStylePaths, + use: [ + ...getCommonLoadersForGlobalStyle(options, includePaths), + { + loader: path.join( + __dirname, + '../../../utils/webpack/deprecated-stylus-loader.js' + ), + options: { + sourceMap: !!options.sourceMap, + stylusOptions: { + include: includePaths, + }, + }, + }, + ], + }, + ]; + + const rules: RuleSetRule[] = [ + { + test: /\.css$|\.scss$|\.sass$|\.less$|\.styl$/, + oneOf: [...cssModuleRules, ...globalCssRules, ...globalStyleRules], + }, + ]; + + if (options.extractCss) { + plugins.push( + // extract global css from js files into own css file + new CssExtractRspackPlugin({ + filename: `[name]${hashFormat.extract}.css`, + }) + ); + } + + config.output = { + ...(config.output ?? {}), + assetModuleFilename: '[name].[contenthash:20][ext]', + crossOriginLoading: options.subresourceIntegrity + ? ('anonymous' as const) + : (false as const), + }; + + // In case users customize their webpack config with unsupported entry. + if (typeof config.entry === 'function') + throw new Error('Entry function is not supported. Use an object.'); + if (typeof config.entry === 'string') + throw new Error('Entry string is not supported. Use an object.'); + if (Array.isArray(config.entry)) + throw new Error('Entry array is not supported. Use an object.'); + + Object.entries(entries).forEach(([entryName, entryData]) => { + if (useNormalizedEntry) { + config.entry[entryName] = { import: entryData.import }; + } else { + config.entry[entryName] = entryData.import; + } + }); + + config.optimization = { + ...(config.optimization ?? {}), + minimizer: [...(config.optimization?.minimizer ?? []), ...minimizer], + emitOnErrors: false, + moduleIds: 'deterministic' as const, + runtimeChunk: options.runtimeChunk ? { name: 'runtime' } : false, + splitChunks: { + defaultSizeTypes: + config.optimization?.splitChunks !== false + ? config.optimization?.splitChunks?.defaultSizeTypes + : ['...'], + maxAsyncRequests: Infinity, + cacheGroups: { + default: !!options.commonChunk && { + chunks: 'async' as const, + minChunks: 2, + priority: 10, + }, + common: !!options.commonChunk && { + name: 'common', + chunks: 'async' as const, + minChunks: 2, + enforce: true, + priority: 5, + }, + vendors: false as const, + vendor: !!options.vendorChunk && { + name: 'vendor', + chunks: (chunk) => chunk.name === 'main', + enforce: true, + test: /[\\/]node_modules[\\/]/, + }, + }, + }, + }; + + config.resolve.mainFields = ['browser', 'module', 'main']; + + config.module = { + ...(config.module ?? {}), + rules: [ + ...(config.module.rules ?? []), + // Images: Inline small images, and emit a separate file otherwise. + { + test: /\.(avif|bmp|gif|ico|jpe?g|png|webp)$/, + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: 10_000, // 10 kB + }, + }, + }, + // SVG: same as image but we need to separate it so it can be swapped for SVGR in the React plugin. + { + test: /\.svg$/, + type: 'asset', + parser: { + dataUrlCondition: { + maxSize: 10_000, // 10 kB + }, + }, + }, + // Fonts: Emit separate file and export the URL. + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + type: 'asset/resource', + }, + ...rules, + ], + }; + + config.plugins ??= []; + config.plugins.push(...plugins); +} + +function getClientEnvironment(mode?: string) { + // Grab NODE_ENV and NX_PUBLIC_* environment variables and prepare them to be + // injected into the application via DefinePlugin in webpack configuration. + const nxPublicKeyRegex = /^NX_PUBLIC_/i; + + const raw = Object.keys(process.env) + .filter((key) => nxPublicKeyRegex.test(key)) + .reduce((env, key) => { + env[key] = process.env[key]; + return env; + }, {}); + + // Stringify all values so we can feed into webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { stringified }; +} diff --git a/packages/rspack/src/plugins/utils/get-css-module-local-ident.ts b/packages/rspack/src/plugins/utils/get-css-module-local-ident.ts new file mode 100644 index 0000000000000..4dd71dc490b95 --- /dev/null +++ b/packages/rspack/src/plugins/utils/get-css-module-local-ident.ts @@ -0,0 +1,31 @@ +import { posix } from 'path'; +import { getHashDigest, interpolateName } from 'loader-utils'; + +export function getCSSModuleLocalIdent( + ctx, + localIdentName, + localName, + options +) { + // Use the filename or folder name, based on some uses the index.js / index.module.(css|scss|sass) project style + const fileNameOrFolder = ctx.resourcePath.match( + /index\.module\.(css|scss|sass)$/ + ) + ? '[folder]' + : '[name]'; + // Create a hash based on a the file location and class name. Will be unique across a project, and close to globally unique. + const hash = getHashDigest( + posix.relative(ctx.rootContext, ctx.resourcePath) + localName, + 'md5', + 'base64', + 5 + ); + // Use loaderUtils to find the file or folder name + const className = interpolateName( + ctx, + `${fileNameOrFolder}_${localName}__${hash}`, + options + ); + // Remove the .module that appears in every classname when based on the file and replace all "." with "_". + return className.replace('.module_', '_').replace(/\./g, '_'); +} diff --git a/packages/rspack/src/plugins/utils/hash-format.ts b/packages/rspack/src/plugins/utils/hash-format.ts new file mode 100644 index 0000000000000..f3693f67d15bb --- /dev/null +++ b/packages/rspack/src/plugins/utils/hash-format.ts @@ -0,0 +1,26 @@ +export interface HashFormat { + chunk: string; + extract: string; + file: string; + script: string; +} + +export function getOutputHashFormat(option: string, length = 20): HashFormat { + const hashFormats: { [option: string]: HashFormat } = { + none: { chunk: '', extract: '', file: '', script: '' }, + media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' }, + bundles: { + chunk: `.[chunkhash:${length}]`, + extract: `.[contenthash:${length}]`, + file: '', + script: `.[contenthash:${length}]`, + }, + all: { + chunk: `.[chunkhash:${length}]`, + extract: `.[contenthash:${length}]`, + file: `.[contenthash:${length}]`, + script: `.[contenthash:${length}]`, + }, + }; + return hashFormats[option] || hashFormats['none']; +} diff --git a/packages/rspack/src/plugins/utils/instantiate-script-plugins.ts b/packages/rspack/src/plugins/utils/instantiate-script-plugins.ts new file mode 100644 index 0000000000000..8a1101a31b5ea --- /dev/null +++ b/packages/rspack/src/plugins/utils/instantiate-script-plugins.ts @@ -0,0 +1,55 @@ +import * as path from 'path'; +import type { RspackPluginInstance } from '@rspack/core'; + +import { getOutputHashFormat } from './hash-format'; +import { ScriptsRspackPlugin } from './plugins/scripts-rspack-plugin'; +import { normalizeExtraEntryPoints } from './normalize-entry'; + +export function instantiateScriptPlugins(options: any): RspackPluginInstance[] { + // process global scripts + const globalScriptsByBundleName = normalizeExtraEntryPoints( + options.scripts || [], + 'scripts' + ).reduce( + ( + prev: { inject: boolean; bundleName: string; paths: string[] }[], + curr + ) => { + const bundleName = curr.bundleName; + const resolvedPath = path.resolve(options.root, curr.input); + const existingEntry = prev.find((el) => el.bundleName === bundleName); + if (existingEntry) { + existingEntry.paths.push(resolvedPath); + } else { + prev.push({ + inject: curr.inject, + bundleName, + paths: [resolvedPath], + }); + } + + return prev; + }, + [] + ); + + const hashFormat = getOutputHashFormat(options.outputHashing as string); + const plugins = []; + // Add a new asset for each entry. + globalScriptsByBundleName.forEach((script) => { + const hash = script.inject ? hashFormat.script : ''; + const bundleName = script.bundleName; + + plugins.push( + new ScriptsRspackPlugin({ + name: bundleName, + sourceMap: !!options.sourceMap, + filename: `${path.basename(bundleName)}${hash}.js`, + scripts: script.paths, + basePath: options.sourceRoot, + }) + ); + }); + + return plugins; +} diff --git a/packages/rspack/src/plugins/utils/loaders/stylesheet-loaders.ts b/packages/rspack/src/plugins/utils/loaders/stylesheet-loaders.ts new file mode 100644 index 0000000000000..603233c8e3457 --- /dev/null +++ b/packages/rspack/src/plugins/utils/loaders/stylesheet-loaders.ts @@ -0,0 +1,145 @@ +import * as path from 'path'; +import autoprefixer = require('autoprefixer'); +import postcssImports = require('postcss-import'); +import { CssExtractRspackPlugin } from '@rspack/core'; + +import { getCSSModuleLocalIdent } from '../get-css-module-local-ident'; +import { getOutputHashFormat } from '../hash-format'; +import { PostcssCliResources } from '../plugins/postcss-cli-resources'; + +interface PostcssOptions { + (loader: any): any; + + config?: string; +} + +export function getCommonLoadersForCssModules( + options: any, + includePaths: string[] +) { + // load component css as raw strings + return [ + { + loader: options.extractCss + ? CssExtractRspackPlugin.loader + : require.resolve('style-loader'), + }, + { + loader: require.resolve('css-loader'), + options: { + modules: { + mode: 'local', + getLocalIdent: getCSSModuleLocalIdent, + }, + importLoaders: 1, + }, + }, + { + loader: require.resolve('postcss-loader'), + options: { + implementation: require('postcss'), + postcssOptions: postcssOptionsCreator(options, { + includePaths, + forCssModules: true, + }), + }, + }, + ]; +} + +export function getCommonLoadersForGlobalCss( + options: any, + includePaths: string[] +) { + return [ + { + loader: options.extractCss + ? CssExtractRspackPlugin.loader + : require.resolve('style-loader'), + }, + { loader: require.resolve('css-loader'), options: { url: false } }, + { + loader: require.resolve('postcss-loader'), + options: { + implementation: require('postcss'), + postcssOptions: postcssOptionsCreator(options, { + includePaths, + }), + }, + }, + ]; +} + +export function getCommonLoadersForGlobalStyle( + options: any, + includePaths: string[] +) { + return [ + { + loader: options.extractCss + ? CssExtractRspackPlugin.loader + : require.resolve('style-loader'), + options: { esModule: true }, + }, + { loader: require.resolve('css-loader'), options: { url: false } }, + { + loader: require.resolve('postcss-loader'), + options: { + implementation: require('postcss'), + postcssOptions: postcssOptionsCreator(options, { + includePaths, + }), + }, + }, + ]; +} + +function postcssOptionsCreator( + options: any, + { + includePaths, + forCssModules = false, + }: { + includePaths: string[]; + forCssModules?: boolean; + } +) { + const hashFormat = getOutputHashFormat(options.outputHashing as string); + // PostCSS options depend on the webpack loader, but we need to set the `config` path as a string due to this check: + // https://github.com/webpack-contrib/postcss-loader/blob/0d342b1/src/utils.js#L36 + + const postcssOptions: PostcssOptions = (loader) => ({ + map: options.sourceMap && + options.sourceMap !== 'hidden' && { + inline: true, + annotation: false, + }, + plugins: [ + postcssImports({ + addModulesDirectories: includePaths, + resolve: (url: string) => (url.startsWith('~') ? url.slice(1) : url), + }), + ...(forCssModules + ? [] + : [ + PostcssCliResources({ + baseHref: options.baseHref, + deployUrl: options.deployUrl, + loader, + filename: `[name]${hashFormat.file}.[ext]`, + publicPath: options.publicPath, + rebaseRootRelative: options.rebaseRootRelative, + }), + autoprefixer(), + ]), + ], + }); + + // If a path to postcssConfig is passed in, set it for app and all libs, otherwise + // use automatic detection. + if (typeof options.postcssConfig === 'string') { + postcssOptions.config = path.join(options.root, options.postcssConfig); + } + + return postcssOptions; +} diff --git a/packages/rspack/src/plugins/utils/models.ts b/packages/rspack/src/plugins/utils/models.ts new file mode 100644 index 0000000000000..ca086f9ab51c6 --- /dev/null +++ b/packages/rspack/src/plugins/utils/models.ts @@ -0,0 +1,256 @@ +import type { Mode } from '@rspack/core'; +import type { ProjectGraph } from '@nx/devkit'; +import type { AssetGlob } from '@nx/js/src/utils/assets/assets'; + +export interface AssetGlobPattern { + glob: string; + input: string; + output: string; + ignore?: string[]; +} + +export interface ExtraEntryPointClass { + bundleName?: string; + inject?: boolean; + input: string; + lazy?: boolean; +} + +export interface FileReplacement { + replace: string; + with: string; +} + +export interface AdditionalEntryPoint { + entryName: string; + entryPath: string; +} + +export interface TransformerPlugin { + name: string; + options: Record; +} + +export type TransformerEntry = string | TransformerPlugin; + +export interface OptimizationOptions { + scripts: boolean; + styles: boolean; +} + +export interface NxAppRspackPluginOptions { + /** + * The tsconfig file for the project. e.g. `tsconfig.json` + */ + tsConfig?: string; + /** + * The entry point for the bundle. e.g. `src/main.ts` + */ + main?: string; + /** + * Secondary entry points for the bundle. + */ + additionalEntryPoints?: AdditionalEntryPoint[]; + /** + * Assets to be copied over to the output path. + */ + assets?: Array; + /** + * Babel configuration file if compiler is babel. + */ + babelConfig?: string; + /** + * If true, Babel will look for a babel.config.json up the directory tree. + */ + babelUpwardRootMode?: boolean; + /** + * Set for the resulting index.html. + */ + baseHref?: string; + /** + * Build the libraries from source. Default is `true`. + */ + buildLibsFromSource?: boolean; + + commonChunk?: boolean; + /** + * The compiler to use. Default is `babel` and requires a `.babelrc` file. + */ + compiler?: 'babel' | 'swc' | 'tsc'; + /** + * Set `crossorigin` attribute on the `script` and `link` tags. + */ + crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; + /** + * Delete the output path before building. + */ + deleteOutputPath?: boolean; + /** + * The deploy path for the application. e.g. `/my-app/` + */ + deployUrl?: string; + /** + * Define external packages that will not be bundled. + * Use `all` to exclude all 3rd party packages, and `none` to bundle all packages. + * Use an array to exclude specific packages from the bundle. + * Default is `none`. + */ + externalDependencies?: 'all' | 'none' | string[]; + /** + * Extract CSS as an external file. Default is `true`. + */ + extractCss?: boolean; + /** + * Extract licenses from 3rd party modules and add them to the output. + */ + extractLicenses?: boolean; + /** + * Replace files at build time. e.g. `[{ "replace": "src/a.dev.ts", "with": "src/a.prod.ts" }]` + */ + fileReplacements?: FileReplacement[]; + /** + * Generate an `index.html` file if `index.html` is passed. Default is `true` + */ + generateIndexHtml?: boolean; + /** + * Generate a `package.json` file for the bundle. Useful for Node applications. + */ + generatePackageJson?: boolean; + /** + * Path to the `index.html`. + */ + index?: string; + /** + * Mode to run the build in. + */ + mode?: Mode; + /** + * Set the memory limit for the type-checking process. Default is `2048`. + */ + memoryLimit?: number; + /** + * Use the source file name in output chunks. Useful for development or for Node. + */ + namedChunks?: boolean; + /** + * Optimize the bundle using Terser. + */ + optimization?: boolean | OptimizationOptions; + /** + * Specify the output filename for the bundle. Useful for Node applications that use `@nx/js:node` to serve. + */ + outputFileName?: string; + /** + * Use file hashes in the output filenames. Recommended for production web applications. + */ + outputHashing?: any; + /** + * Override `output.path` in webpack configuration. This setting is not recommended and exists for backwards compatibility. + */ + outputPath?: string; + /** + * Override `watchOptions.poll` in webpack configuration. This setting is not recommended and exists for backwards compatibility. + */ + poll?: number; + /** + * The polyfill file to use. Useful for supporting legacy browsers. e.g. `src/polyfills.ts` + */ + polyfills?: string; + /** + * Manually set the PostCSS configuration file. By default, PostCSS will look for `postcss.config.js` in the directory. + */ + postcssConfig?: string; + /** + * Display build progress in the terminal. + */ + progress?: boolean; + /** + * Add an additional chunk for the Webpack runtime. Defaults to `true` when `target === 'web'`. + */ + runtimeChunk?: boolean; + /** + * External scripts that will be included before the main application entry. + */ + scripts?: Array; + /** + * Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option. + */ + skipOverrides?: boolean; + /** + * Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option. + */ + skipPackageManager?: boolean; + /** + * Skip type checking. Default is `false`. + */ + skipTypeChecking?: boolean; + /** + * Skip type checking. Default is `false`. + */ + typeCheck?: boolean; + /** + * Generate source maps. + */ + sourceMap?: boolean | 'hidden'; + /** + * When `true`, `process.env.NODE_ENV` will be excluded from the bundle. Useful for building a web application to run in a Node environment. + */ + ssr?: boolean; + /** + * Generate a `stats.json` file which can be analyzed using tools such as `webpack-bundle-analyzer`. + */ + statsJson?: boolean; + /** + * Options for the style preprocessor. e.g. `{ "includePaths": [] }` for SASS. + */ + stylePreprocessorOptions?: any; + /** + * External stylesheets that will be included with the application. + */ + styles?: Array; + /** + * Enables the use of subresource integrity validation. + */ + subresourceIntegrity?: boolean; + /** + * Override the `target` option in webpack configuration. This setting is not recommended and exists for backwards compatibility. + */ + target?: string | string[]; + /** + * List of TypeScript Compiler Transformers Plugins. + */ + transformers?: TransformerEntry[]; + /** + * Generate a separate vendor chunk for 3rd party packages. + */ + vendorChunk?: boolean; + /** + * Log additional information for debugging purposes. + */ + verbose?: boolean; + /** + * Watch for file changes. + */ + watch?: boolean; + /** + * Set a public path for assets resources with absolute paths. + */ + publicPath?: string; + /** + * Whether to rebase absolute path for assets in postcss cli resources. + */ + rebaseRootRelative?: boolean; +} + +export interface NormalizedNxAppRspackPluginOptions + extends NxAppRspackPluginOptions { + projectName: string; + root: string; + projectRoot: string; + sourceRoot: string; + configurationName: string; + targetName: string; + projectGraph: ProjectGraph; + outputFileName: string; + assets: AssetGlobPattern[]; +} diff --git a/packages/rspack/src/plugins/utils/normalize-entry.ts b/packages/rspack/src/plugins/utils/normalize-entry.ts new file mode 100644 index 0000000000000..d2a6d12548b4a --- /dev/null +++ b/packages/rspack/src/plugins/utils/normalize-entry.ts @@ -0,0 +1,30 @@ +import { ExtraEntryPoint, NormalizedEntryPoint } from '../../utils/model'; + +export function normalizeExtraEntryPoints( + extraEntryPoints: ExtraEntryPoint[], + defaultBundleName: string +): NormalizedEntryPoint[] { + return extraEntryPoints.map((entry) => { + let normalizedEntry; + if (typeof entry === 'string') { + normalizedEntry = { + input: entry, + inject: true, + bundleName: defaultBundleName, + }; + } else { + const { inject = true, ...newEntry } = entry; + let bundleName; + + if (entry.bundleName) { + bundleName = entry.bundleName; + } else { + bundleName = defaultBundleName; + } + + normalizedEntry = { ...newEntry, bundleName }; + } + + return normalizedEntry; + }); +} diff --git a/packages/rspack/src/plugins/utils/plugins/postcss-cli-resources.ts b/packages/rspack/src/plugins/utils/plugins/postcss-cli-resources.ts new file mode 100644 index 0000000000000..c790d285ea29a --- /dev/null +++ b/packages/rspack/src/plugins/utils/plugins/postcss-cli-resources.ts @@ -0,0 +1,198 @@ +import { interpolateName } from 'loader-utils'; +import * as path from 'path'; +import type { Declaration } from 'postcss'; +import * as url from 'node:url'; +import type { LoaderContext } from '@rspack/core'; + +function wrapUrl(url: string): string { + let wrappedUrl; + const hasSingleQuotes = url.indexOf("'") >= 0; + if (hasSingleQuotes) { + wrappedUrl = `"${url}"`; + } else { + wrappedUrl = `'${url}'`; + } + return `url(${wrappedUrl})`; +} + +export interface PostcssCliResourcesOptions { + baseHref?: string; + deployUrl?: string; + resourcesOutputPath?: string; + rebaseRootRelative?: boolean; + filename: string; + loader: LoaderContext; + publicPath: string; +} + +async function resolve( + file: string, + base: string, + resolver: (file: string, base: string) => Promise +): Promise { + try { + return await resolver(`./${file}`, base); + } catch { + return resolver(file, base); + } +} + +module.exports.postcss = true; + +export function PostcssCliResources(options: PostcssCliResourcesOptions) { + const { + deployUrl = '', + baseHref = '', + resourcesOutputPath = '', + rebaseRootRelative = false, + filename, + loader, + publicPath = '', + } = options; + const dedupeSlashes = (url: string) => url.replace(/\/\/+/g, '/'); + const process = async ( + inputUrl: string, + context: string, + resourceCache: Map + ) => { + // If root-relative, absolute or protocol relative url, leave as is + if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) { + return inputUrl; + } + if (!rebaseRootRelative && /^\//.test(inputUrl)) { + return inputUrl; + } + // If starts with a caret, remove and return remainder + // this supports bypassing asset processing + if (inputUrl.startsWith('^')) { + return inputUrl.slice(1); + } + const cacheKey = path.resolve(context, inputUrl); + const cachedUrl = resourceCache.get(cacheKey); + if (cachedUrl) { + return cachedUrl; + } + if (inputUrl.startsWith('~')) { + inputUrl = inputUrl.slice(1); + } + if (inputUrl.startsWith('/')) { + let outputUrl = ''; + if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) { + // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is. + outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`; + } else if (baseHref.match(/:\/\//)) { + // If baseHref contains a scheme, include it as is. + outputUrl = + baseHref.replace(/\/$/, '') + + dedupeSlashes(`/${deployUrl}/${inputUrl}`); + } else { + // Join together base-href, deploy-url and the original URL. + outputUrl = dedupeSlashes( + `/${baseHref}/${deployUrl}/${publicPath}/${inputUrl}` + ); + } + resourceCache.set(cacheKey, outputUrl); + return outputUrl; + } + const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, '/')); + const resolver = (file: string, base: string) => + new Promise((resolve, reject) => { + loader.resolve(base, decodeURI(file), (err, result) => { + if (err) { + reject(err); + return; + } + resolve(result); + }); + }); + const result = await resolve(pathname as string, context, resolver); + return new Promise((resolve, reject) => { + loader.fs.readFile(result as string, (err: Error, content: Buffer) => { + if (err) { + reject(err); + return; + } + + let outputPath = interpolateName( + { resourcePath: result } as LoaderContext, + filename, + { content } + ); + if (resourcesOutputPath) { + outputPath = path.posix.join(resourcesOutputPath, outputPath); + } + loader.addDependency(result as string); + loader.emitFile(outputPath, content, undefined); + let outputUrl = outputPath.replace(/\\/g, '/'); + if (hash || search) { + outputUrl = url.format({ pathname: outputUrl, hash, search }); + } + const loaderOptions: any = loader.loaders[loader.loaderIndex].options; + if (deployUrl && loaderOptions.ident !== 'extracted') { + outputUrl = url.resolve(deployUrl, outputUrl); + } + resourceCache.set(cacheKey, outputUrl); + resolve(outputUrl); + }); + }); + }; + return { + postcssPlugin: 'postcss-cli-resources', + Once(root) { + const urlDeclarations: Array = []; + /** + * TODO: Explore if this can be rewritten using the new `Declaration()` + * listener added in postcss v8 + */ + root.walkDecls((decl) => { + if (decl.value && decl.value.includes('url')) { + urlDeclarations.push(decl); + } + }); + if (urlDeclarations.length === 0) { + return; + } + const resourceCache = new Map(); + return Promise.all( + urlDeclarations.map(async (decl) => { + const value = decl.value; + const urlRegex = /url(?:\(\s*(['"]?))(.*?)(?:\1\s*\))/g; + const segments: string[] = []; + let match; + let lastIndex = 0; + let modified = false; + // We want to load it relative to the file that imports + const inputFile = decl.source && decl.source.input.file; + const context = + (inputFile && path.dirname(inputFile)) || loader.context; + while ((match = urlRegex.exec(value))) { + const originalUrl = match[2]; + let processedUrl; + try { + processedUrl = await process(originalUrl, context, resourceCache); + } catch (err) { + loader.emitError(decl.error(err.message, { word: originalUrl })); + continue; + } + if (lastIndex < match.index) { + segments.push(value.slice(lastIndex, match.index)); + } + if (!processedUrl || originalUrl === processedUrl) { + segments.push(match[0]); + } else { + segments.push(wrapUrl(processedUrl)); + modified = true; + } + lastIndex = match.index + match[0].length; + } + if (lastIndex < value.length) { + segments.push(value.slice(lastIndex)); + } + if (modified) { + decl.value = segments.join(''); + } + }) + ); + }, + }; +} diff --git a/packages/rspack/src/plugins/utils/plugins/scripts-rspack-plugin.ts b/packages/rspack/src/plugins/utils/plugins/scripts-rspack-plugin.ts new file mode 100644 index 0000000000000..a15158c317cc7 --- /dev/null +++ b/packages/rspack/src/plugins/utils/plugins/scripts-rspack-plugin.ts @@ -0,0 +1,127 @@ +import { interpolateName } from 'loader-utils'; +import * as path from 'path'; +import { + sources, + EntryPlugin, + type Compiler, + type Compilation, +} from '@rspack/core'; + +export interface ScriptsRspackPluginOptions { + name: string; + sourceMap: boolean; + scripts: string[]; + filename: string; + basePath: string; +} + +interface ScriptOutput { + filename: string; + source: sources.Source; +} + +function addDependencies(compilation: any, scripts: string[]): void { + for (const script of scripts) { + compilation.fileDependencies.add(script); + } +} + +function hook( + compiler: any, + action: (compilation: any, callback: (err?: Error) => void) => void +) { + compiler.hooks.thisCompilation.tap( + 'scripts-rspack-plugin', + (compilation: any) => { + compilation.hooks.additionalAssets.tapAsync( + 'scripts-rspack-plugin', + (callback: (err?: Error) => void) => action(compilation, callback) + ); + } + ); +} + +export class ScriptsRspackPlugin { + private _lastBuildTime?: number; + private _cachedOutput?: ScriptOutput; + + constructor(private options: Partial = {}) {} + + private _insertOutput( + compiler: Compiler, + compilation: Compilation, + { filename, source }: ScriptOutput, + cached = false + ) { + new EntryPlugin(compiler.context, this.options.name).apply(compiler); + + compilation.assets[filename] = source; + } + + apply(compiler: Compiler): void { + if (!this.options.scripts || this.options.scripts.length === 0) { + return; + } + + const scripts = this.options.scripts + .filter((script) => !!script) + .map((script) => path.resolve(this.options.basePath || '', script)); + + hook(compiler, (compilation: Compilation, callback) => { + const sourceGetters = scripts.map((fullPath) => { + return new Promise((resolve, reject) => { + compilation.inputFileSystem.readFile( + fullPath, + (err: Error, data: Buffer) => { + if (err) { + reject(err); + return; + } + + const content = data.toString(); + + let source; + if (this.options.sourceMap) { + // TODO: Look for source map file (for '.min' scripts, etc.) + + let adjustedPath = fullPath; + if (this.options.basePath) { + adjustedPath = path.relative(this.options.basePath, fullPath); + } + source = new sources.OriginalSource(content, adjustedPath); + } else { + source = new sources.RawSource(content); + } + + resolve(source); + } + ); + }); + }); + + Promise.all(sourceGetters) + .then((_sources) => { + const concatSource = new sources.ConcatSource(); + _sources.forEach((source) => { + concatSource.add(source); + concatSource.add('\n;'); + }); + + const combinedSource = new sources.CachedSource(concatSource); + const filename = interpolateName( + { resourcePath: 'scripts.js' }, + this.options.filename as string, + { content: combinedSource.source() } + ); + + const output = { filename, source: combinedSource }; + this._insertOutput(compiler, compilation, output); + this._cachedOutput = output; + addDependencies(compilation, scripts); + + callback(); + }) + .catch((err: Error) => callback(err)); + }); + } +} diff --git a/packages/rspack/src/utils/config.ts b/packages/rspack/src/utils/config.ts index 848d2b56a7300..d2dc7b094f505 100644 --- a/packages/rspack/src/utils/config.ts +++ b/packages/rspack/src/utils/config.ts @@ -1,7 +1,13 @@ -import type { ExecutorContext } from '@nx/devkit'; +import { + ExecutorContext, + readCachedProjectGraph, + readProjectsConfigurationFromProjectGraph, + workspaceRoot, +} from '@nx/devkit'; import type { Configuration } from '@rspack/core'; +import { readNxJson } from 'nx/src/config/configuration'; -import { SharedConfigContext } from './model'; +import { NormalizedRspackExecutorSchema } from '../executors/rspack/schema'; export const nxRspackComposablePlugin = 'nxRspackComposablePlugin'; @@ -12,7 +18,7 @@ export function isNxRspackComposablePlugin( } export interface NxRspackExecutionContext { - options: unknown; + options: NormalizedRspackExecutorSchema; context: ExecutorContext; configuration?: string; } @@ -27,23 +33,85 @@ export interface AsyncNxComposableRspackPlugin { | Promise; } -export function composePlugins(...plugins: any[]) { - return Object.defineProperty( +export function composePlugins( + ...plugins: ( + | NxComposableRspackPlugin + | AsyncNxComposableRspackPlugin + | Promise + )[] +) { + return Object.assign( async function combined( config: Configuration, - ctx: SharedConfigContext + ctx: NxRspackExecutionContext ): Promise { + // Rspack may be calling us as a standard config function. + // Build up Nx context from environment variables. + // This is to enable `@nx/webpack/plugin` to work with existing projects. + if (ctx['env']) { + ensureNxRspackExecutionContext(ctx); + // Build this from scratch since what webpack passes us is the env, not config, + // and `withNX()` creates a new config object anyway. + config = {}; + } + for (const plugin of plugins) { const fn = await plugin; config = await fn(config, ctx); } return config; }, - nxRspackComposablePlugin, { - value: true, - enumerable: false, - writable: false, + [nxRspackComposablePlugin]: true, + } + ); +} + +export function composePluginsSync(...plugins: NxComposableRspackPlugin[]) { + return Object.assign( + function combined( + config: Configuration, + ctx: NxRspackExecutionContext + ): Configuration { + for (const plugin of plugins) { + config = plugin(config, ctx); + } + return config; + }, + { + [nxRspackComposablePlugin]: true, } ); } + +function ensureNxRspackExecutionContext(ctx: NxRspackExecutionContext): void { + const projectName = process.env.NX_TASK_TARGET_PROJECT; + const targetName = process.env.NX_TASK_TARGET_TARGET; + const configurationName = process.env.NX_TASK_TARGET_CONFIGURATION; + const projectGraph = readCachedProjectGraph(); + const projectNode = projectGraph.nodes[projectName]; + ctx.options ??= { + root: workspaceRoot, + projectRoot: projectNode.data.root, + sourceRoot: projectNode.data.sourceRoot ?? projectNode.data.root, + // These aren't actually needed since NxRspackPlugin and withNx both support them being undefined. + assets: undefined, + outputPath: undefined, + tsConfig: undefined, + outputFileName: undefined, + main: undefined, + rspackConfig: undefined, + }; + ctx.context ??= { + projectName, + targetName, + configurationName, + projectsConfigurations: + readProjectsConfigurationFromProjectGraph(projectGraph), + nxJsonConfiguration: readNxJson(workspaceRoot), + cwd: process.cwd(), + root: workspaceRoot, + isVerbose: process.env['NX_VERBOSE_LOGGING'] === 'true', + projectGraph, + }; +} diff --git a/packages/rspack/src/utils/generator-utils.ts b/packages/rspack/src/utils/generator-utils.ts index 4c1cdd5285487..06e226185e0ad 100644 --- a/packages/rspack/src/utils/generator-utils.ts +++ b/packages/rspack/src/utils/generator-utils.ts @@ -172,6 +172,7 @@ export function addOrChangeBuildTarget( // If standalone project then use the project's name in dist. project.root === '.' ? project.name : project.root ), + index: joinPathFragments(project.root, 'src/index.html'), main: determineMain(tree, options), tsConfig: determineTsConfig(tree, options), rspackConfig: joinPathFragments(project.root, 'rspack.config.js'), diff --git a/packages/rspack/src/utils/model.ts b/packages/rspack/src/utils/model.ts index 3ca89adaaadfd..0b518d3bc6f5d 100644 --- a/packages/rspack/src/utils/model.ts +++ b/packages/rspack/src/utils/model.ts @@ -1,7 +1,19 @@ -import { ExecutorContext } from '@nx/devkit'; -import { RspackExecutorSchema } from '../executors/rspack/schema'; +export interface ExtraEntryPointClass { + bundleName?: string; + inject?: boolean; + input: string; + lazy?: boolean; +} + +export type ExtraEntryPoint = ExtraEntryPointClass | string; + +export type NormalizedEntryPoint = Required; -export interface SharedConfigContext { - options: RspackExecutorSchema; - context: ExecutorContext; +export interface EmittedFile { + id?: string; + name?: string; + file: string; + extension: string; + initial: boolean; + asset?: boolean; } diff --git a/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts index 492aa612a3305..d15cee062812c 100644 --- a/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts +++ b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation-ssr.ts @@ -1,10 +1,10 @@ import { DefinePlugin } from '@rspack/core'; -import { SharedConfigContext } from '../../model'; import { ModuleFederationConfig, NxModuleFederationConfigOverride, } from '../models'; import { getModuleFederationConfig } from './utils'; +import { NxRspackExecutionContext } from '../../config'; export async function withModuleFederationForSSR( options: ModuleFederationConfig, @@ -19,7 +19,7 @@ export async function withModuleFederationForSSR( isServer: true, }); - return (config, { context }: SharedConfigContext) => { + return (config, { context }: NxRspackExecutionContext) => { config.target = 'async-node'; config.output.uniqueName = options.name; config.optimization = { diff --git a/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts index 7f43b02c63cd4..67494dcd2e259 100644 --- a/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts +++ b/packages/rspack/src/utils/module-federation/with-module-federation/with-module-federation.ts @@ -1,12 +1,12 @@ import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; import type { Configuration } from '@rspack/core'; import { DefinePlugin } from '@rspack/core'; -import { SharedConfigContext } from '../../model'; import { ModuleFederationConfig, NxModuleFederationConfigOverride, } from '../models'; import { getModuleFederationConfig } from './utils'; +import { NxRspackExecutionContext } from '../../config'; const isVarOrWindow = (libType?: string) => libType === 'var' || libType === 'window'; @@ -31,7 +31,7 @@ export async function withModuleFederation( return function makeConfig( config: Configuration, - { context }: SharedConfigContext + { context }: NxRspackExecutionContext ): Configuration { config.output.uniqueName = options.name; config.output.publicPath = 'auto'; diff --git a/packages/rspack/src/utils/read-rspack-options.ts b/packages/rspack/src/utils/read-rspack-options.ts index 20cd378eb1589..6c69c8836b2fc 100644 --- a/packages/rspack/src/utils/read-rspack-options.ts +++ b/packages/rspack/src/utils/read-rspack-options.ts @@ -25,6 +25,12 @@ export async function readRspackOptions( root: workspaceRoot, projectRoot: '', sourceRoot: '', + outputFileName: '', + assets: [], + main: '', + tsConfig: '', + outputPath: '', + rspackConfig: '', }, context: { root: workspaceRoot, diff --git a/packages/rspack/src/utils/with-nx.ts b/packages/rspack/src/utils/with-nx.ts index cd9939786340c..81461751ea8cf 100644 --- a/packages/rspack/src/utils/with-nx.ts +++ b/packages/rspack/src/utils/with-nx.ts @@ -11,13 +11,13 @@ import * as path from 'path'; import { join } from 'path'; import { GeneratePackageJsonPlugin } from '../plugins/generate-package-json-plugin'; import { getCopyPatterns } from './get-copy-patterns'; -import { SharedConfigContext } from './model'; import { normalizeAssets } from './normalize-assets'; +import { NxRspackExecutionContext } from './config'; export function withNx(_opts = {}) { return function makeConfig( config: Configuration, - { options, context }: SharedConfigContext + { options, context }: NxRspackExecutionContext ): Configuration { const isProd = process.env.NODE_ENV === 'production' || options.mode === 'production'; diff --git a/packages/rspack/src/utils/with-react.ts b/packages/rspack/src/utils/with-react.ts index 6afd0974c5a3c..396a7c5379d34 100644 --- a/packages/rspack/src/utils/with-react.ts +++ b/packages/rspack/src/utils/with-react.ts @@ -1,11 +1,11 @@ import { Configuration } from '@rspack/core'; -import { SharedConfigContext } from './model'; import { withWeb } from './with-web'; +import { NxRspackExecutionContext } from './config'; export function withReact(opts = {}) { return function makeConfig( config: Configuration, - { options, context }: SharedConfigContext + { options, context }: NxRspackExecutionContext ): Configuration { const isDev = process.env.NODE_ENV === 'development' || options.mode === 'development'; diff --git a/packages/rspack/src/utils/with-web.ts b/packages/rspack/src/utils/with-web.ts index e5ba6fce9e43e..4c85dad59d026 100644 --- a/packages/rspack/src/utils/with-web.ts +++ b/packages/rspack/src/utils/with-web.ts @@ -1,132 +1,52 @@ -import { Configuration, RuleSetRule, rspack } from '@rspack/core'; -import * as path from 'path'; -import { SharedConfigContext } from './model'; +import { Configuration } from '@rspack/core'; +import { ExtraEntryPointClass } from './model'; +import { applyWebConfig } from '../plugins/utils/apply-web-config'; +import { NxRspackExecutionContext } from './config'; export interface WithWebOptions { + baseHref?: string; + crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; + deployUrl?: string; + extractCss?: boolean; + generateIndexHtml?: boolean; + index?: string; + postcssConfig?: string; + scripts?: Array; + styles?: Array; + subresourceIntegrity?: boolean; stylePreprocessorOptions?: { includePaths?: string[]; }; cssModules?: boolean; + ssr?: boolean; } -export function withWeb(opts: WithWebOptions = {}) { +const processed = new Set(); + +export function withWeb(pluginOptions: WithWebOptions = {}) { return function makeConfig( config: Configuration, - { options, context }: SharedConfigContext + { options, context }: NxRspackExecutionContext ): Configuration { - const isProd = - process.env.NODE_ENV === 'production' || options.mode === 'production'; - - const projectRoot = path.join( - context.root, - context.projectGraph.nodes[context.projectName].data.root - ); - - const includePaths: string[] = []; - if (opts?.stylePreprocessorOptions?.includePaths?.length > 0) { - opts.stylePreprocessorOptions.includePaths.forEach( - (includePath: string) => - includePaths.push(path.resolve(context.root, includePath)) - ); - } - - let lessPathOptions: { paths?: string[] } = {}; - - if (includePaths.length > 0) { - lessPathOptions = { - paths: includePaths, - }; + if (processed.has(config)) { + return config; } - return { - ...config, - target: config.target ?? 'web', - experiments: { - css: true, - }, - module: { - ...config.module, - rules: [ - ...(config.module.rules || []), - { - test: /\.css$/, - type: opts?.cssModules ? 'css/module' : undefined, - }, - { - test: /\.css$/, - type: 'css', - use: [ - { - loader: require.resolve('postcss-loader'), - }, - ], - }, - { - test: /\.scss$|\.sass$/, - type: opts?.cssModules ? 'css/module' : undefined, - use: [ - { - loader: require.resolve('sass-loader'), - options: { - sourceMap: !!options.sourceMap, - sassOptions: { - fiber: false, - // bootstrap-sass requires a minimum precision of 8 - precision: 8, - includePaths, - }, - }, - }, - ], - }, - { - test: /.less$/, - type: opts?.cssModules ? 'css/module' : undefined, - use: [ - { - loader: require.resolve('less-loader'), - options: { - sourceMap: !!options.sourceMap, - lessOptions: { - javascriptEnabled: true, - ...lessPathOptions, - }, - }, - }, - ], - }, - { - test: /\.styl$/, - use: [ - { - loader: require.resolve('stylus-loader'), - options: { - sourceMap: !!options.sourceMap, - stylusOptions: { - include: includePaths, - }, - }, - }, - ], - }, - ].filter((a): a is RuleSetRule => !!a), + applyWebConfig( + { + ...options, + ...pluginOptions, + root: context.root, + projectName: context.projectName, + targetName: context.targetName, + configurationName: context.configurationName, + projectGraph: context.projectGraph, }, - plugins: [ - ...config.plugins, - new rspack.HtmlRspackPlugin({ - template: options.indexHtml - ? path.join(context.root, options.indexHtml) - : path.join(projectRoot, 'src/index.html'), - ...(options.baseHref ? { base: { href: options.baseHref } } : {}), - }), - new rspack.EnvironmentPlugin({ - NODE_ENV: isProd ? 'production' : 'development', - }), - new rspack.DefinePlugin( - getClientEnvironment(isProd ? 'production' : undefined).stringified - ), - ], - }; + config + ); + + processed.add(config); + return config; }; } From 7d355719222d98471fe6e4d8c0cf5033a505c32d Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 5 Nov 2024 17:42:24 +0000 Subject: [PATCH 2/9] chore(rspack): add todo to check crossOrigin --- packages/rspack/src/utils/with-web.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rspack/src/utils/with-web.ts b/packages/rspack/src/utils/with-web.ts index 4c85dad59d026..c5a45ddfb8a25 100644 --- a/packages/rspack/src/utils/with-web.ts +++ b/packages/rspack/src/utils/with-web.ts @@ -5,6 +5,7 @@ import { NxRspackExecutionContext } from './config'; export interface WithWebOptions { baseHref?: string; + // TODO: Check if this is supportable in rspack. It was only being used in the WriteIndexHtmlPlugin for Webpack. The same isn't supported in HtmlRspackPlugin. crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; deployUrl?: string; extractCss?: boolean; From f0e0014e25ce1cbfca801c53f54d7672f9d73fc1 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 5 Nov 2024 17:49:31 +0000 Subject: [PATCH 3/9] chore(rspack): update e2e test --- e2e/rspack/tests/rspack.spec.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/e2e/rspack/tests/rspack.spec.ts b/e2e/rspack/tests/rspack.spec.ts index 0d629a14801a9..627cf0c1f26d2 100644 --- a/e2e/rspack/tests/rspack.spec.ts +++ b/e2e/rspack/tests/rspack.spec.ts @@ -65,7 +65,11 @@ describe('rspack e2e', () => { }); expect(result).toContain('Successfully ran target build'); // Make sure expected files are present. - expect(listFiles(`dist/${project}`)).toHaveLength(5); + /** + * The files that are generated are: + * ["3rdpartylicenses.txt", "assets", "favicon.ico", "index.html", "main.bf7851e6.js", "runtime.e4294127.js"] + */ + expect(listFiles(`dist/${project}`)).toHaveLength(6); result = runCLI(`test ${project}`); expect(result).toContain('Successfully ran target test'); @@ -83,7 +87,7 @@ describe('rspack e2e', () => { env: { NODE_ENV: 'production' }, }); expect(result).toContain('Successfully ran target build'); - expect(listFiles(`dist/${project}`)).toHaveLength(5); // same length as before + expect(listFiles(`dist/${project}`)).toHaveLength(6); // same length as before // Generate a new app and check that the files are correct const app2 = uniq('app2'); @@ -116,7 +120,7 @@ describe('rspack e2e', () => { }); expect(result).toContain('Successfully ran target build'); // Make sure expected files are present. - expect(listFiles(`dist/${app2}`)).toHaveLength(5); + expect(listFiles(`dist/${app2}`)).toHaveLength(6); result = runCLI(`test ${app2}`); expect(result).toContain('Successfully ran target test'); From 0af066316bc522d48eea1f897e286a90b5cac8c3 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 5 Nov 2024 17:55:34 +0000 Subject: [PATCH 4/9] fix(rspack): update required stubs --- packages/rspack/src/plugins/utils/models.ts | 1 + packages/rspack/src/utils/config.ts | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/rspack/src/plugins/utils/models.ts b/packages/rspack/src/plugins/utils/models.ts index ca086f9ab51c6..55daa784fe28f 100644 --- a/packages/rspack/src/plugins/utils/models.ts +++ b/packages/rspack/src/plugins/utils/models.ts @@ -77,6 +77,7 @@ export interface NxAppRspackPluginOptions { * The compiler to use. Default is `babel` and requires a `.babelrc` file. */ compiler?: 'babel' | 'swc' | 'tsc'; + // TODO: Check if this is supportable in rspack. It was only being used in the WriteIndexHtmlPlugin for Webpack. The same isn't supported in HtmlRspackPlugin. /** * Set `crossorigin` attribute on the `script` and `link` tags. */ diff --git a/packages/rspack/src/utils/config.ts b/packages/rspack/src/utils/config.ts index d2dc7b094f505..a19dba0a1cbc3 100644 --- a/packages/rspack/src/utils/config.ts +++ b/packages/rspack/src/utils/config.ts @@ -96,10 +96,7 @@ function ensureNxRspackExecutionContext(ctx: NxRspackExecutionContext): void { sourceRoot: projectNode.data.sourceRoot ?? projectNode.data.root, // These aren't actually needed since NxRspackPlugin and withNx both support them being undefined. assets: undefined, - outputPath: undefined, - tsConfig: undefined, outputFileName: undefined, - main: undefined, rspackConfig: undefined, }; ctx.context ??= { From 0ed023879e053070e44929dfe65baa4bc1ecf916 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 6 Nov 2024 15:19:46 +0000 Subject: [PATCH 5/9] fix(rspack): remove crossOrigin option as it is not supported --- packages/rspack/src/plugins/utils/models.ts | 5 ----- packages/rspack/src/utils/with-web.ts | 2 -- 2 files changed, 7 deletions(-) diff --git a/packages/rspack/src/plugins/utils/models.ts b/packages/rspack/src/plugins/utils/models.ts index 55daa784fe28f..d96eeab28b712 100644 --- a/packages/rspack/src/plugins/utils/models.ts +++ b/packages/rspack/src/plugins/utils/models.ts @@ -77,11 +77,6 @@ export interface NxAppRspackPluginOptions { * The compiler to use. Default is `babel` and requires a `.babelrc` file. */ compiler?: 'babel' | 'swc' | 'tsc'; - // TODO: Check if this is supportable in rspack. It was only being used in the WriteIndexHtmlPlugin for Webpack. The same isn't supported in HtmlRspackPlugin. - /** - * Set `crossorigin` attribute on the `script` and `link` tags. - */ - crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; /** * Delete the output path before building. */ diff --git a/packages/rspack/src/utils/with-web.ts b/packages/rspack/src/utils/with-web.ts index c5a45ddfb8a25..6989194737f09 100644 --- a/packages/rspack/src/utils/with-web.ts +++ b/packages/rspack/src/utils/with-web.ts @@ -5,8 +5,6 @@ import { NxRspackExecutionContext } from './config'; export interface WithWebOptions { baseHref?: string; - // TODO: Check if this is supportable in rspack. It was only being used in the WriteIndexHtmlPlugin for Webpack. The same isn't supported in HtmlRspackPlugin. - crossOrigin?: 'none' | 'anonymous' | 'use-credentials'; deployUrl?: string; extractCss?: boolean; generateIndexHtml?: boolean; From fbf5e3181e74a832c85440f7de4716618391c6d8 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 6 Nov 2024 15:23:51 +0000 Subject: [PATCH 6/9] fix(rspack): support string for sourcemap to enable inline-source-map --- packages/rspack/src/executors/rspack/schema.d.ts | 2 +- packages/rspack/src/plugins/utils/models.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rspack/src/executors/rspack/schema.d.ts b/packages/rspack/src/executors/rspack/schema.d.ts index 4256c33711f2d..f1362a3d98bc7 100644 --- a/packages/rspack/src/executors/rspack/schema.d.ts +++ b/packages/rspack/src/executors/rspack/schema.d.ts @@ -18,7 +18,7 @@ export interface RspackExecutorSchema { rspackConfig: string; optimization?: boolean | OptimizationOptions; - sourceMap?: boolean | 'hidden'; + sourceMap?: boolean | string; assets?: any[]; extractLicenses?: boolean; fileReplacements?: FileReplacement[]; diff --git a/packages/rspack/src/plugins/utils/models.ts b/packages/rspack/src/plugins/utils/models.ts index d96eeab28b712..3a1405a506b58 100644 --- a/packages/rspack/src/plugins/utils/models.ts +++ b/packages/rspack/src/plugins/utils/models.ts @@ -187,7 +187,7 @@ export interface NxAppRspackPluginOptions { /** * Generate source maps. */ - sourceMap?: boolean | 'hidden'; + sourceMap?: boolean | string; /** * When `true`, `process.env.NODE_ENV` will be excluded from the bundle. Useful for building a web application to run in a Node environment. */ From bb48cd07634e59cb33a48318c73c88f51cce8b79 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 6 Nov 2024 16:55:10 +0000 Subject: [PATCH 7/9] fix(rspack): use NX_GRAPH_CREATION to bail out of withWeb early --- packages/rspack/src/plugins/utils/apply-web-config.ts | 2 +- .../src/plugins/nx-webpack-plugin/lib/apply-web-config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rspack/src/plugins/utils/apply-web-config.ts b/packages/rspack/src/plugins/utils/apply-web-config.ts index fce9de94706b3..7d92391c79ec2 100644 --- a/packages/rspack/src/plugins/utils/apply-web-config.ts +++ b/packages/rspack/src/plugins/utils/apply-web-config.ts @@ -32,7 +32,7 @@ export function applyWebConfig( useNormalizedEntry?: boolean; } = {} ): void { - if (!process.env['NX_TASK_TARGET_PROJECT']) return; + if (!process.env['NX_GRAPH_CREATION']) return; // Defaults that was applied from executor schema previously. options.runtimeChunk ??= true; // need this for HMR and other things to work diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts index 56af4f17b6ab9..27a094d9ea3e0 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts @@ -35,7 +35,7 @@ export function applyWebConfig( useNormalizedEntry?: boolean; } = {} ): void { - if (!process.env['NX_TASK_TARGET_PROJECT']) return; + if (!process.env['NX_GRAPH_CREATION']) return; // Defaults that was applied from executor schema previously. options.runtimeChunk ??= true; // need this for HMR and other things to work From f13a87f3d8a34ced0e861394e823556173c6ca1c Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 6 Nov 2024 18:13:04 +0000 Subject: [PATCH 8/9] fix(rspack): remove babel options --- .../rspack/src/plugins/utils/apply-web-config.ts | 12 ++++++------ packages/rspack/src/plugins/utils/models.ts | 13 +------------ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/rspack/src/plugins/utils/apply-web-config.ts b/packages/rspack/src/plugins/utils/apply-web-config.ts index 7d92391c79ec2..f2be11a654580 100644 --- a/packages/rspack/src/plugins/utils/apply-web-config.ts +++ b/packages/rspack/src/plugins/utils/apply-web-config.ts @@ -9,7 +9,7 @@ import { EnvironmentPlugin, } from '@rspack/core'; import { instantiateScriptPlugins } from './instantiate-script-plugins'; -import path, { join } from 'path'; +import { join, resolve } from 'path'; import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity'; import { getOutputHashFormat } from './hash-format'; import { normalizeExtraEntryPoints } from './normalize-entry'; @@ -103,7 +103,7 @@ export function applyWebConfig( if (options?.stylePreprocessorOptions?.includePaths?.length > 0) { options.stylePreprocessorOptions.includePaths.forEach( (includePath: string) => - includePaths.push(path.resolve(options.root, includePath)) + includePaths.push(resolve(options.root, includePath)) ); } @@ -120,7 +120,7 @@ export function applyWebConfig( normalizeExtraEntryPoints(options.styles, 'styles').forEach((style) => { const resolvedPath = style.input.startsWith('.') ? style.input - : path.resolve(options.root, style.input); + : resolve(options.root, style.input); // Add style entry points. if (entries[style.bundleName]) { entries[style.bundleName].import.push(resolvedPath); @@ -178,7 +178,7 @@ export function applyWebConfig( use: [ ...getCommonLoadersForCssModules(options, includePaths), { - loader: path.join( + loader: join( __dirname, '../../../utils/webpack/deprecated-stylus-loader.js' ), @@ -241,7 +241,7 @@ export function applyWebConfig( use: [ ...getCommonLoadersForGlobalCss(options, includePaths), { - loader: path.join( + loader: join( __dirname, '../../../utils/webpack/deprecated-stylus-loader.js' ), @@ -305,7 +305,7 @@ export function applyWebConfig( use: [ ...getCommonLoadersForGlobalStyle(options, includePaths), { - loader: path.join( + loader: join( __dirname, '../../../utils/webpack/deprecated-stylus-loader.js' ), diff --git a/packages/rspack/src/plugins/utils/models.ts b/packages/rspack/src/plugins/utils/models.ts index 3a1405a506b58..55cf7e342bcdc 100644 --- a/packages/rspack/src/plugins/utils/models.ts +++ b/packages/rspack/src/plugins/utils/models.ts @@ -55,14 +55,6 @@ export interface NxAppRspackPluginOptions { * Assets to be copied over to the output path. */ assets?: Array; - /** - * Babel configuration file if compiler is babel. - */ - babelConfig?: string; - /** - * If true, Babel will look for a babel.config.json up the directory tree. - */ - babelUpwardRootMode?: boolean; /** * Set for the resulting index.html. */ @@ -73,10 +65,7 @@ export interface NxAppRspackPluginOptions { buildLibsFromSource?: boolean; commonChunk?: boolean; - /** - * The compiler to use. Default is `babel` and requires a `.babelrc` file. - */ - compiler?: 'babel' | 'swc' | 'tsc'; + /** * Delete the output path before building. */ From dc99e6403b83bdf70a1b58b129bc67dd99dfdee6 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 6 Nov 2024 19:38:57 +0000 Subject: [PATCH 9/9] fix(rspack): use global.NX_GRAPH_CREATION correctly --- packages/rspack/src/plugins/utils/apply-web-config.ts | 2 +- .../src/plugins/nx-webpack-plugin/lib/apply-web-config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rspack/src/plugins/utils/apply-web-config.ts b/packages/rspack/src/plugins/utils/apply-web-config.ts index f2be11a654580..54f8036ec8262 100644 --- a/packages/rspack/src/plugins/utils/apply-web-config.ts +++ b/packages/rspack/src/plugins/utils/apply-web-config.ts @@ -32,7 +32,7 @@ export function applyWebConfig( useNormalizedEntry?: boolean; } = {} ): void { - if (!process.env['NX_GRAPH_CREATION']) return; + if (global.NX_GRAPH_CREATION) return; // Defaults that was applied from executor schema previously. options.runtimeChunk ??= true; // need this for HMR and other things to work diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts index 27a094d9ea3e0..7451509093100 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-web-config.ts @@ -35,7 +35,7 @@ export function applyWebConfig( useNormalizedEntry?: boolean; } = {} ): void { - if (!process.env['NX_GRAPH_CREATION']) return; + if (global.NX_GRAPH_CREATION) return; // Defaults that was applied from executor schema previously. options.runtimeChunk ??= true; // need this for HMR and other things to work