Skip to content

Commit

Permalink
feat: generate source maps for sass with asset/resource
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Jun 11, 2021
1 parent 7d00b10 commit f486d27
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 1 deletion.
48 changes: 48 additions & 0 deletions README.md
Expand Up @@ -681,6 +681,54 @@ module.exports = {
};
```

### Generate source maps for sass with asset/resource

It is possible to extract files using [`asset modules`](https://webpack.js.org/guides/asset-modules/).

This requires:

- specify the `asset/resource` type for scss/sass files
- disable webpack source maps generation
- enable [`sourceMap`](https://sass-lang.com/documentation/js-api#sourcemap) in [`sassOptions`](#sassoptions)
- disable [`omitSourceMapUrl`](https://sass-lang.com/documentation/js-api#omitsourcemapurl) in [`sassOptions`](#sassoptions)
- disable [`sourceMapEmbed`](https://sass-lang.com/documentation/js-api#sourcemapembed) in [`sassOptions`](#sassoptions)

**webpack.config.js**

```javascript
module.exports = {
devtool: false,
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "asset/resource",
generator: {
filename: "assets/[name].css",
},
use: [
{
loader: "sass-loader",
options: {
sourceMap: false,
sassOptions: {
// If `sourceMap` is true, source map name will be "[name].css.map"
sourceMap: "assets/[name].css.map",
// A source map url is generated relative to this file
// The file name does not matter, only the directory structure is important
outFile: path.join(__dirname, "style.css"),
omitSourceMapUrl: false,
sourceMapEmbed: false,
},
},
},
],
},
],
},
};
```

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.

## Contributing
Expand Down
24 changes: 24 additions & 0 deletions src/index.js
Expand Up @@ -51,6 +51,24 @@ async function loader(content) {

const render = getRenderFunctionFromSassImplementation(implementation);

const sourceMapShouldBeEmmited =
!sassOptions.omitSourceMapUrl && !sassOptions.sourceMapEmbed;

if (sourceMapShouldBeEmmited && sassOptions.sourceMap) {
const filename = path.basename(this.resourcePath);
const mapNameTemplate =
sassOptions.sourceMap === true ? "[name].css.map" : sassOptions.sourceMap;
// eslint-disable-next-line no-underscore-dangle
const { path: mapFilename } = this._compilation.getPathWithInfo(
mapNameTemplate,
{
filename,
}
);

sassOptions.sourceMap = mapFilename;
}

render(sassOptions, (error, result) => {
if (error) {
// There are situations when the `file` property do not exist
Expand Down Expand Up @@ -80,6 +98,12 @@ async function loader(content) {
}
});

if (sourceMapShouldBeEmmited && map) {
this.emitFile(sassOptions.sourceMap, JSON.stringify(map));

map = null;
}

callback(null, result.css.toString(), map);
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Expand Up @@ -180,7 +180,7 @@ async function getSassOptions(
// all paths in sourceMap.sources will be relative to that path.
// Pretty complicated... :(
options.sourceMap = true;
options.outFile = path.join(loaderContext.rootContext, "style.css.map");
options.outFile = path.join(loaderContext.rootContext, "style.css");
options.sourceMapContents = true;
options.omitSourceMapUrl = true;
options.sourceMapEmbed = false;
Expand Down
32 changes: 32 additions & 0 deletions test/__snapshots__/sourceMap-options.test.js.snap
@@ -1,5 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (dart-sass) (sass): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (dart-sass) (sass): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (dart-sass) (scss): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (dart-sass) (scss): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (node-sass) (sass): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (node-sass) (sass): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (node-sass) (scss): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (node-sass) (scss): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (dart-sass) (sass): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (dart-sass) (sass): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (dart-sass) (scss): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (dart-sass) (scss): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (node-sass) (sass): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (node-sass) (sass): warnings 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (node-sass) (scss): errors 1`] = `Array []`;

exports[`sourceMap option should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (node-sass) (scss): warnings 1`] = `Array []`;

exports[`sourceMap option should generate source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (dart-sass) (sass): css 1`] = `
"@charset \\"UTF-8\\";
@import \\"./file.css\\";
Expand Down
80 changes: 80 additions & 0 deletions test/sourceMap-options.test.js
Expand Up @@ -235,6 +235,86 @@ describe("sourceMap option", () => {
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it(`should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "true" value (${implementationName}) (${syntax})`, async () => {
const testId = getTestId("language", syntax);
const options = {
implementation: getImplementationByName(implementationName),
sourceMap: false,
sassOptions: {
sourceMap: true,
outFile: path.join(__dirname, "style.css"),
sourceMapContents: true,
omitSourceMapUrl: false,
sourceMapEmbed: false,
},
};
const compiler = getCompiler(testId, {
devtool: false,
rules: [
{
test: /\.s[ac]ss$/i,
type: "asset/resource",
generator: {
filename: "assets/[name].css",
},
use: [
{
loader: path.join(__dirname, "../src/cjs.js"),
options,
},
],
},
],
});
const stats = await compile(compiler);
const { compilation } = stats;

expect(compilation.getAsset("assets/language.css")).toBeDefined();
expect(compilation.getAsset("language.css.map")).toBeDefined();
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it(`should generate and emit source maps when value has "false" value, but the "sassOptions.sourceMap" has the "string" value (${implementationName}) (${syntax})`, async () => {
const testId = getTestId("language", syntax);
const options = {
implementation: getImplementationByName(implementationName),
sourceMap: false,
sassOptions: {
sourceMap: "assets/[name].css.map",
outFile: path.join(__dirname, "styles.css"),
sourceMapContents: true,
omitSourceMapUrl: false,
sourceMapEmbed: false,
},
};
const compiler = getCompiler(testId, {
devtool: false,
rules: [
{
test: /\.s[ac]ss$/i,
type: "asset/resource",
generator: {
filename: "assets/[name].css",
},
use: [
{
loader: path.join(__dirname, "../src/cjs.js"),
options,
},
],
},
],
});
const stats = await compile(compiler);
const { compilation } = stats;

expect(compilation.getAsset("assets/language.css")).toBeDefined();
// expect(compilation.getAsset("assets/language.css.map")).toBeDefined();
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});
});
});
});

0 comments on commit f486d27

Please sign in to comment.