Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): improve package deep import Sass …
Browse files Browse the repository at this point in the history
…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

(cherry picked from commit 67752a4)
  • Loading branch information
clydin committed Nov 21, 2022
1 parent 52f63b1 commit 19f5cc7
Showing 1 changed file with 44 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>) => {
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);
Expand All @@ -51,48 +75,30 @@ export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: stri
url,
{ previousResolvedModules }: FileImporterWithRequestContextOptions,
): Promise<URL | null> => {
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,
),
);
}
}

Expand Down

0 comments on commit 19f5cc7

Please sign in to comment.