feat(webpack): use sass-embedded and modern-compiler for sass (#29999)

## Current Behavior
Webpack and Rspack currently use `sass` and its Legacy API with
`sass-loader`.
There is also no method to pass stylePreprocessorOptions other than
`includePaths` to the loaders.


## Expected Behavior
Switch to using `modern-compiler` api to remove deprecation warnings and
improve build performance.
Allow users to choose between `sass` and `sass-embedded` for sass
compiler implementation.

Expand the `stylePreprocesserOptions` interface to accept
`includePaths`, `sassOptions` and `lessOptions` that will be passed to
the appropriate loader.
This commit is contained in:
Colum Ferry 2025-02-24 17:44:19 +00:00 committed by GitHub
parent 75a69d93d0
commit 82169ace03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 704 additions and 240 deletions

View File

@ -5154,7 +5154,7 @@
"file": "generated/packages/rspack/migrations/20.5.0-package-updates.json",
"hidden": false,
"name": "20.5.0-package-updates",
"version": "20.5.0-beta.3",
"version": "20.5.0-beta.4",
"originalFilePath": "/packages/rspack",
"path": "/nx-api/rspack/migrations/20.5.0-package-updates",
"type": "migration"
@ -5955,6 +5955,16 @@
}
},
"migrations": {
"/nx-api/webpack/migrations/20.5.0-package-updates": {
"description": "",
"file": "generated/packages/webpack/migrations/20.5.0-package-updates.json",
"hidden": false,
"name": "20.5.0-package-updates",
"version": "20.5.0-beta.3",
"originalFilePath": "/packages/webpack",
"path": "/nx-api/webpack/migrations/20.5.0-package-updates",
"type": "migration"
},
"/nx-api/webpack/migrations/19.7.0-package-updates": {
"description": "",
"file": "generated/packages/webpack/migrations/19.7.0-package-updates.json",

View File

@ -5122,7 +5122,7 @@
"file": "generated/packages/rspack/migrations/20.5.0-package-updates.json",
"hidden": false,
"name": "20.5.0-package-updates",
"version": "20.5.0-beta.3",
"version": "20.5.0-beta.4",
"originalFilePath": "/packages/rspack",
"path": "rspack/migrations/20.5.0-package-updates",
"type": "migration"
@ -5918,6 +5918,16 @@
}
],
"migrations": [
{
"description": "",
"file": "generated/packages/webpack/migrations/20.5.0-package-updates.json",
"hidden": false,
"name": "20.5.0-package-updates",
"version": "20.5.0-beta.3",
"originalFilePath": "/packages/webpack",
"path": "webpack/migrations/20.5.0-package-updates",
"type": "migration"
},
{
"description": "",
"file": "generated/packages/webpack/migrations/19.7.0-package-updates.json",

View File

@ -286,10 +286,24 @@
"description": "Paths to include. Paths will be resolved to project root.",
"type": "array",
"items": { "type": "string" }
},
"sassOptions": {
"description": "Options to pass to sass-loader.",
"type": "object"
},
"lessOptions": {
"description": "Options to pass to less-loader.",
"type": "object"
}
},
"additionalProperties": false
},
"sassImplementation": {
"type": "string",
"description": "The implementation of the SASS compiler to use. Can be either `sass` or `sass-embedded`. Defaults to `sass-embedded`.",
"enum": ["sass", "sass-embedded"],
"default": "sass"
},
"styles": {
"type": "array",
"description": "External Styles which will be included with the application",

View File

@ -1,7 +1,9 @@
{
"name": "20.5.0-package-updates",
"version": "20.5.0-beta.3",
"version": "20.5.0-beta.4",
"packages": {
"sass-embedded": { "version": "^1.83.4", "alwaysAddToPackageJson": true },
"sass-loader": { "version": "^16.0.4", "alwaysAddToPackageJson": true },
"@rspack/core": { "version": "^1.2.2", "alwaysAddToPackageJson": false }
},
"aliases": [],

View File

@ -222,6 +222,12 @@
},
"additionalProperties": false
},
"sassImplementation": {
"type": "string",
"description": "The implementation of the SASS compiler to use. Can be either `sass` or `sass-embedded`. Defaults to `sass-embedded`.",
"enum": ["sass", "sass-embedded"],
"default": "sass"
},
"optimization": {
"description": "Enables optimization of the build output.",
"oneOf": [

View File

@ -0,0 +1,15 @@
{
"name": "20.5.0-package-updates",
"version": "20.5.0-beta.3",
"packages": {
"sass-loader": { "version": "^16.0.4", "alwaysAddToPackageJson": false },
"sass-embedded": { "version": "^1.83.4", "alwaysAddToPackageJson": true }
},
"aliases": [],
"description": "",
"hidden": false,
"implementation": "",
"path": "/packages/webpack",
"schema": null,
"type": "migration"
}

View File

@ -421,13 +421,17 @@ describe('React Applications', () => {
};
return config;
});
updateFile(
`apps/${appName}/src/base.${style}`,
`html { font-family: "Comic Sans MS"; }`
);
updateFile(
`apps/${appName}/src/styles.${style}`,
`@import 'base.${style}';`
);
updateFile(
`apps/${appName}/src/app/app.module.${style}`,
(s) => `@import 'base.${style}';\n${s}`
(s) => `@import '../base.${style}';\n${s}`
);
updateFile(
`libs/shared/lib/base.${style}`,

View File

@ -281,7 +281,8 @@
"rollup-plugin-typescript2": "^0.36.0",
"rxjs": "^7.8.0",
"sass": "1.55.0",
"sass-loader": "^12.2.0",
"sass-embedded": "^1.83.4",
"sass-loader": "^16.0.4",
"semver": "^7.6.3",
"source-map-loader": "^5.0.0",
"source-map-support": "0.5.19",

View File

@ -50,6 +50,8 @@
"@module-federation/enhanced",
"css-loader",
"webpack",
"sass-embedded",
"sass",
"ts-checker-rspack-plugin"
]
}

View File

@ -96,8 +96,16 @@
}
},
"20.5.0": {
"version": "20.5.0-beta.3",
"version": "20.5.0-beta.4",
"packages": {
"sass-embedded": {
"version": "^1.83.4",
"alwaysAddToPackageJson": true
},
"sass-loader": {
"version": "^16.0.4",
"alwaysAddToPackageJson": true
},
"@rspack/core": {
"version": "^1.2.2",
"alwaysAddToPackageJson": false

View File

@ -42,8 +42,9 @@
"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",
"sass": "^1.85.0",
"sass-embedded": "^1.83.4",
"sass-loader": "^16.0.4",
"source-map-loader": "^5.0.0",
"style-loader": "^3.3.0",
"picocolors": "^1.1.0",

View File

@ -33,6 +33,7 @@ export function normalizeOptions(
styles: options.optimization,
}
: options.optimization,
sassImplementation: options.sassImplementation ?? 'sass',
};
if (options.assets) {
normalizedOptions.assets = normalizeAssets(

View File

@ -35,7 +35,12 @@ export interface RspackExecutorSchema {
sourceMap?: boolean | DevTool;
standardRspackConfigFunction?: boolean;
statsJson?: boolean;
stylePreprocessorOptions?: any;
stylePreprocessorOptions?: {
includePaths?: string[];
sassOptions?: Record<string, any>;
lessOptions?: Record<string, any>;
};
sassImplementation?: 'sass' | 'sass-embedded';
styles?: Array<ExtraEntryPointClass | string>;
target?: 'web' | 'node';
transformers?: TransformerEntry[];

View File

@ -245,10 +245,24 @@
"items": {
"type": "string"
}
},
"sassOptions": {
"description": "Options to pass to sass-loader.",
"type": "object"
},
"lessOptions": {
"description": "Options to pass to less-loader.",
"type": "object"
}
},
"additionalProperties": false
},
"sassImplementation": {
"type": "string",
"description": "The implementation of the SASS compiler to use. Can be either `sass` or `sass-embedded`. Defaults to `sass-embedded`.",
"enum": ["sass", "sass-embedded"],
"default": "sass"
},
"styles": {
"type": "array",
"description": "External Styles which will be included with the application",

View File

@ -17,6 +17,8 @@ import {
rspackDevServerVersion,
rspackPluginMinifyVersion,
rspackPluginReactRefreshVersion,
sassEmbeddedVersion,
sassLoaderVersion,
} from '../../utils/versions';
import { InitGeneratorSchema } from './schema';
@ -101,6 +103,10 @@ export async function rspackInitGenerator(
if (schema.style === 'less') {
devDependencies['less-loader'] = lessLoaderVersion;
}
if (schema.style === 'scss') {
devDependencies['sass-loader'] = sassLoaderVersion;
devDependencies['sass-embedded'] = sassEmbeddedVersion;
}
if (schema.framework !== 'none' || schema.devServer) {
devDependencies['@rspack/dev-server'] = rspackDevServerVersion;

View File

@ -98,6 +98,8 @@ export function applyWebConfig(
// Determine hashing format.
const hashFormat = getOutputHashFormat(options.outputHashing as string);
const sassOptions = options.stylePreprocessorOptions?.sassOptions;
const lessOptions = options.stylePreprocessorOptions?.lessOptions;
const includePaths: string[] = [];
if (options?.stylePreprocessorOptions?.includePaths?.length > 0) {
options.stylePreprocessorOptions.includePaths.forEach(
@ -140,11 +142,16 @@ export function applyWebConfig(
{
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
implementation:
options.sassImplementation === 'sass-embedded'
? require.resolve('sass-embedded')
: require.resolve('sass'),
api: 'modern-compiler',
sassOptions: {
fiber: false,
precision: 8,
includePaths,
...(sassOptions ?? {}),
},
},
},
@ -160,6 +167,7 @@ export function applyWebConfig(
options: {
lessOptions: {
paths: includePaths,
...(lessOptions ?? {}),
},
},
},
@ -199,13 +207,18 @@ export function applyWebConfig(
{
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
api: 'modern-compiler',
implementation:
options.sassImplementation === 'sass-embedded'
? require.resolve('sass-embedded')
: require.resolve('sass'),
sourceMap: !!options.sourceMap,
sassOptions: {
fiber: false,
// bootstrap-sass requires a minimum precision of 8
precision: 8,
includePaths,
...(sassOptions ?? {}),
},
},
},
@ -223,6 +236,7 @@ export function applyWebConfig(
lessOptions: {
javascriptEnabled: true,
...lessPathOptions,
...(lessOptions ?? {}),
},
},
},
@ -263,13 +277,18 @@ export function applyWebConfig(
{
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
api: 'modern-compiler',
implementation:
options.sassImplementation === 'sass-embedded'
? require.resolve('sass-embedded')
: require.resolve('sass'),
sourceMap: !!options.sourceMap,
sassOptions: {
fiber: false,
// bootstrap-sass requires a minimum precision of 8
precision: 8,
includePaths,
...(sassOptions ?? {}),
},
},
},
@ -287,6 +306,7 @@ export function applyWebConfig(
lessOptions: {
javascriptEnabled: true,
...lessPathOptions,
...(lessOptions ?? {}),
},
},
},

View File

@ -159,6 +159,12 @@ export interface NxAppRspackPluginOptions {
* Add an additional chunk for the rspack runtime. Defaults to `true` when `target === 'web'`.
*/
runtimeChunk?: boolean;
// TODO(v21): Make Sass Embedded the default in version 21.
// TODO(v22): Remove in version 22.
/**
* The implementation of the SASS compiler to use. Can be either `sass` or `sass-embedded`. Defaults to `sass-embedded`.
*/
sassImplementation?: 'sass' | 'sass-embedded';
/**
* External scripts that will be included before the main application entry.
*/
@ -194,7 +200,11 @@ export interface NxAppRspackPluginOptions {
/**
* Options for the style preprocessor. e.g. `{ "includePaths": [] }` for SASS.
*/
stylePreprocessorOptions?: any;
stylePreprocessorOptions?: {
includePaths?: string[];
sassOptions?: Record<string, any>;
lessOptions?: Record<string, any>;
};
/**
* External stylesheets that will be included with the application.
*/

View File

@ -120,6 +120,8 @@ export function normalizeOptions(
projectRoot: projectNode.data.root,
root: workspaceRoot,
runtimeChunk: combinedPluginAndMaybeExecutorOptions.runtimeChunk ?? true,
sassImplementation:
combinedPluginAndMaybeExecutorOptions.sassImplementation ?? 'sass',
scripts: combinedPluginAndMaybeExecutorOptions.scripts ?? [],
sourceMap: combinedPluginAndMaybeExecutorOptions.sourceMap ?? !isProd,
sourceRoot,

View File

@ -5,6 +5,8 @@ export const rspackDevServerVersion = '1.0.9';
export const rspackPluginMinifyVersion = '^0.7.5';
export const rspackPluginReactRefreshVersion = '^1.0.0';
export const lessLoaderVersion = '~11.1.3';
export const sassLoaderVersion = '^16.0.4';
export const sassEmbeddedVersion = '^1.83.4';
export const reactRefreshVersion = '~0.14.0';
@ -14,7 +16,6 @@ export const nestjsPlatformExpressVersion = '~9.0.0';
export const nestjsMicroservicesVersion = '~9.0.0';
export const lessVersion = '4.1.3';
export const sassVersion = '^1.42.1';
export const stylusVersion = '^0.55.0';
export const eslintPluginImportVersion = '2.27.5';

View File

@ -10,10 +10,13 @@ export interface WithWebOptions {
generateIndexHtml?: boolean;
index?: string;
postcssConfig?: string;
sassImplementation?: 'sass' | 'sass-embedded';
scripts?: Array<ExtraEntryPointClass | string>;
styles?: Array<ExtraEntryPointClass | string>;
stylePreprocessorOptions?: {
includePaths?: string[];
sassOptions?: Record<string, any>;
lessOptions?: Record<string, any>;
};
cssModules?: boolean;
ssr?: boolean;

View File

@ -47,6 +47,8 @@
"less",
"less-loader",
"postcss-loader",
"sass",
"sass-embedded",
"sass-loader",
"style-loader",
"stylus",

View File

@ -35,6 +35,19 @@
"alwaysAddToPackageJson": false
}
}
},
"20.5.0": {
"version": "20.5.0-beta.3",
"packages": {
"sass-loader": {
"version": "^16.0.4",
"alwaysAddToPackageJson": false
},
"sass-embedded": {
"version": "^1.83.4",
"alwaysAddToPackageJson": true
}
}
}
}
}

View File

@ -51,8 +51,9 @@
"postcss-import": "~14.1.0",
"postcss-loader": "^6.1.1",
"rxjs": "^7.8.0",
"sass": "^1.42.1",
"sass-loader": "^12.2.0",
"sass": "^1.85.0",
"sass-embedded": "^1.83.4",
"sass-loader": "^16.0.4",
"source-map-loader": "^5.0.0",
"style-loader": "^3.3.0",
"stylus": "^0.64.0",

View File

@ -26,6 +26,7 @@ export function normalizeOptions(
outputFileName: options.outputFileName ?? 'main.js',
webpackConfig: normalizePluginPath(options.webpackConfig, root),
fileReplacements: normalizeFileReplacements(root, options.fileReplacements),
sassImplementation: options.sassImplementation ?? 'sass',
optimization:
typeof options.optimization !== 'object'
? {

View File

@ -81,6 +81,7 @@ export interface WebpackExecutorOptions {
index?: string;
postcssConfig?: string;
scripts?: Array<ExtraEntryPointClass | string>;
sassImplementation?: 'sass' | 'sass-embedded';
stylePreprocessorOptions?: any;
styles?: Array<ExtraEntryPointClass | string>;
subresourceIntegrity?: boolean;

View File

@ -144,6 +144,12 @@
},
"additionalProperties": false
},
"sassImplementation": {
"type": "string",
"description": "The implementation of the SASS compiler to use. Can be either `sass` or `sass-embedded`. Defaults to `sass-embedded`.",
"enum": ["sass", "sass-embedded"],
"default": "sass"
},
"optimization": {
"description": "Enables optimization of the build output.",
"oneOf": [

View File

@ -93,6 +93,8 @@ export function applyWebConfig(
// Determine hashing format.
const hashFormat = getOutputHashFormat(options.outputHashing as string);
const sassOptions = options.stylePreprocessorOptions?.sassOptions;
const lessOptions = options.stylePreprocessorOptions?.lessOptions;
const includePaths: string[] = [];
if (options?.stylePreprocessorOptions?.includePaths?.length > 0) {
options.stylePreprocessorOptions.includePaths.forEach(
@ -141,11 +143,16 @@ export function applyWebConfig(
{
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
api: 'modern-compiler',
implementation:
options.sassImplementation === 'sass-embedded'
? require.resolve('sass-embedded')
: require.resolve('sass'),
sassOptions: {
fiber: false,
precision: 8,
includePaths,
...(sassOptions ?? {}),
},
},
},
@ -161,6 +168,7 @@ export function applyWebConfig(
options: {
lessOptions: {
paths: includePaths,
...(lessOptions ?? {}),
},
},
},
@ -200,13 +208,18 @@ export function applyWebConfig(
{
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
api: 'modern-compiler',
implementation:
options.sassImplementation === 'sass-embedded'
? require.resolve('sass-embedded')
: require.resolve('sass'),
sourceMap: !!options.sourceMap,
sassOptions: {
fiber: false,
// bootstrap-sass requires a minimum precision of 8
precision: 8,
includePaths,
...(sassOptions ?? {}),
},
},
},
@ -224,6 +237,7 @@ export function applyWebConfig(
lessOptions: {
javascriptEnabled: true,
...lessPathOptions,
...(lessOptions ?? {}),
},
},
},
@ -264,13 +278,18 @@ export function applyWebConfig(
{
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
api: 'modern-compiler',
implementation:
options.sassImplementation === 'sass-embedded'
? require.resolve('sass-embedded')
: require.resolve('sass'),
sourceMap: !!options.sourceMap,
sassOptions: {
fiber: false,
// bootstrap-sass requires a minimum precision of 8
precision: 8,
includePaths,
...(sassOptions ?? {}),
},
},
},
@ -288,6 +307,7 @@ export function applyWebConfig(
lessOptions: {
javascriptEnabled: true,
...lessPathOptions,
...(lessOptions ?? {}),
},
},
},

View File

@ -128,6 +128,8 @@ export function normalizeOptions(
target: combinedPluginAndMaybeExecutorOptions.target,
targetName,
vendorChunk: combinedPluginAndMaybeExecutorOptions.vendorChunk ?? !isProd,
sassImplementation:
combinedPluginAndMaybeExecutorOptions.sassImplementation ?? 'sass',
};
}

View File

@ -194,7 +194,11 @@ export interface NxAppWebpackPluginOptions {
/**
* Options for the style preprocessor. e.g. `{ "includePaths": [] }` for SASS.
*/
stylePreprocessorOptions?: any;
stylePreprocessorOptions?: {
includePaths?: string[];
sassOptions?: Record<string, any>;
lessOptions?: Record<string, any>;
};
/**
* External stylesheets that will be included with the application.
*/
@ -215,6 +219,12 @@ export interface NxAppWebpackPluginOptions {
* Use tsconfig-paths-webpack-plugin to resolve modules using paths in the tsconfig file.
*/
useTsconfigPaths?: boolean;
// TODO(v21): Make Sass Embedded the default in version 21.
// TODO(v22): Remove in version 22.
/**
* The implementation of the SASS compiler to use. Can be either `sass` or `sass-embedded`. Defaults to `sass-embedded`.
*/
sassImplementation?: 'sass' | 'sass-embedded';
/**
* Generate a separate vendor chunk for 3rd party packages.
*/

View File

@ -17,8 +17,13 @@ export interface WithWebOptions {
generateIndexHtml?: boolean;
index?: string;
postcssConfig?: string;
sassImplementation?: 'sass' | 'sass-embedded';
scripts?: Array<ExtraEntryPointClass | string>;
stylePreprocessorOptions?: any;
stylePreprocessorOptions?: {
includePaths?: string[];
sassOptions?: Record<string, any>;
lessOptions?: Record<string, any>;
};
styles?: Array<ExtraEntryPointClass | string>;
subresourceIntegrity?: boolean;
ssr?: boolean;

706
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff