[heft-sass-plugin] Restore resolution of bare specifiers - #6094
Open
Ian Clanton-Thuon (iclanton) wants to merge 1 commit into
Open
Ian Clanton-Thuon (iclanton) wants to merge 1 commit into
Ian Clanton-Thuon (iclanton) wants to merge 1 commit into
Conversation
Since the move to the `pkg:` importer, a bare specifier such as `@use '@scope/pkg/theme'` only ever resolved relative to the importing file, so it failed with "Can't find stylesheet to import". There was no configuration option to change this, and the failing import is often inside a third-party package that the consuming project cannot edit. - Bare specifiers now fall back to the new `loadPaths` option and then to `node_modules` when they do not resolve relative to the importing file. Relative resolution still takes precedence, matching Sass semantics. - Package resolution failures return null instead of throwing, so Sass reports its usual error pointing at the offending line. - The legacy `~` rewrite is applied in the resolver rather than only by the `@use`/`@import`/`@forward` preprocessor, so it also works in constructs such as `meta.load-css()`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b66c2e44-e9ac-4611-8c45-85cb60478f7b
Ian Clanton-Thuon (iclanton)
force-pushed
the
fix-sass-bare-specifier-resolution
branch
from
September 25, 2026 01:09
47c1903 to
d4e4123
Compare
Bharat Middha (bmiddha)
approved these changes
Sep 25, 2026
David Michon (dmichon-msft)
requested changes
Sep 25, 2026
| "changes": [ | ||
| { | ||
| "packageName": "@rushstack/heft-sass-plugin", | ||
| "comment": "Fix a regression where a bare specifier such as `@use '@scope/pkg/theme'` could not be resolved. Bare specifiers now fall back to the new `loadPaths` option and then to `node_modules` when they do not resolve relative to the importing file. Also apply the legacy `~` rewrite in constructs other than `@use`/`@import`/`@forward`, such as `meta.load-css()`.", |
Contributor
There was a problem hiding this comment.
Per CSS and SCSS spec, @scope/pkg/theme is a relative path. Having @use perform node_module resolution is a deviation from the spec, so therefore introducing it cannot be considered "fixing a regression" when the behavior was 100% by design.
| // `node_modules`, matching the behavior of the Sass `loadPaths` option and `NodePackageImporter`. | ||
| // This form is what non-Heft Sass toolchains emit, so stylesheets inside third-party packages | ||
| // frequently use it and cannot be rewritten by the consuming project. | ||
| return await this.#canonicalizeBareSpecifierAsync(url, context); |
Contributor
There was a problem hiding this comment.
Please don't interpret a relative specifier as an external module specifier unless the configuration explicitly asks to, since, again, this is a deviation from the import spec (the target of @import is a URL).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses SharePoint/sp-dev-docs#11030
Summary
Restores resolution of bare specifiers in Sass stylesheets, e.g.
@use '@scope/pkg/theme'.Since the move to the
pkg:importer, a bare specifier only ever resolved relative to the importing file. When that failed, there was no fallback, so the build stopped withCan't find stylesheet to import. There is also no configuration option to influence resolution —loadPathswas never exposed, and the schema isadditionalProperties: false.The practical impact is that the failing
@useis frequently inside a third-party package, where the consuming project cannot rewrite it. A shared design-system package that ships Sass sources and uses bare specifiers internally cannot switch topkg:either, becausepkg:is only understood whereNodePackageImporteris configured — every other toolchain (Dart Sass CLI--load-path,sass-loader, Vite, Angular CLI) expects bare specifiers plus load paths.Reported downstream against SPFx 1.23 in microsoft/sp-dev-docs#11030 (confirmed and reproduced by the SharePoint team); the same resolution change reached SPFx via
@microsoft/spfx-web-build-rig, which consumes this plugin.Changes
#canonicalizeAsyncnow attempts resolution relative to the importing file first, and only if that fails and the specifier is bare, falls back to the newloadPathsoption and then tonode_modules. Relative resolution keeps precedence, matching Sass semantics.loadPathsoption inconfig/sass.json, resolved against the project folder. Analogous to the Sass compiler option of the same name, and provides the escape hatch that the schema previously lacked.nullrather than propagating, so Sass reports its usualCan't find stylesheet to importpointing at the offending line, instead of an internalCannot find package "...".~handled in the resolver. Previously the~→pkg:rewrite was done by a regex over@import/@use/@forwardonly, and any surviving tilde threwUnexpected tilde in URL. The rewrite now also happens during canonicalization, so~works in other constructs — most notably@include meta.load-css('~@scope/pkg').Testing
heft test— 64 passing, 7 new.The new tests cover resolution from
node_modules, resolution inside a dependency stylesheet (the reported scenario:meta.load-css('pkg:shared-styles')whereshared-styles/_index.scsscontains an uneditable bare@use),~insidemeta.load-css(), and resolution vialoadPaths.Three are guard tests rather than fix tests, pinning behavior that must not change: a file relative to the importer still wins over a same-named package; a specifier naming no installed package still produces the normal Sass diagnostic; and a bare specifier does not resolve from a folder that is not a configured load path.
I verified these tests fail for the right reason: reverting only the resolver change makes exactly the four fix-targeting tests fail — reproducing
Unexpected tilde in URL: ~shared-styles— while the three guard tests pass in both states, confirming nothing was quietly widened.Because
node_modules/is gitignored, the package fixture tree is generated at test time under the project'stemp/test/, so snapshots stay checkout-independent.Docs
README options table plus a rewritten "Sass import resolution" section documenting the resolution order and when to prefer
pkg:over a bare specifier;templates/sass.jsongains a commentedloadPathsentry.