Skip to content

Commit 769a06e

Browse files
fix: generate absolute sources for source maps (#882)
BREAKING CHANGE: loader generates absolute `sources` in source maps, avoid modifying `sass` source maps if the `sourceMap` option is `false`
1 parent d2b532c commit 769a06e

File tree

3 files changed

+65
-18
lines changed

3 files changed

+65
-18
lines changed

src/index.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getSassOptions,
1010
getWebpackImporter,
1111
getRenderFunctionFromSassImplementation,
12+
absolutifySourceMapSource,
1213
} from './utils';
1314
import SassError from './SassError';
1415

@@ -27,8 +28,15 @@ function loader(content) {
2728
});
2829

2930
const implementation = getSassImplementation(options.implementation);
30-
const sassOptions = getSassOptions(this, options, content, implementation);
31-
31+
const useSourceMap =
32+
typeof options.sourceMap === 'boolean' ? options.sourceMap : this.sourceMap;
33+
const sassOptions = getSassOptions(
34+
this,
35+
options,
36+
content,
37+
implementation,
38+
useSourceMap
39+
);
3240
const shouldUseWebpackImporter =
3341
typeof options.webpackImporter === 'boolean'
3442
? options.webpackImporter
@@ -58,7 +66,8 @@ function loader(content) {
5866
return;
5967
}
6068

61-
if (result.map) {
69+
// Modify source paths only for webpack, otherwise we do nothing
70+
if (result.map && useSourceMap) {
6271
// eslint-disable-next-line no-param-reassign
6372
result.map = JSON.parse(result.map);
6473

@@ -67,13 +76,16 @@ function loader(content) {
6776
// eslint-disable-next-line no-param-reassign
6877
delete result.map.file;
6978

79+
// eslint-disable-next-line no-param-reassign
80+
result.sourceRoot = '';
81+
7082
// node-sass returns POSIX paths, that's why we need to transform them back to native paths.
7183
// This fixes an error on windows where the source-map module cannot resolve the source maps.
7284
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
7385
// eslint-disable-next-line no-param-reassign
74-
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
75-
// eslint-disable-next-line no-param-reassign
76-
result.map.sources = result.map.sources.map(path.normalize);
86+
result.map.sources = result.map.sources.map((source) =>
87+
absolutifySourceMapSource(this.rootContext, source)
88+
);
7789
}
7890

7991
result.stats.includedFiles.forEach((includedFile) => {

src/utils.js

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,16 @@ function proxyCustomImporters(importers, loaderContext) {
9393
* @param {object} loaderOptions
9494
* @param {string} content
9595
* @param {object} implementation
96+
* @param {boolean} useSourceMap
9697
* @returns {Object}
9798
*/
98-
function getSassOptions(loaderContext, loaderOptions, content, implementation) {
99+
function getSassOptions(
100+
loaderContext,
101+
loaderOptions,
102+
content,
103+
implementation,
104+
useSourceMap
105+
) {
99106
const options = klona(
100107
loaderOptions.sassOptions
101108
? typeof loaderOptions.sassOptions === 'function'
@@ -143,23 +150,19 @@ function getSassOptions(loaderContext, loaderOptions, content, implementation) {
143150
options.outputStyle = 'compressed';
144151
}
145152

146-
const useSourceMap =
147-
typeof loaderOptions.sourceMap === 'boolean'
148-
? loaderOptions.sourceMap
149-
: loaderContext.sourceMap;
150-
151153
if (useSourceMap) {
152154
// Deliberately overriding the sourceMap option here.
153155
// node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
154156
// In case it is a string, options.sourceMap should be a path where the source map is written.
155157
// But since we're using the data option, the source map will not actually be written, but
156158
// all paths in sourceMap.sources will be relative to that path.
157159
// Pretty complicated... :(
158-
options.sourceMap = path.join(process.cwd(), '/sass.css.map');
159-
options.sourceMapRoot = process.cwd();
160+
options.sourceMap = true;
161+
options.outFile = path.join(loaderContext.rootContext, 'style.css.map');
162+
// options.sourceMapRoot = process.cwd();
160163
options.sourceMapContents = true;
161164
options.omitSourceMapUrl = true;
162-
options.sourceMapEmbed = false;
165+
// options.sourceMapEmbed = false;
163166
}
164167

165168
const { resourcePath } = loaderContext;
@@ -483,10 +486,36 @@ function getRenderFunctionFromSassImplementation(implementation) {
483486
return nodeSassJobQueue.push.bind(nodeSassJobQueue);
484487
}
485488

489+
const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
490+
491+
function getURLType(source) {
492+
if (source[0] === '/') {
493+
if (source[1] === '/') {
494+
return 'scheme-relative';
495+
}
496+
497+
return 'path-absolute';
498+
}
499+
500+
return ABSOLUTE_SCHEME.test(source) ? 'absolute' : 'path-relative';
501+
}
502+
503+
function absolutifySourceMapSource(sourceRoot, source) {
504+
const sourceType = getURLType(source);
505+
506+
// Do no touch `scheme-relative`, `path-absolute` and `absolute` types
507+
if (sourceType === 'path-relative') {
508+
return path.resolve(sourceRoot, path.normalize(source));
509+
}
510+
511+
return source;
512+
}
513+
486514
export {
487515
getSassImplementation,
488516
getSassOptions,
489517
getWebpackResolver,
490518
getWebpackImporter,
491519
getRenderFunctionFromSassImplementation,
520+
absolutifySourceMapSource,
492521
};

test/sourceMap-options.test.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ describe('sourceMap option', () => {
4242

4343
sourceMap.sourceRoot = '';
4444
sourceMap.sources = sourceMap.sources.map((source) =>
45-
source.replace(/\\/g, '/')
45+
path
46+
.relative(path.resolve(__dirname, '..'), source)
47+
.replace(/\\/g, '/')
4648
);
4749

4850
expect(css).toMatchSnapshot('css');
@@ -124,7 +126,9 @@ describe('sourceMap option', () => {
124126

125127
sourceMap.sourceRoot = '';
126128
sourceMap.sources = sourceMap.sources.map((source) =>
127-
source.replace(/\\/g, '/')
129+
path
130+
.relative(path.resolve(__dirname, '..'), source)
131+
.replace(/\\/g, '/')
128132
);
129133

130134
expect(css).toMatchSnapshot('css');
@@ -157,7 +161,9 @@ describe('sourceMap option', () => {
157161

158162
sourceMap.sourceRoot = '';
159163
sourceMap.sources = sourceMap.sources.map((source) =>
160-
source.replace(/\\/g, '/')
164+
path
165+
.relative(path.resolve(__dirname, '..'), source)
166+
.replace(/\\/g, '/')
161167
);
162168

163169
expect(css).toMatchSnapshot('css');

0 commit comments

Comments
 (0)