From f4be831197010a17394264bc74b1eb385ba95028 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:48:44 +0000 Subject: [PATCH] feat(@angular/build): Support Sass package importers Enhanced Sass integration by adding support for package importers. See: https://sass-lang.com/blog/announcing-pkg-importers/ Closes: #29854 --- .../esbuild/stylesheets/sass-language.ts | 7 ++-- tests/legacy-cli/e2e.bzl | 1 + .../tests/build/styles/sass-pkg-importer.ts | 34 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/legacy-cli/e2e/tests/build/styles/sass-pkg-importer.ts diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts b/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts index b3972ac407..4bccfb3adb 100644 --- a/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts +++ b/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts @@ -43,7 +43,8 @@ export const SassStylesheetLanguage = Object.freeze<StylesheetLanguage>({ resolveDir = dirname(fileURLToPath(options.containingUrl)); } - const result = await build.resolve(url, { + const path = url.startsWith('pkg:') ? url.slice(4) : url; + const result = await build.resolve(path, { kind: 'import-rule', resolveDir, }); @@ -56,8 +57,8 @@ export const SassStylesheetLanguage = Object.freeze<StylesheetLanguage>({ }); function parsePackageName(url: string): { packageName: string; readonly pathSegments: string[] } { - const parts = url.split('/'); - const hasScope = parts.length >= 2 && parts[0].startsWith('@'); + const parts = (url.startsWith('pkg:') ? url.slice(4) : url).split('/'); + const hasScope = parts.length >= 2 && parts[0][0] === '@'; const [nameOrScope, nameOrFirstPath, ...pathPart] = parts; const packageName = hasScope ? `${nameOrScope}/${nameOrFirstPath}` : nameOrScope; diff --git a/tests/legacy-cli/e2e.bzl b/tests/legacy-cli/e2e.bzl index 1bed11a775..2152f6dcd2 100644 --- a/tests/legacy-cli/e2e.bzl +++ b/tests/legacy-cli/e2e.bzl @@ -46,6 +46,7 @@ WEBPACK_IGNORE_TESTS = [ "tests/i18n/ivy-localize-app-shell.js", "tests/i18n/ivy-localize-app-shell-service-worker.js", "tests/commands/serve/ssr-http-requests-assets.js", + "tests/build/styles/sass-pkg-importer.js", "tests/build/prerender/http-requests-assets.js", "tests/build/prerender/error-with-sourcemaps.js", "tests/build/server-rendering/server-routes-*", diff --git a/tests/legacy-cli/e2e/tests/build/styles/sass-pkg-importer.ts b/tests/legacy-cli/e2e/tests/build/styles/sass-pkg-importer.ts new file mode 100644 index 0000000000..8fbb6e7431 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/sass-pkg-importer.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert'; +import { writeFile } from '../../../utils/fs'; +import { getActivePackageManager, uninstallPackage } from '../../../utils/packages'; +import { ng } from '../../../utils/process'; +import { isPrereleaseCli, updateJsonFile } from '../../../utils/project'; +import { appendFile } from 'node:fs/promises'; +import { getGlobalVariable } from '../../../utils/env'; + +export default async function () { + assert( + getGlobalVariable('argv')['esbuild'], + 'This test should not be called in the Webpack suite.', + ); + + // forcibly remove in case another test doesn't clean itself up + await uninstallPackage('@angular/material'); + + const isPrerelease = await isPrereleaseCli(); + const tag = isPrerelease ? '@next' : ''; + if (getActivePackageManager() === 'npm') { + await appendFile('.npmrc', '\nlegacy-peer-deps=true'); + } + + await ng('add', `@angular/material${tag}`, '--skip-confirmation'); + await Promise.all([ + updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = ['src/styles.scss']; + }), + writeFile('src/styles.scss', `@use 'pkg:@angular/material' as mat;`), + ]); + + await ng('build'); +}