Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(loader): source map sources and file paths #602

Closed
Closed
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,37 @@ module.exports = {

If you want to edit the original Sass files inside Chrome, [there's a good blog post](https://medium.com/@toolmantim/getting-started-with-css-sourcemaps-and-in-browser-sass-editing-b4daab987fb0). Checkout [test/sourceMap](https://github.com/webpack-contrib/sass-loader/tree/master/test) for a running example.

#### Opting out of legacy source maps behavior

To receive source maps as URLs relative to `loader.resourcePath`, set the `legacySourceMaps` option to `false`.

```javascript
module.exports = {
// ...
devtool: "source-map",
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader", options: {
sourceMap: true
}
}, {
loader: "sass-loader", options: {
legacySourceMaps: false,
sourceMap: true
}
}]
}]
}
};
```

_\* If you are using a plugin to extract source maps, such as `webpack.SourceMapDevToolPlugin`, make sure to set `devtool: false`._


### Environment variables

If you want to prepend Sass code before the actual entry file, you can set the `data` option. In this case, the sass-loader will not override the `data` option but just append the entry's content. This is especially useful when some of your Sass variables depend on the environment:
Expand Down
29 changes: 16 additions & 13 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,22 @@ function sassLoader(content) {

if (result.map && result.map !== "{}") {
result.map = JSON.parse(result.map);
// result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
delete result.map.file;
// The first source is 'stdin' according to node-sass because we've used the data input.
// Now let's override that value with the correct relative path.
// Since we specified options.sourceMap = path.join(process.cwd(), "/sass.map"); in normalizeOptions,
// we know that this path is relative to process.cwd(). This is how node-sass works.
result.map.sources[0] = path.relative(process.cwd(), resourcePath);
// node-sass returns POSIX paths, that's why we need to transform them back to native paths.
// This fixes an error on windows where the source-map module cannot resolve the source maps.
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
result.map.sources = result.map.sources.map(path.normalize);
if (options.legacySourceMaps !== false) {
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
result.map.sources = result.map.sources.map(path.normalize);

// Legacy test considers the presence of the `file` attribute to
// be an invalid source map. Until the tests are updated,
// continue with removing the property.
delete result.map.file;
}
// else {
// // node-sass returns relative URL paths, not file system paths
// // (POSIX or otherwise.) Aside from possible differences in the
// // path separator, relative URL paths are (almost always) valid
// // file system paths, allowing them to be used as such.
// }
} else {
result.map = null;
}
Expand Down
101 changes: 82 additions & 19 deletions lib/normalizeOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,88 @@ function normalizeOptions(loaderContext, content, webpackImporter) {
// Not using the `this.sourceMap` flag because css source maps are different
// @see https://github.com/webpack/css-loader/pull/40
if (options.sourceMap) {
// Deliberately overriding the sourceMap option here.
// node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
// In case it is a string, options.sourceMap should be a path where the source map is written.
// But since we're using the data option, the source map will not actually be written, but
// all paths in sourceMap.sources will be relative to that path.
// Pretty complicated... :(
options.sourceMap = path.join(process.cwd(), "/sass.map");
if ("sourceMapRoot" in options === false) {
options.sourceMapRoot = process.cwd();
}
if ("omitSourceMapUrl" in options === false) {
// The source map url doesn't make sense because we don't know the output path
// The css-loader will handle that for us
options.omitSourceMapUrl = true;
}
if ("sourceMapContents" in options === false) {
// If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
// when exported by webpack-extract-text-plugin.
options.sourceMapContents = true;
/**
* > Path to a file for LibSass to compile.
* @see node-sass [file]{@link https://github.com/sass/node-sass#file}
*
* > **Special behaviours**
* >
* > In the case that both file and data options are set, node-sass will
* > give precedence to data and use file to calculate paths in
* > sourcemaps.
* @see node-sass [Special behaviours]{@link https://github.com/sass/node-sass#special-behaviours}
*
* Another benefit to setting this is that `stdin`/`stdout` will no
* longer appear in `map.sources`. There will be no need to update
* `map.sources`, `map.names`, or similar.
*
* @type {String} [options.file=null]
*/
options.file = resourcePath;

/**
* > **Special:** Required when `sourceMap` is a truthy value
* >
* > Specify the intended location of the output file. Strongly
* > recommended when outputting source maps so that they can properly
* > refer back to their intended files.
* >
* > **Attention** enabling this option will not write the file on disk
* > for you, it's for internal reference purpose only (to generate the
* > map for example).
* @see node-sass [outFile]{@link https://github.com/sass/node-sass#outfile}
*
* Even though we are always setting `sourceMap` to a string, the
* documentation says this is required, so set it to the expected value
* to comply with the requirement.
*
* `sass-loader` isn't responsible for writing the map, so it doesn't
* have to worry about updating the map with a transformation that
* changes locations (suchs as map.file or map.sources).
*
* Changing the file extension counts as changing the location because
* it changes the path.
*
* @type {String | null} [options.outFile=null]
*/
options.outFile = resourcePath;

if (options.legacySourceMaps !== false) {
// Deliberately overriding the sourceMap option here.
// node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
// In case it is a string, options.sourceMap should be a path where the source map is written.
// But since we're using the data option, the source map will not actually be written, but
// all paths in sourceMap.sources will be relative to that path.
// Pretty complicated... :(
options.sourceMap = path.join(process.cwd(), "/sass.map");
if ("sourceMapRoot" in options === false) {
options.sourceMapRoot = process.cwd();
}
if ("omitSourceMapUrl" in options === false) {
// The source map url doesn't make sense because we don't know the output path
// The css-loader will handle that for us
options.omitSourceMapUrl = true;
}
if ("sourceMapContents" in options === false) {
// If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
// when exported by webpack-extract-text-plugin.
options.sourceMapContents = true;
}
} else {
/**
* > **Special: ** Setting the `sourceMap` option requires also setting
* > the `outFile` option
* >
* > Enables the outputting of a source map during `render` and
* > `renderSync`. When `sourceMap === true`, the value of `outFile` is
* > used as the target output location for the source map. When
* > `typeof sourceMap === "string"`, the value of `sourceMap` will be
* > used as the writing location for the file.
* @see node-sass [sourceMap]{@link https://github.com/sass/node-sass#sourcemap}
*
* @type {Boolean | String | undefined} [options.sourceMap=undefined]
*/
options.sourceMap = options.outFile + ".map";
}
}

Expand Down