Target SharePoint environment
SharePoint Online
SharePoint development model
💥 SharePoint Framework
Developer environment
macOS, windows, linux
Additional environment details
I reproduced this on two SPFx projects — one scaffolded and pinned to the SPFx 1.22 toolchain, one to SPFx 1.23 — and verified the versions from each project's node_modules:
|
SPFx 1.22 project |
SPFx 1.23 project |
SPFx (@microsoft/spfx-web-build-rig) |
1.22.2 |
1.23.0 |
@rushstack/heft-sass-plugin |
0.15.24 |
1.3.8 |
@rushstack/heft |
1.1.2 |
1.2.17 |
sass-embedded |
1.77.8 |
1.85.1 |
| Node.js |
22.22.0 |
22.22.0 |
@microsoft/spfx-web-build-rig@1.22.2 pins @rushstack/heft-sass-plugin@0.15.24; @1.23.0 pins @1.3.8 — so this is the version pair every SPFx project gets on each release. The reference dependency I imported in the repro is @n8d/htwoo-core@2.7.1.
Describe the bug / error
Between the @rushstack/heft-sass-plugin version SPFx 1.22 ships (0.15.24) and the one SPFx 1.23 ships (1.3.8), the plugin's Sass import-resolution model was rewritten — from Sass loadPaths + a tilde (~) importer to the pkg: scheme plus a tilde-to-pkg: text rewrite. pkg: is the modern Sass mechanism, so the direction is fine, but the change isn't documented anywhere I could find: not in the plugin CHANGELOG, not in the SPFx 1.23 release notes, not in Configure Sass processing during builds, and not in Migrate from the Gulp Toolchain to Heft Toolchain — even though that migration guide's whole job is to spell out what changes on upgrade.
Three things that worked in SPFx 1.22 fail in SPFx 1.23:
|
Import form |
SPFx 1.22 (0.15.24) |
SPFx 1.23 (1.3.8) |
| D |
bare specifier — @use '@scope/pkg/...' |
works |
Can't find stylesheet to import |
| E |
~ inside @include meta.load-css('~@scope/pkg/...') |
works |
Unexpected tilde in URL |
| G |
config/sass.json with importIncludePaths |
valid option |
build fails config validation |
E is the one that really catches you out. ~ still works in @use, @import and @forward, so it's natural to assume ~ is just supported — until you use it in meta.load-css() and the build throws.
Why E happens
In 1.3.8, ~ is no longer a Sass importer. It's rewritten to pkg: by a regular expression that runs over the source before compilation (SassProcessor.js, line 56):
const importTildeRegex = /^(\s*@(?:import|use|forward)\s*)('~(?:[^']+)'|"~(?:[^"]+)")/gm;
That regex only matches @import, @use and @forward. @include meta.load-css('~…') doesn't start with any of those, so the ~ is never rewritten — and when it reaches the importer it is rejected outright (SassProcessor.js, line 356):
throw new Error(`Unexpected tilde in URL: ${url} in context: ${context.containingUrl?.href}`);
In 0.15.24, ~ was a genuine Sass importer, and Sass consults importers for meta.load-css() too — so it worked everywhere. Bare specifiers (D) worked in 0.15.24 because the plugin passed loadPaths: [<project>/node_modules, <project>/src]; 1.3.8 passes no loadPaths at all. importIncludePaths (G) was a documented config/sass.json option in 0.15.24's schema; 1.3.8 removed it, and the schema is additionalProperties: false, so setting it now fails validation.
Steps to reproduce
- Create (or upgrade to) an SPFx 1.23 project and install any package that ships SCSS partials — e.g.
npm install @n8d/htwoo-core.
- Add a
.global.scss file under src/webparts/<wp>/components/ containing:
@use 'sass:meta';
.x { @include meta.load-css('~@n8d/htwoo-core/lib/components/all'); }
- Run
heft build. The build fails:
[build:sass] Error: src/webparts/.../X.global.scss:2:11 - Error: Unexpected tilde in URL:
~@n8d/htwoo-core/lib/components/all in context: heft:/.../X.global.scss
-------------------- Failed --------------------
- Add another file with a bare specifier —
@use '@n8d/htwoo-core/lib/components/all'; — and heft build fails with Can't find stylesheet to import.
- Add
"importIncludePaths": ["node_modules", "src"] to config/sass.json and heft build fails before compiling anything:
[build:sass] Error: Resolved configuration object does not match schema: ... JSON validation failed
[build:sass] must NOT have additional properties: importIncludePaths
- Build the same files on an SPFx 1.22 project — all three succeed.
Full cross-version reproduction
I compiled six identical fixture files (Matrix{A..F}.global.scss) with each project's own heft build. Import target: @n8d/htwoo-core/lib/components/all.
| Fixture |
Statement |
SPFx 1.22 |
SPFx 1.23 |
| MatrixA |
@import '~…/all' |
PASS |
PASS |
| MatrixB |
@use '~…/all' |
PASS |
PASS |
| MatrixC |
@use 'pkg:…/all' |
FAIL |
PASS (new in 1.23) |
| MatrixD |
@use '…/all' (bare specifier) |
PASS |
FAIL |
| MatrixE |
@include meta.load-css('~…/all') |
PASS |
FAIL |
| MatrixF |
@include meta.load-css('pkg:…/all') |
FAIL |
PASS (new in 1.23) |
SPFx 1.23 — heft build (plugin 1.3.8):
[build:sass] Compiling 7 files...
[build:sass] Error: src/webparts/.../MatrixD.global.scss:1:0 - Can't find stylesheet to import.
[build:sass] @use '@n8d/htwoo-core/lib/components/all';
[build:sass] Error: src/webparts/.../MatrixE.global.scss:2:11 - Error: Unexpected tilde in URL:
~@n8d/htwoo-core/lib/components/all in context: heft:/.../MatrixE.global.scss
-------------------- Failed (4.352s) --------------------
Only MatrixD and MatrixE errored. A, B, C, F compiled fine.
SPFx 1.22 — heft build (plugin 0.15.24):
Error occurred parsing and generating typings for file ".../MatrixC.global.scss": ... Can't find stylesheet to import.
Error occurred parsing and generating typings for file ".../MatrixF.global.scss": ... Can't find stylesheet to import.
[build:sass] Generated sass typings
Only MatrixC and MatrixF errored (0.15.24 has no pkg: importer). MatrixA, B, D and E all compiled — confirming D and E are genuine regressions, not pre-existing failures.
Two more things the same builds surfaced
The rig still advertises the removed option. @microsoft/spfx-web-build-rig@1.23.0 ships this in node_modules/@microsoft/spfx-web-build-rig/profiles/default/config/sass.json (the file your project's config/sass.json extends — not your project file itself):
Un-comment that and the build fails the schema validation above — the option it documents no longer exists.
A 1.23-scaffolded project will not build on 1.22. The webpart SCSS the 1.23 generator emits starts with @import 'pkg:@fluentui/react/dist/sass/References.scss'; (or pkg:@microsoft/sp-office-ui-fabric-core/... for the no-framework template). That pkg: line fails on SPFx 1.22, since 0.15.24 has no pkg: importer. (See also #10833 on the scaffold's deprecated @import.)
Expected behavior
The behavior change itself is reasonable — pkg: is the right modern mechanism. The problem is that it's undocumented, and ~ is left half-working (fine in @use/@import/@forward, broken in @include meta.load-css()), which is exactly the kind of thing that turns a 1.22→1.23 upgrade into mystery build failures.
I'd expect the docs to cover it:
- The SPFx 1.23 release notes and the Configure Sass processing during builds page should state that the resolution model changed —
loadPaths/importIncludePaths are gone, pkg: is the supported scheme, and ~ is only auto-rewritten in @use/@import/@forward.
- The Migrate from the Gulp Toolchain to Heft Toolchain guide should include a Sass step: replace
@include meta.load-css('~…') and bare @use '@scope/pkg/…' with the pkg: form, and remove importIncludePaths from config/sass.json.
- The stale
importIncludePaths comment should be removed from spfx-web-build-rig's sass.json.
Workaround for anyone hitting this today
Use pkg: for every npm-package import — it's the one form that works in @use, @import, @forward and meta.load-css():
@include meta.load-css('~@scope/pkg/...') → @include meta.load-css('pkg:@scope/pkg/...')
- bare
@use '@scope/pkg/...' → @use 'pkg:@scope/pkg/...'
- delete
importIncludePaths from config/sass.json
References
Target SharePoint environment
SharePoint Online
SharePoint development model
💥 SharePoint Framework
Developer environment
macOS, windows, linux
Additional environment details
I reproduced this on two SPFx projects — one scaffolded and pinned to the SPFx 1.22 toolchain, one to SPFx 1.23 — and verified the versions from each project's
node_modules:@microsoft/spfx-web-build-rig)@rushstack/heft-sass-plugin@rushstack/heftsass-embedded@microsoft/spfx-web-build-rig@1.22.2pins@rushstack/heft-sass-plugin@0.15.24;@1.23.0pins@1.3.8— so this is the version pair every SPFx project gets on each release. The reference dependency I imported in the repro is@n8d/htwoo-core@2.7.1.Describe the bug / error
Between the
@rushstack/heft-sass-pluginversion SPFx 1.22 ships (0.15.24) and the one SPFx 1.23 ships (1.3.8), the plugin's Sass import-resolution model was rewritten — from SassloadPaths+ a tilde (~) importer to thepkg:scheme plus a tilde-to-pkg:text rewrite.pkg:is the modern Sass mechanism, so the direction is fine, but the change isn't documented anywhere I could find: not in the pluginCHANGELOG, not in the SPFx 1.23 release notes, not in Configure Sass processing during builds, and not in Migrate from the Gulp Toolchain to Heft Toolchain — even though that migration guide's whole job is to spell out what changes on upgrade.Three things that worked in SPFx 1.22 fail in SPFx 1.23:
@use '@scope/pkg/...'Can't find stylesheet to import~inside@include meta.load-css('~@scope/pkg/...')Unexpected tilde in URLconfig/sass.jsonwithimportIncludePathsE is the one that really catches you out.
~still works in@use,@importand@forward, so it's natural to assume~is just supported — until you use it inmeta.load-css()and the build throws.Why E happens
In 1.3.8,
~is no longer a Sass importer. It's rewritten topkg:by a regular expression that runs over the source before compilation (SassProcessor.js, line 56):That regex only matches
@import,@useand@forward.@include meta.load-css('~…')doesn't start with any of those, so the~is never rewritten — and when it reaches the importer it is rejected outright (SassProcessor.js, line 356):In 0.15.24,
~was a genuine Sass importer, and Sass consults importers formeta.load-css()too — so it worked everywhere. Bare specifiers (D) worked in 0.15.24 because the plugin passedloadPaths: [<project>/node_modules, <project>/src]; 1.3.8 passes noloadPathsat all.importIncludePaths(G) was a documentedconfig/sass.jsonoption in 0.15.24's schema; 1.3.8 removed it, and the schema isadditionalProperties: false, so setting it now fails validation.Steps to reproduce
npm install @n8d/htwoo-core..global.scssfile undersrc/webparts/<wp>/components/containing:heft build. The build fails:@use '@n8d/htwoo-core/lib/components/all';— andheft buildfails withCan't find stylesheet to import."importIncludePaths": ["node_modules", "src"]toconfig/sass.jsonandheft buildfails before compiling anything:Full cross-version reproduction
I compiled six identical fixture files (
Matrix{A..F}.global.scss) with each project's ownheft build. Import target:@n8d/htwoo-core/lib/components/all.@import '~…/all'@use '~…/all'@use 'pkg:…/all'@use '…/all'(bare specifier)@include meta.load-css('~…/all')@include meta.load-css('pkg:…/all')SPFx 1.23 —
heft build(plugin 1.3.8):Only MatrixD and MatrixE errored. A, B, C, F compiled fine.
SPFx 1.22 —
heft build(plugin 0.15.24):Only MatrixC and MatrixF errored (0.15.24 has no
pkg:importer). MatrixA, B, D and E all compiled — confirming D and E are genuine regressions, not pre-existing failures.Two more things the same builds surfaced
The rig still advertises the removed option.
@microsoft/spfx-web-build-rig@1.23.0ships this innode_modules/@microsoft/spfx-web-build-rig/profiles/default/config/sass.json(the file your project'sconfig/sass.jsonextends — not your project file itself):Un-comment that and the build fails the schema validation above — the option it documents no longer exists.
A 1.23-scaffolded project will not build on 1.22. The webpart SCSS the 1.23 generator emits starts with
@import 'pkg:@fluentui/react/dist/sass/References.scss';(orpkg:@microsoft/sp-office-ui-fabric-core/...for the no-framework template). Thatpkg:line fails on SPFx 1.22, since 0.15.24 has nopkg:importer. (See also #10833 on the scaffold's deprecated@import.)Expected behavior
The behavior change itself is reasonable —
pkg:is the right modern mechanism. The problem is that it's undocumented, and~is left half-working (fine in@use/@import/@forward, broken in@include meta.load-css()), which is exactly the kind of thing that turns a 1.22→1.23 upgrade into mystery build failures.I'd expect the docs to cover it:
loadPaths/importIncludePathsare gone,pkg:is the supported scheme, and~is only auto-rewritten in@use/@import/@forward.@include meta.load-css('~…')and bare@use '@scope/pkg/…'with thepkg:form, and removeimportIncludePathsfromconfig/sass.json.importIncludePathscomment should be removed fromspfx-web-build-rig'ssass.json.Workaround for anyone hitting this today
Use
pkg:for every npm-package import — it's the one form that works in@use,@import,@forwardandmeta.load-css():@include meta.load-css('~@scope/pkg/...')→@include meta.load-css('pkg:@scope/pkg/...')@use '@scope/pkg/...'→@use 'pkg:@scope/pkg/...'importIncludePathsfromconfig/sass.jsonReferences
@rushstack/heft-sass-plugin@0.15.24/@1.3.8SassProcessor.js—importTildeRegex(line 56),nonCanonicalScheme: 'pkg'(line 103),Unexpected tilde in URLthrow (line 356)@rushstack/heft-sass-plugin@1.3.8heft-sass-plugin.schema.json—additionalProperties: false, noimportIncludePathspkg:importers — https://sass-lang.com/blog/announcing-pkg-importers/meta.load-css()— https://sass-lang.com/documentation/modules/meta/#load-css