Skip to content

Commit 0662a2e

Browse files
clydindgp1130
authored andcommitted
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).
1 parent 18b72a8 commit 0662a2e

File tree

2 files changed

+63
-8
lines changed

2 files changed

+63
-8
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts

+62-3
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@
88

99
import type { PartialMessage, Plugin, PluginBuild } from 'esbuild';
1010
import { readFile } from 'node:fs/promises';
11-
import { dirname, relative } from 'node:path';
11+
import { dirname, join, relative } from 'node:path';
1212
import { fileURLToPath, pathToFileURL } from 'node:url';
1313
import type { CompileResult, Exception } from 'sass';
14-
import { SassWorkerImplementation } from '../../sass/sass-service';
14+
import {
15+
FileImporterWithRequestContextOptions,
16+
SassWorkerImplementation,
17+
} from '../../sass/sass-service';
1518

16-
let sassWorkerPool: SassWorkerImplementation;
19+
let sassWorkerPool: SassWorkerImplementation | undefined;
1720

1821
function isSassException(error: unknown): error is Exception {
1922
return !!error && typeof error === 'object' && 'sassMessage' in error;
2023
}
2124

2225
export function shutdownSassWorkerPool(): void {
2326
sassWorkerPool?.close();
27+
sassWorkerPool = undefined;
2428
}
2529

2630
export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: string[] }): Plugin {
@@ -41,6 +45,61 @@ export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: stri
4145
sourceMap: options.sourcemap,
4246
sourceMapIncludeSources: options.sourcemap,
4347
quietDeps: true,
48+
importers: [
49+
{
50+
findFileUrl: async (
51+
url,
52+
{ previousResolvedModules }: FileImporterWithRequestContextOptions,
53+
): Promise<URL | null> => {
54+
let result = await build.resolve(url, {
55+
kind: 'import-rule',
56+
// This should ideally be the directory of the importer file from Sass
57+
// but that is not currently available from the Sass importer API.
58+
resolveDir: build.initialOptions.absWorkingDir,
59+
});
60+
61+
// Workaround to support Yarn PnP without access to the importer file from Sass
62+
if (!result.path && previousResolvedModules?.size) {
63+
for (const previous of previousResolvedModules) {
64+
result = await build.resolve(url, {
65+
kind: 'import-rule',
66+
resolveDir: previous,
67+
});
68+
}
69+
}
70+
71+
// Check for package deep imports
72+
if (!result.path) {
73+
const parts = url.split('/');
74+
const hasScope = parts.length > 2 && parts[0].startsWith('@');
75+
if (hasScope || parts.length > 1) {
76+
const [nameOrScope, nameOrFirstPath, ...pathPart] = parts;
77+
const packageName = hasScope
78+
? `${nameOrScope}/${nameOrFirstPath}`
79+
: nameOrScope;
80+
const packageResult = await build.resolve(packageName + '/package.json', {
81+
kind: 'import-rule',
82+
// This should ideally be the directory of the importer file from Sass
83+
// but that is not currently available from the Sass importer API.
84+
resolveDir: build.initialOptions.absWorkingDir,
85+
});
86+
87+
if (packageResult.path) {
88+
return pathToFileURL(
89+
join(
90+
dirname(packageResult.path),
91+
!hasScope ? nameOrFirstPath : '',
92+
...pathPart,
93+
),
94+
);
95+
}
96+
}
97+
}
98+
99+
return result.path ? pathToFileURL(result.path) : null;
100+
},
101+
},
102+
],
44103
logger: {
45104
warn: (text, { deprecation, span }) => {
46105
warnings.push({

packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ async function bundleStylesheet(
2727
entry: Required<Pick<BuildOptions, 'stdin'> | Pick<BuildOptions, 'entryPoints'>>,
2828
options: BundleStylesheetOptions,
2929
) {
30-
const loadPaths = options.includePaths ?? [];
31-
// Needed to resolve node packages.
32-
loadPaths.push(path.join(options.workspaceRoot, 'node_modules'));
33-
3430
// Execute esbuild
3531
const result = await bundle({
3632
...entry,
@@ -50,7 +46,7 @@ async function bundleStylesheet(
5046
conditions: ['style', 'sass'],
5147
mainFields: ['style', 'sass'],
5248
plugins: [
53-
createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths }),
49+
createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths: options.includePaths }),
5450
createCssResourcePlugin(),
5551
],
5652
});

0 commit comments

Comments
 (0)