From 0014459820dc1c127e93993414c154947a7f8da6 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 3 Nov 2022 15:56:59 -0400 Subject: [PATCH] fix(@angular-devkit/build-angular): account for package.json exports with Sass in esbuild builder When using the experimental esbuild-based browser application builder, Sass module imports will now resolve using esbuild's resolve methods. This allows for package.json exports and main fields to be recognized when resolving an import or use rules in Sass files (scss or sass file extensions). (cherry picked from commit 0662a2e9eb116915718d512876bdacab929a6ed9) --- .../builders/browser-esbuild/sass-plugin.ts | 65 ++++++++++++++++++- .../builders/browser-esbuild/stylesheets.ts | 6 +- 2 files changed, 63 insertions(+), 8 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 7baeba226de5..1f7aab5a38f4 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 @@ -8,12 +8,15 @@ import type { PartialMessage, Plugin, PluginBuild } from 'esbuild'; import { readFile } from 'node:fs/promises'; -import { dirname, relative } from 'node:path'; +import { dirname, join, relative } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import type { CompileResult, Exception } from 'sass'; -import { SassWorkerImplementation } from '../../sass/sass-service'; +import { + FileImporterWithRequestContextOptions, + SassWorkerImplementation, +} from '../../sass/sass-service'; -let sassWorkerPool: SassWorkerImplementation; +let sassWorkerPool: SassWorkerImplementation | undefined; function isSassException(error: unknown): error is Exception { return !!error && typeof error === 'object' && 'sassMessage' in error; @@ -21,6 +24,7 @@ function isSassException(error: unknown): error is Exception { export function shutdownSassWorkerPool(): void { sassWorkerPool?.close(); + sassWorkerPool = undefined; } export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: string[] }): Plugin { @@ -41,6 +45,61 @@ export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: stri sourceMap: options.sourcemap, sourceMapIncludeSources: options.sourcemap, quietDeps: true, + importers: [ + { + findFileUrl: async ( + 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, + }); + } + } + + // 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, + ), + ); + } + } + } + + return result.path ? pathToFileURL(result.path) : null; + }, + }, + ], logger: { warn: (text, { deprecation, span }) => { warnings.push({ diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts index 2bfaf70f867c..7c58c8aaaaa4 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts @@ -27,10 +27,6 @@ async function bundleStylesheet( entry: Required | Pick>, options: BundleStylesheetOptions, ) { - const loadPaths = options.includePaths ?? []; - // Needed to resolve node packages. - loadPaths.push(path.join(options.workspaceRoot, 'node_modules')); - // Execute esbuild const result = await bundle({ ...entry, @@ -50,7 +46,7 @@ async function bundleStylesheet( conditions: ['style', 'sass'], mainFields: ['style', 'sass'], plugins: [ - createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths }), + createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths: options.includePaths }), createCssResourcePlugin(), ], });