diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts index 2a9385641126..5c2d1fbb7f16 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts @@ -423,12 +423,30 @@ function getSassResolutionImporter( }); return { - findFileUrl: (url, { fromImport }): Promise => { + findFileUrl: async (url, { fromImport }): Promise => { + if (url.charAt(0) === '.') { + // Let Sass handle relative imports. + return null; + } + + let file: string | undefined; const resolve = fromImport ? resolveImport : resolveModule; - return resolve(root, url) - .then((file) => pathToFileURL(file)) - .catch(() => null); + try { + file = await resolve(root, url); + } catch { + // Try to resolve a partial file + // @use '@material/button/button' as mdc-button; + // `@material/button/button` -> `@material/button/_button` + const lastSlashIndex = url.lastIndexOf('/'); + const underscoreIndex = lastSlashIndex + 1; + if (underscoreIndex > 0 && url.charAt(underscoreIndex) !== '_') { + const partialFileUrl = `${url.slice(0, underscoreIndex)}_${url.slice(underscoreIndex)}`; + file = await resolve(root, partialFileUrl).catch(() => undefined); + } + } + + return file ? pathToFileURL(file) : null; }, }; } diff --git a/tests/legacy-cli/e2e/tests/build/styles/scss-partial-resolution.ts b/tests/legacy-cli/e2e/tests/build/styles/scss-partial-resolution.ts new file mode 100644 index 000000000000..f3863ad72adb --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/scss-partial-resolution.ts @@ -0,0 +1,31 @@ +import { installPackage } from '../../../utils/packages'; +import { writeMultipleFiles, deleteFile, replaceInFile } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +export default async function () { + // Supports resolving node_modules with are pointing to partial files partial files. + // @material/button/button below points to @material/button/_button.scss + // https://unpkg.com/browse/@material/button@14.0.0/_button.scss + + await installPackage('@material/button@14.0.0'); + + await writeMultipleFiles({ + 'src/styles.scss': ` + @use '@material/button/button' as mat; + `, + 'src/app/app.component.scss': ` + @use '@material/button/button' as mat; + `, + }); + + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = ['src/styles.scss']; + }); + + await deleteFile('src/app/app.component.css'); + await replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss'); + + await ng('build', '--configuration=development'); +}