Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "Fix sourceMap: true crashing on Linux/macOS when compiled .scss files use @use or @import",
"type": "patch",
"packageName": "@rushstack/heft-sass-plugin"
}
],
"packageName": "@rushstack/heft-sass-plugin",
"email": "cmalonzo@microsoft.com"
}
6 changes: 5 additions & 1 deletion heft-plugins/heft-sass-plugin/src/SassProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,10 @@ export class SassProcessor {

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

Expand Down Expand Up @@ -860,6 +863,7 @@ export class SassProcessor {
// Rewrite heft: URL sources to paths relative to the map file's directory
// so that source-map-loader can resolve them back to the original .scss.
const rewrittenSources: string[] = result.sourceMap.sources.map((source) => {
if (!source.startsWith('heft:')) return source;
const absoluteSourcePath: string = heftUrlToPath(source);
return Path.convertToSlashes(path.relative(mapDir, absoluteSourcePath));
});
Expand Down
18 changes: 18 additions & 0 deletions heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,24 @@ describe(SassProcessor.name, () => {
expect(parsedMap.sources[0]).toMatch(/classes-and-exports\.module\.scss$/);
});

it('emits a valid .css.map when the entry file @uses a partial (Linux/macOS regression)', async () => {
// use-with-partial.module.scss uses @use 'partial', which goes through loadAsync.
// Without sourceMapUrl on the ImporterResult, sass-embedded falls back to a data: URL
// for the partial in the source map; heftUrlToPath then crashes on Linux/macOS.
const { processor } = createProcessor(terminalProvider, { sourceMap: true });
await compileFixtureAsync(processor, 'use-with-partial.module.scss');

const mapPaths: string[] = getAllWrittenPathsMatching('.css.map');
expect(mapPaths).toHaveLength(1);

const mapJson: string = getWrittenFile('use-with-partial.module.css.map');
const parsedMap: { version: number; mappings: string; sources: string[] } = JSON.parse(mapJson);
expect(parsedMap.version).toBe(3);
expect(parsedMap.mappings).toBeTruthy();
// Both the entry file and the partial must resolve to real paths, not data: URLs
expect(parsedMap.sources.every((s: string) => !s.startsWith('data:'))).toBe(true);
});

it('does not emit .css.map or sourceMappingURL comment by default', async () => {
const { processor } = createProcessor(terminalProvider);
await compileFixtureAsync(processor, 'classes-and-exports.module.scss');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,35 @@ export default styles;",
}
`;

exports[`SassProcessor sourceMap option emits a valid .css.map when the entry file @uses a partial (Linux/macOS regression): terminal-output 1`] = `
Array [
"[verbose] Checking for changes to 1 files...[n]",
"[ log] Compiling 1 files...[n]",
]
`;

exports[`SassProcessor sourceMap option emits a valid .css.map when the entry file @uses a partial (Linux/macOS regression): written-files 1`] = `
Map {
"/fake/output/dts/use-with-partial.module.scss.d.ts" => "declare interface IStyles {
container: string;
header: string;
}
declare const styles: IStyles;
export default styles;",
"/fake/output/css/use-with-partial.module.css" => ".container {
color: #0078d4;
padding: 8px;
}

.header {
border-bottom: 1px solid #0078d4;
}
/*# sourceMappingURL=use-with-partial.module.css.map */
",
"/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\\"}",
}
`;

exports[`SassProcessor sourceMap option uses the correct map filename when doNotTrimOriginalFileExtension is true: terminal-output 1`] = `
Array [
"[verbose] Checking for changes to 1 files...[n]",
Expand Down
Loading