Skip to content

[SPFx 1.22→1.23] Sass import resolution changed — ~ breaks in meta.load-css(), bare specifiers and importIncludePaths no longer work (undocumented) #10854

@StfBauer

Description

@StfBauer

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

  1. Create (or upgrade to) an SPFx 1.23 project and install any package that ships SCSS partials — e.g. npm install @n8d/htwoo-core.
  2. 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'); }
  3. 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 --------------------
    
  4. 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.
  5. 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
    
  6. 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):

/**
 * A list of paths used when resolving Sass imports.  The paths should be relative to the project root.
 *
 * Default value: ["node_modules", "src"]
 */
// "importIncludePaths": ["node_modules", "src"],

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

Metadata

Metadata

Assignees

Labels

Needs: Triage 🔍Awaiting categorization and initial review.area:spfxCategory: SharePoint Framework (not extensions related)area:toolingCategory: Development tooling

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions