Loads a Sass/SCSS file and compiles it to CSS.
To begin, you'll need to install sass-loader:
npm install sass-loader sass webpack --save-devor
yarn add -D sass-loader sass webpackor
pnpm add -D sass-loader sass webpackNote
Webpack has built-in CSS support, so no extra loaders are required to process the CSS generated by sass-loader - just enable experiments.css and set the module type to css/auto.
If you prefer the loader-based setup, install style-loader and css-loader via npm i style-loader css-loader and chain them with sass-loader instead.
sass-loader requires you to install either Dart Sass or Sass Embedded on your own (more documentation can be found below).
This allows you to control the versions of all your dependencies and to choose which Sass implementation to use.
Note
We highly recommend using Sass Embedded or Dart Sass.
Use the sass-loader with the built-in CSS support of webpack (the css/auto module type) to let webpack handle the generated CSS - it extracts styles into a separate file and injects them into the document.
Alternatively, you can chain the sass-loader with the css-loader and the style-loader to immediately apply all styles to the DOM, or with the mini-css-extract-plugin to extract it into a separate file.
Then add the loader to your webpack configuration. For example:
app.js
import "./style.scss";style.scss
$body-color: red;
body {
color: $body-color;
}webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
// Lets webpack handle the generated CSS using its built-in CSS support,
// `css/auto` also enables CSS modules for `*.module.scss` files
type: "css/auto",
use: [
// Compiles Sass to CSS
"sass-loader",
],
},
],
},
experiments: {
// Enables the built-in CSS support of webpack
css: true,
},
};Finally run webpack via your preferred method (e.g., via CLI or an npm script).
Note
All examples below use the built-in CSS support of webpack.
If you use css-loader and style-loader (or mini-css-extract-plugin) instead, remove the type and experiments options and put them before the sass-loader in the use array:
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
],
},
};For production mode, the style option defaults to compressed unless otherwise specified in sassOptions.
Webpack provides an advanced mechanism to resolve files.
The sass-loader uses Sass's custom importer feature to pass every @use, @import and @forward request to the webpack resolving engine, so your webpack resolve configuration applies to stylesheets, and you can load Sass modules from node_modules:
@use "bootstrap";For @use "theme" inside src/app.scss, the loader asks webpack for the following and takes the first hit:
- the partial
src/_theme.sass,src/_theme.scss,src/_theme.css src/theme.sass,src/theme.scss,src/theme.cssthemeas written, so aliases and package requests resolve
Directories resolve through their _index/index file. For @import only, the import-only files _theme.import.scss and theme.import.scss are tried before everything else.
Relative requests win over module ones, so @use "theme" behaves like @use "./theme" when both could match. Keeping both _theme.scss and theme.scss in one directory is ambiguous and Sass reports an error for it, so the order within a directory rarely matters.
alias- applied to every request, and tried beforenode_modulesmodules- extra directories to look in, e.g.srcbyDependency.sass- requests are resolved withdependencyType: "sass", so this targets stylesheets onlyplugins,symlinks,rootsand the rest of the resolver options
webpack.config.js
module.exports = {
resolve: {
alias: { "@styles": path.resolve(__dirname, "src/styles") },
modules: [path.resolve(__dirname, "src"), "node_modules"],
},
};style.scss
@use "@styles/theme" as *; // resolved by `resolve.alias`
@use "abstracts" as *; // resolved by `resolve.modules` to `src/abstracts/_index.scss`Some options are fixed to match Sass's own algorithm and can't be changed through resolve: the extensions are .sass, .scss and .css (so resolve.extensions doesn't apply here), mainFiles prefer _index/index, mainFields prefer sass and style over main, and conditionNames prefer the sass and style export conditions. Your own mainFields and conditionNames are kept after those.
A package request resolves through the sass and style conditions of its exports field, falling back to the sass, style and main fields. The pkg: URL scheme is supported as well:
@use "pkg:bootstrap";The importer hands the request back to Sass, which then applies its own resolution - sassOptions.loadPaths, the SASS_PATH environment variable and any custom importer you configured.
Sass compiles @import "theme.css" to a plain CSS @import, so the loader leaves it in the output untouched - whatever handles the CSS afterwards (the built-in CSS support of webpack, css-loader, or the browser) decides what happens with it. @use "theme.css" includes the file's content instead, and is resolved like any other request:
@import "theme.css"; // stays `@import "theme.css";` in the output
@use "theme.css"; // inlines the content of the fileUsing ~ is deprecated and should be removed from your code, but we still support it for historical reasons.
Why can you remove it? The loader will first try to resolve @use as a relative path. If it cannot be resolved, then the loader will try to resolve it inside node_modules.
Prepending module paths with a ~ tells webpack to search through node_modules.
@use "~bootstrap";It's important to prepend the path with only ~, because ~/ resolves to the home directory.
Webpack needs to distinguish between bootstrap and ~bootstrap because CSS and Sass files have no special syntax for importing relative files.
Writing @use "style.scss" is the same as @use "./style.scss";
Since Sass implementations don't provide url rewriting, all linked assets must be relative to the output.
- If webpack handles the generated CSS (i.e. the built-in CSS support or the
css-loader), all URLs must be relative to the entry-file (e.g.main.scss). - If you're just generating CSS without letting webpack handle it, URLs must be relative to your web root.
You might be surprised by this first issue, as it is natural to expect relative references to be resolved against the .sass/.scss file in which they are specified (like in regular .css files).
Thankfully there are two solutions to this problem:
-
Add the missing URL rewriting using the resolve-url-loader. Place it before
sass-loaderin the loader chain. -
Library authors usually provide a variable to modify the asset path. bootstrap-sass for example, has an
$icon-font-path.
Type:
type implementation = object | string;Default: sass
The special implementation option determines which implementation of Sass to use.
By default, the loader resolves the implementation based on your dependencies.
Just add the desired implementation to your package.json (sass or sass-embedded package) and install dependencies.
Example where the sass-loader uses the sass (dart-sass) implementation:
package.json
{
"devDependencies": {
"sass-loader": "^7.2.0",
"sass": "^1.22.10"
}
}Example where the sass-loader uses the sass-embedded implementation:
package.json
{
"devDependencies": {
"sass-loader": "^7.2.0",
"sass": "^1.22.10"
},
"optionalDependencies": {
"sass-embedded": "^1.70.0"
}
}Note
Using optionalDependencies means that sass-loader can fallback to sass when running on an operating system not supported by sass-embedded
Be aware of the order that sass-loader will resolve the implementation:
sass-embeddedsass
You can specify a specific implementation by using the implementation option, which accepts one of the above values.
For example, to always use Dart Sass, you'd pass:
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
// Prefer `dart-sass`, even if `sass-embedded` is available
implementation: require("sass"),
},
},
],
},
],
},
experiments: {
css: true,
},
};For example, to use Dart Sass, you'd pass:
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
// Prefer `dart-sass`, even if `sass-embedded` is available
implementation: require.resolve("sass"),
},
},
],
},
],
},
experiments: {
css: true,
},
};Type:
type sassOptions =
| import("sass").StringOptionsWithImporter<"async">
| ((
content: string | Buffer,
loaderContext: LoaderContext,
meta: any,
) => import("sass").StringOptionsWithImporter<"async">);Default: defaults values for Sass implementation
Options for Dart Sass or Sass Embedded implementation.
Note
The charset option is true by default for dart-sass. We strongly discourage setting this to false because webpack doesn't support files other than utf-8.
Note
The syntax option is scss for the scss extension, indented for the sass extension, and css for the css extension.
Note
Options such as data and url are unavailable and will be ignored.
ℹ We strongly discourage changing the
sourceMapoption becausesass-loadersets it automatically when thesourceMapoption istrue.
Please consult their respective documentation before using them:
- Dart Sass documentation for all available
sassoptions. - Sass Embedded documentation for all available
sass-embeddedoptions.
Use an object for the Sass implementation setup.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
sassOptions: {
style: "compressed",
loadPaths: ["absolute/path/a", "absolute/path/b"],
},
},
},
],
},
],
},
experiments: {
css: true,
},
};Allows configuring the Sass implementation with different options based on the loader context.
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
sassOptions: (loaderContext) => {
// More information about available properties https://webpack.js.org/api/loaders/
const { resourcePath, rootContext } = loaderContext;
const relativePath = path.relative(rootContext, resourcePath);
if (relativePath === "styles/foo.scss") {
return {
loadPaths: ["absolute/path/c", "absolute/path/d"],
};
}
return {
loadPaths: ["absolute/path/a", "absolute/path/b"],
};
},
},
},
],
},
],
},
experiments: {
css: true,
},
};Type:
type sourceMap = boolean;Default: depends on the compiler.devtool value
Enables/disables generation of source maps.
By default generation of source maps depends on the devtool option.
All values enable source map generation except eval and false.
ℹ If
true, thesourceMapoption fromsassOptionswill be ignored.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
},
],
},
experiments: {
css: true,
},
};webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
sourceMap: true,
sassOptions: {
style: "compressed",
},
},
},
],
},
],
},
experiments: {
css: true,
},
};Type:
type additionalData =
| string
| ((content: string | Buffer, loaderContext: LoaderContext) => string);Default: undefined
Prepends Sass/SCSS code before the actual entry file.
In this case, the sass-loader will not override the data option but just prepend the entry's content.
This is especially useful when some of your Sass variables depend on the environment:
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
additionalData: `$env: ${process.env.NODE_ENV};`,
},
},
],
},
],
},
experiments: {
css: true,
},
};module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
additionalData: (content, loaderContext) => {
// More information about available properties https://webpack.js.org/api/loaders/
const { resourcePath, rootContext } = loaderContext;
const relativePath = path.relative(rootContext, resourcePath);
if (relativePath === "styles/foo.scss") {
return `$value: 100px;${content}`;
}
return `$value: 200px;${content}`;
},
},
},
],
},
],
},
experiments: {
css: true,
},
};module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
additionalData: async (content, loaderContext) => {
// More information about available properties https://webpack.js.org/api/loaders/
const { resourcePath, rootContext } = loaderContext;
const relativePath = path.relative(rootContext, resourcePath);
if (relativePath === "styles/foo.scss") {
return `$value: 100px;${content}`;
}
return `$value: 200px;${content}`;
},
},
},
],
},
],
},
experiments: {
css: true,
},
};Type:
type webpackImporter = boolean;Default: true
Enables/disables the default webpack importer.
This can improve performance in some cases, though use it with caution because aliases and @import at-rules starting with ~ will not work.
You can pass your own importer to solve this (see Sass importer documentation).
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
webpackImporter: false,
},
},
],
},
],
},
experiments: {
css: true,
},
};Type:
type warnRuleAsWarning = boolean;Default: true
Treats the @warn rule as a webpack warning.
style.scss
$known-prefixes: webkit, moz, ms, o;
@mixin prefix($property, $value, $prefixes) {
@each $prefix in $prefixes {
@if not index($known-prefixes, $prefix) {
@warn "Unknown prefix #{$prefix}.";
}
-#{$prefix}-#{$property}: $value;
}
#{$property}: $value;
}
.tilt {
// Oops, we typo'd "webkit" as "wekbit"!
@include prefix(transform, rotate(15deg), wekbit ms);
}The presented code will throw a webpack warning instead of logging.
To ignore unnecessary warnings you can use the ignoreWarnings option.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
warnRuleAsWarning: true,
},
},
],
},
],
},
experiments: {
css: true,
},
};Type:
type api = "auto" | "modern" | "modern-compiler";Default: "auto" for sass (dart-sass) and sass-embedded
Allows you to switch between the modern and modern-compiler APIs. You can find more information here. The modern-compiler option enables the modern API with support for Shared Resources.
When "auto" is used, the loader picks "modern-compiler" whenever the implementation exposes initAsyncCompiler (i.e. recent versions of sass and sass-embedded) and falls back to "modern" otherwise. Combined with sass-embedded, this yields the best build performance out of the box.
Note
Using modern-compiler and sass-embedded together significantly improves performance and decreases build time. They are now selected automatically by the default "auto" API.
Note
The legacy Sass JS API is no longer supported. If you were using api: "legacy", please migrate to the modern API. See the Sass JS API docs to learn how to migrate.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
api: "modern-compiler",
sassOptions: {
// Your sass options
},
},
},
],
},
],
},
experiments: {
css: true,
},
};By default, the output of @debug messages is disabled.
Add the following to webpack.config.js to enable them:
module.exports = {
stats: {
loggingDebug: ["sass-loader"],
},
// ...
};For production builds, it's recommended to extract the CSS from your bundle to enable parallel loading of CSS/JS resources.
There are five recommended ways to extract a stylesheet from a bundle:
Webpack emits CSS into separate files on its own, so nothing but experiments.css is required.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: ["sass-loader"],
},
],
},
output: {
// Both options are optional
cssFilename: "[name].css",
cssChunkFilename: "[id].css",
},
experiments: {
css: true,
},
};webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
// fallback to style-loader in development
process.env.NODE_ENV !== "production"
? "style-loader"
: MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].css",
chunkFilename: "[id].css",
}),
],
};webpack.config.js
const path = require("node:path");
module.exports = {
entry: [path.resolve(__dirname, "./src/scss/app.scss")],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [],
},
{
test: /\.scss$/,
exclude: /node_modules/,
type: "asset/resource",
generator: {
filename: "bundle.css",
},
use: ["sass-loader"],
},
],
},
};4. extract-loader (simpler, but specialized on the css-loader's output)
5. file-loader (deprecated--should only be used in webpack v4)
webpack.config.js
const path = require("node:path");
module.exports = {
entry: [path.resolve(__dirname, "./src/scss/app.scss")],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [],
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{
loader: "file-loader",
options: { outputPath: "css/", name: "[name].min.css" },
},
"sass-loader",
],
},
],
},
};(source: https://stackoverflow.com/a/60029923/2969615)
Enables/disables generation of source maps.
To enable CSS source maps, you'll need to pass the sourceMap option to the sass-loader (and to the css-loader too, when you use it).
webpack.config.js
module.exports = {
devtool: "source-map", // any "source-map"-like devtool is possible
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
},
],
},
experiments: {
css: true,
},
};If you want to edit the original Sass files inside Chrome, there's a good blog post. Checkout test/sourceMap for a working example.
We welcome all contributions! If you're new here, please take a moment to review our contributing guidelines before submitting issues or pull requests.