Skip to content

Commit c5fb990

Browse files
authored
fix(heft-sass-plugin): set sourceMapUrl in loadAsync to prevent data:… (#5814)
* fix(heft-sass-plugin): set sourceMapUrl in loadAsync to prevent data: URL crash on Linux/macOS * Remove test --------- Co-authored-by: Camille Malonzo <cmalonzo@users.noreply.github.com>
1 parent 4e14a4c commit c5fb990

4 files changed

Lines changed: 63 additions & 1 deletion

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Fix sourceMap: true crashing on Linux/macOS when compiled .scss files use @use or @import",
5+
"type": "patch",
6+
"packageName": "@rushstack/heft-sass-plugin"
7+
}
8+
],
9+
"packageName": "@rushstack/heft-sass-plugin",
10+
"email": "cmalonzo@microsoft.com"
11+
}

heft-plugins/heft-sass-plugin/src/SassProcessor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,10 @@ export class SassProcessor {
242242

243243
return {
244244
contents: record.content,
245-
syntax: determineSyntaxFromFilePath(absolutePath)
245+
syntax: determineSyntaxFromFilePath(absolutePath),
246+
// Without sourceMapUrl, sass-embedded falls back to a data: URL for this file in the
247+
// source map. data: URLs crash heftUrlToPath on Linux/macOS (non-empty URL host).
248+
sourceMapUrl: url
246249
};
247250
};
248251

@@ -860,6 +863,7 @@ export class SassProcessor {
860863
// Rewrite heft: URL sources to paths relative to the map file's directory
861864
// so that source-map-loader can resolve them back to the original .scss.
862865
const rewrittenSources: string[] = result.sourceMap.sources.map((source) => {
866+
if (!source.startsWith('heft:')) return source;
863867
const absoluteSourcePath: string = heftUrlToPath(source);
864868
return Path.convertToSlashes(path.relative(mapDir, absoluteSourcePath));
865869
});

heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,24 @@ describe(SassProcessor.name, () => {
759759
expect(parsedMap.sources[0]).toMatch(/classes-and-exports\.module\.scss$/);
760760
});
761761

762+
it('emits a valid .css.map when the entry file @uses a partial (Linux/macOS regression)', async () => {
763+
// use-with-partial.module.scss uses @use 'partial', which goes through loadAsync.
764+
// Without sourceMapUrl on the ImporterResult, sass-embedded falls back to a data: URL
765+
// for the partial in the source map; heftUrlToPath then crashes on Linux/macOS.
766+
const { processor } = createProcessor(terminalProvider, { sourceMap: true });
767+
await compileFixtureAsync(processor, 'use-with-partial.module.scss');
768+
769+
const mapPaths: string[] = getAllWrittenPathsMatching('.css.map');
770+
expect(mapPaths).toHaveLength(1);
771+
772+
const mapJson: string = getWrittenFile('use-with-partial.module.css.map');
773+
const parsedMap: { version: number; mappings: string; sources: string[] } = JSON.parse(mapJson);
774+
expect(parsedMap.version).toBe(3);
775+
expect(parsedMap.mappings).toBeTruthy();
776+
// Both the entry file and the partial must resolve to real paths, not data: URLs
777+
expect(parsedMap.sources.every((s: string) => !s.startsWith('data:'))).toBe(true);
778+
});
779+
762780
it('does not emit .css.map or sourceMappingURL comment by default', async () => {
763781
const { processor } = createProcessor(terminalProvider);
764782
await compileFixtureAsync(processor, 'classes-and-exports.module.scss');

heft-plugins/heft-sass-plugin/src/test/__snapshots__/SassProcessor.test.ts.snap

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,35 @@ export default styles;",
13311331
}
13321332
`;
13331333

1334+
exports[`SassProcessor sourceMap option emits a valid .css.map when the entry file @uses a partial (Linux/macOS regression): terminal-output 1`] = `
1335+
Array [
1336+
"[verbose] Checking for changes to 1 files...[n]",
1337+
"[ log] Compiling 1 files...[n]",
1338+
]
1339+
`;
1340+
1341+
exports[`SassProcessor sourceMap option emits a valid .css.map when the entry file @uses a partial (Linux/macOS regression): written-files 1`] = `
1342+
Map {
1343+
"/fake/output/dts/use-with-partial.module.scss.d.ts" => "declare interface IStyles {
1344+
container: string;
1345+
header: string;
1346+
}
1347+
declare const styles: IStyles;
1348+
export default styles;",
1349+
"/fake/output/css/use-with-partial.module.css" => ".container {
1350+
color: #0078d4;
1351+
padding: 8px;
1352+
}
1353+
1354+
.header {
1355+
border-bottom: 1px solid #0078d4;
1356+
}
1357+
/*# sourceMappingURL=use-with-partial.module.css.map */
1358+
",
1359+
"/fake/output/css/use-with-partial.module.css.map" => "{\\"version\\":3,\\"sourceRoot\\":\\"\\",\\"sources\\":[\\"fixtures/use-with-partial.module.scss\\",\\"fixtures/_partial.scss\\"],\\"names\\":[],\\"mappings\\":\\"AAIA;EACE,OCHY;EDIZ;;;AAGF;EACE\\",\\"sourcesContent\\":[\\"// Uses the modern @use syntax to import variables from a local partial.\\\\n// Verifies that SassProcessor resolves _partial.scss when @use 'partial' is written.\\\\n@use 'partial' as tokens;\\\\n\\\\n.container {\\\\n color: tokens.$brand-color;\\\\n padding: tokens.$spacing-unit * 2;\\\\n}\\\\n\\\\n.header {\\\\n border-bottom: 1px solid tokens.$brand-color;\\\\n}\\\\n\\",\\"// Sass partial exposing shared design tokens.\\\\n// Imported via @use 'partial' in use-with-partial.module.scss.\\\\n$brand-color: #0078d4;\\\\n$spacing-unit: 4px;\\\\n\\"],\\"file\\":\\"use-with-partial.module.css\\"}",
1360+
}
1361+
`;
1362+
13341363
exports[`SassProcessor sourceMap option uses the correct map filename when doNotTrimOriginalFileExtension is true: terminal-output 1`] = `
13351364
Array [
13361365
"[verbose] Checking for changes to 1 files...[n]",

0 commit comments

Comments
 (0)