feat(@angular/build): add sass to stylePreprocessorOptions in application builder

This commit introduces the functionality to configure a limited number of options for Sass processing in the Angular application builder. The following options have been added to enhance the Sass integration:

- **futureDeprecations**: Specifies features that are scheduled for deprecation. The compiler will treat these as active and emit warnings as necessary.
- **fatalDeprecations**: Identifies Sass features that are already deprecated and will cause build failures if used.
- **silenceDeprecations**: This option suppresses deprecation warnings for specified versions.

Usage example:
```json
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:application",
    "options": {
      "outputHashing": "none",
      "namedChunks": true,
      "stylePreprocessorOptions": {
        "sass": {
          "futureDeprecations": ["color-functions"],
          "fatalDeprecations": ["color-functions"],
          "silenceDeprecations": ["1.77.0"]
        }
      }
    }
  }
}
```

For more information about these options, please refer to the  Sass documentation: https://sass-lang.com/documentation/js-api/interfaces/options/

Closes #28587
This commit is contained in:
Alan Agius 2024-10-16 07:36:49 +00:00 committed by Alan Agius
parent 3db1d81397
commit b6951f4482
7 changed files with 145 additions and 2 deletions

View File

@ -125,6 +125,34 @@
"type": "string"
},
"default": []
},
"sass": {
"description": "Options to pass to the sass preprocessor.",
"type": "object",
"properties": {
"fatalDeprecations": {
"description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.",
"type": "array",
"items": {
"type": "string"
}
},
"silenceDeprecations": {
"description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.",
"type": "array",
"items": {
"type": "string"
}
},
"futureDeprecations": {
"description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.",
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false

View File

@ -0,0 +1,89 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { buildApplication } from '../../index';
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
import { logging } from '@angular-devkit/core';
describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
describe('Option: "stylePreprocessorOptions.sass"', () => {
it('should cause the build to fail when using `fatalDeprecations` in global styles', async () => {
await harness.writeFile('src/styles.scss', 'p { color: darken(red, 10%) }');
harness.useTarget('build', {
...BASE_OPTIONS,
styles: ['src/styles.scss'],
stylePreprocessorOptions: {
sass: {
fatalDeprecations: ['color-functions'],
},
},
});
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
expect(result?.success).toBeFalse();
expect(logs).not.toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('darken() is deprecated'),
}),
);
});
it('should succeed without `fatalDeprecations` despite using deprecated color functions', async () => {
await harness.writeFiles({
'src/styles.scss': 'p { color: darken(red, 10%) }',
'src/app/app.component.scss': 'p { color: darken(red, 10%) }',
});
await harness.modifyFile('src/app/app.component.ts', (content) => {
return content.replace('./app.component.css', 'app.component.scss');
});
harness.useTarget('build', {
...BASE_OPTIONS,
styles: ['src/styles.scss'],
stylePreprocessorOptions: {
sass: {},
},
});
const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});
it('should cause the build to fail when using `fatalDeprecations` in component styles', async () => {
await harness.modifyFile('src/app/app.component.ts', (content) => {
return content.replace('./app.component.css', 'app.component.scss');
});
await harness.writeFile('src/app/app.component.scss', 'p { color: darken(red, 10%) }');
harness.useTarget('build', {
...BASE_OPTIONS,
stylePreprocessorOptions: {
sass: {
fatalDeprecations: ['color-functions'],
},
},
});
const { result, logs } = await harness.executeOnce({
outputLogsOnFailure: false,
});
expect(result?.success).toBeFalse();
expect(logs).not.toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('darken() is deprecated'),
}),
);
});
});
});

View File

@ -68,6 +68,9 @@ export function createCompilerPluginOptions(
sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
outputNames,
includePaths: stylePreprocessorOptions?.includePaths,
// string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sass: stylePreprocessorOptions?.sass as any,
externalDependencies,
target,
inlineStyleLanguage,

View File

@ -63,6 +63,9 @@ export function createGlobalStylesBundleOptions(
bundles: '[name]',
},
includePaths: stylePreprocessorOptions?.includePaths,
// string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sass: stylePreprocessorOptions?.sass as any,
tailwindConfiguration,
postcssConfiguration,
cacheOptions,

View File

@ -16,7 +16,7 @@ import { CssStylesheetLanguage } from './css-language';
import { createCssResourcePlugin } from './css-resource-plugin';
import { LessStylesheetLanguage } from './less-language';
import { SassStylesheetLanguage } from './sass-language';
import { StylesheetPluginFactory } from './stylesheet-plugin-factory';
import { StylesheetPluginFactory, StylesheetPluginsass } from './stylesheet-plugin-factory';
export interface BundleStylesheetOptions {
workspaceRoot: string;
@ -26,6 +26,7 @@ export interface BundleStylesheetOptions {
sourcemap: boolean | 'external' | 'inline' | 'linked';
outputNames: { bundles: string; media: string };
includePaths?: string[];
sass?: StylesheetPluginsass;
externalDependencies?: string[];
target: string[];
tailwindConfiguration?: { file: string; package: string };
@ -51,6 +52,7 @@ export function createStylesheetBundleOptions(
inlineComponentData,
tailwindConfiguration: options.tailwindConfiguration,
postcssConfiguration: options.postcssConfiguration,
sass: options.sass,
},
cache,
);

View File

@ -94,8 +94,9 @@ async function compileString(
// failing resolution attempts.
const resolutionCache = new MemoryCache<URL | null>();
const packageRootCache = new MemoryCache<string | null>();
const warnings: PartialMessage[] = [];
const { silenceDeprecations, futureDeprecations, fatalDeprecations } = options.sass ?? {};
try {
const { css, sourceMap, loadedUrls } = await sassWorkerPool.compileStringAsync(data, {
url: pathToFileURL(filePath),
@ -104,6 +105,9 @@ async function compileString(
loadPaths: options.includePaths,
sourceMap: options.sourcemap,
sourceMapIncludeSources: options.sourcemap,
silenceDeprecations,
fatalDeprecations,
futureDeprecations,
quietDeps: true,
importers: [
{

View File

@ -11,9 +11,18 @@ import glob from 'fast-glob';
import assert from 'node:assert';
import { readFile } from 'node:fs/promises';
import { extname } from 'node:path';
import type { Options } from 'sass';
import type { PostcssConfiguration } from '../../../utils/postcss-configuration';
import { LoadResultCache, createCachedLoad } from '../load-result-cache';
/**
* Configuration options for handling Sass-specific deprecations in a stylesheet plugin.
*/
export type StylesheetPluginsass = Pick<
Options<'async'>,
'futureDeprecations' | 'fatalDeprecations' | 'silenceDeprecations'
>;
/**
* Convenience type for a postcss processor.
*/
@ -60,6 +69,11 @@ export interface StylesheetPluginOptions {
* and any tailwind usage must be manually configured in the custom postcss usage.
*/
postcssConfiguration?: PostcssConfiguration;
/**
* Optional Options for configuring Sass behavior.
*/
sass?: StylesheetPluginsass;
}
/**