diff --git a/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts index ebe3a8f2762f..a95017c0bd42 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts @@ -295,6 +295,36 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { harness.expectFile('dist/browser/media/logo.svg').toExist(); }); + it('should rebase a URL with interpolation using concatenation referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + $extra-var: "2"; + $postfix-var: "xyz"; + .a { + background-image: url("#{$my-var}logo#{$extra-var+ "-" + $postfix-var}.svg") + } + `, + 'src/theme/b.scss': `$my-var: "./images/";`, + 'src/theme/images/logo2-xyz.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/styles.css') + .content.toContain(`url("./media/logo2-xyz.svg")`); + harness.expectFile('dist/browser/media/logo2-xyz.svg').toExist(); + }); + it('should rebase a URL with an non-leading interpolation referencing a local resource', async () => { await harness.writeFiles({ 'src/styles.scss': `@use 'theme/a';`, diff --git a/packages/angular/build/src/tools/sass/rebasing-importer.ts b/packages/angular/build/src/tools/sass/rebasing-importer.ts index 533cefd0bdb8..68a69ae8dbb3 100644 --- a/packages/angular/build/src/tools/sass/rebasing-importer.ts +++ b/packages/angular/build/src/tools/sass/rebasing-importer.ts @@ -24,28 +24,6 @@ export interface DirectoryEntry { directories: Set; } -/** - * Ensures that a bare specifier URL path that is intended to be treated as - * a relative path has a leading `./` or `../` prefix. - * - * @param url A bare specifier URL path that should be considered relative. - * @returns - */ -function ensureRelative(url: string): string { - // Empty - if (!url) { - return url; - } - - // Already relative - if (url[0] === '.' && (url[1] === '/' || (url[1] === '.' && url[2] === '/'))) { - return url; - } - - // Needs prefix - return './' + url; -} - /** * A Sass Importer base class that provides the load logic to rebase all `url()` functions * within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler @@ -100,18 +78,15 @@ abstract class UrlRebasingImporter implements Importer<'sync'> { // Sass variable usage either starts with a `$` or contains a namespace and a `.$` const valueNormalized = value[0] === '$' || /^\w+\.\$/.test(value) ? `#{${value}}` : value; - const rebasedPath = - relative(this.entryDirectory, stylesheetDirectory) + '||file:' + valueNormalized; + const rebasedPath = relative(this.entryDirectory, stylesheetDirectory); // Normalize path separators and escape characters // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax - const rebasedUrl = ensureRelative( - rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&'), - ); + const rebasedUrl = rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&'); updatedContents ??= new MagicString(contents); // Always quote the URL to avoid potential downstream parsing problems - updatedContents.update(start, end, `"${rebasedUrl}"`); + updatedContents.update(start, end, `"${rebasedUrl}||file:${valueNormalized}"`); } if (updatedContents) {