From 18662f293be1ed092b1ac6ee6f3ac5c41b542c3b Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 17 Nov 2022 18:54:45 -0500 Subject: [PATCH] fix(@angular-devkit/build-angular): improve package deep import Sass index resolution in esbuild plugin When resolving Sass imports in the experimental esbuild-based browser application builder's Sass plugin, previously imported modules are used as the base for resolution attempts to workaround the lack of the importer file provided by Sass. When attempting to resolve a deep import into a package (including the potential Sass index files), these previously imported modules also need to be checked. This is particularly relevant when using the Yarn PnP or pnpm package managers which enforce strict dependency resolution. Fixes #24271 --- .../builders/browser-esbuild/sass-plugin.ts | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts index 0d3fd62e4ec0..1ef2613e1302 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts @@ -31,6 +31,30 @@ export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: stri return { name: 'angular-sass', setup(build: PluginBuild): void { + const resolveUrl = async (url: string, previousResolvedModules?: Set) => { + let result = await build.resolve(url, { + kind: 'import-rule', + // This should ideally be the directory of the importer file from Sass + // but that is not currently available from the Sass importer API. + resolveDir: build.initialOptions.absWorkingDir, + }); + + // Workaround to support Yarn PnP without access to the importer file from Sass + if (!result.path && previousResolvedModules?.size) { + for (const previous of previousResolvedModules) { + result = await build.resolve(url, { + kind: 'import-rule', + resolveDir: previous, + }); + if (result.path) { + break; + } + } + } + + return result; + }; + build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => { // Lazily load Sass when a Sass file is found sassWorkerPool ??= new SassWorkerImplementation(true); @@ -51,48 +75,30 @@ export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: stri url, { previousResolvedModules }: FileImporterWithRequestContextOptions, ): Promise => { - let result = await build.resolve(url, { - kind: 'import-rule', - // This should ideally be the directory of the importer file from Sass - // but that is not currently available from the Sass importer API. - resolveDir: build.initialOptions.absWorkingDir, - }); - - // Workaround to support Yarn PnP without access to the importer file from Sass - if (!result.path && previousResolvedModules?.size) { - for (const previous of previousResolvedModules) { - result = await build.resolve(url, { - kind: 'import-rule', - resolveDir: previous, - }); - } - } + const result = await resolveUrl(url, previousResolvedModules); // Check for package deep imports if (!result.path) { const parts = url.split('/'); - const hasScope = parts.length > 2 && parts[0].startsWith('@'); - if (hasScope || parts.length > 1) { - const [nameOrScope, nameOrFirstPath, ...pathPart] = parts; - const packageName = hasScope - ? `${nameOrScope}/${nameOrFirstPath}` - : nameOrScope; - const packageResult = await build.resolve(packageName + '/package.json', { - kind: 'import-rule', - // This should ideally be the directory of the importer file from Sass - // but that is not currently available from the Sass importer API. - resolveDir: build.initialOptions.absWorkingDir, - }); - - if (packageResult.path) { - return pathToFileURL( - join( - dirname(packageResult.path), - !hasScope ? nameOrFirstPath : '', - ...pathPart, - ), - ); - } + const hasScope = parts.length >= 2 && parts[0].startsWith('@'); + const [nameOrScope, nameOrFirstPath, ...pathPart] = parts; + const packageName = hasScope + ? `${nameOrScope}/${nameOrFirstPath}` + : nameOrScope; + + const packageResult = await resolveUrl( + packageName + '/package.json', + previousResolvedModules, + ); + + if (packageResult.path) { + return pathToFileURL( + join( + dirname(packageResult.path), + !hasScope ? nameOrFirstPath : '', + ...pathPart, + ), + ); } }