
sass-loader
Loads a Sass/SCSS file and compiles it to CSS.
Getting Started
To begin, you’ll need to install sass-loader:
npm install sass-loader sass webpack --save-dev
or
yarn add -D sass-loader sass webpack
or
pnpm add -D sass-loader sass webpack
Webpack has built-in CSS support, so no extra loaders are required to process the CSS generated by sass-loader - just set the module type to css/auto. This is the recommended setup, and needs webpack 5.87.0 or later.
Since webpack 5.109.0 the built-in support is on by default (experiments.css defaults to "auto"), but "auto" switches it off when another module.rules entry handles .css files, so the examples below keep experiments.css: true. If such a rule still uses css-loader, give it type: "javascript/auto" so built-in CSS leaves those files to it.
The deprecated loader-based setup still works: install style-loader and css-loader via npm i style-loader css-loader and chain them with sass-loader instead, or follow the Native CSS migration guide to move off them.
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.
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. Its output is a .css file the page has to link: let webpack build the page — an HTML entry or output.html: true (webpack 5.108.0 or later; before 5.109.0 also set experiments.html: true) adds the <link> for you — set exportType: "style" (webpack 5.106.0 or later) to inject the styles from JavaScript instead, or link the emitted file yourself.
Alternatively, you can chain the sass-loader with the deprecated css-loader and the style-loader to immediately apply all styles to the DOM, or with the deprecated 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,
type: "css/auto",
use: [
"sass-loader",
],
},
],
},
experiments: {
css: true,
},
};
Finally run webpack via your preferred method (e.g., via CLI or an npm script).
All examples below use the built-in CSS support of webpack.
If you still use the deprecated 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"],
},
],
},
};
The style option in production mode
For production mode, the style option defaults to compressed unless otherwise specified in sassOptions.
Resolving import, use and forward at-rules
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";
How a request is resolved
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.css
theme as 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.
What your resolve configuration controls
alias - applied to every request, and tried before node_modules
modules - extra directories to look in, e.g. src
byDependency.sass - requests are resolved with dependencyType: "sass", so this targets stylesheets only
plugins, symlinks, roots and 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.
Packages
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";
When webpack can’t resolve a request
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.
Plain CSS files
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 file
The ~ prefix
Using ~ 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";
Problems with url(...)
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-loader in the loader chain.
-
Library authors usually provide a variable to modify the asset path. bootstrap-sass for example, has an $icon-font-path.
Options
implementation
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"
}
}
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-embedded
sass
You can specify a specific implementation by using the implementation option, which accepts one of the above values.
object
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: {
implementation: require("sass"),
},
},
],
},
],
},
experiments: {
css: true,
},
};
string
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: {
implementation: require.resolve("sass"),
},
},
],
},
],
},
experiments: {
css: true,
},
};
sassOptions
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.
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.
The syntax option is scss for the scss extension, indented for the sass extension, and css for the css extension.
Options such as data and url are unavailable and will be ignored.
ℹ We strongly discourage changing the sourceMap option because sass-loader sets it automatically when the sourceMap option is true.
Please consult their respective documentation before using them:
object
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,
},
};
function
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) => {
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,
},
};
sourceMap
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, the sourceMap option from sassOptions will 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,
},
};
additionalData
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:
string
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,
},
};
function
Sync
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
additionalData: (content, loaderContext) => {
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,
},
};
Async
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: [
{
loader: "sass-loader",
options: {
additionalData: async (content, loaderContext) => {
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,
},
};
webpackImporter
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,
},
};
warnRuleAsWarning
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,
},
};
api
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.
Using modern-compiler and sass-embedded together significantly improves performance and decreases build time. They are now selected automatically by the default "auto" API.
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: {
},
},
},
],
},
],
},
experiments: {
css: true,
},
};
How to enable @debug output
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"],
},
};
Examples
For production builds, extract the CSS from your bundle to enable parallel loading of CSS/JS resources.
With the built-in CSS support of webpack this needs no plugin: the CSS that sass-loader produces is extracted into separate files out of the box — every entry point and every lazily loaded chunk gets its own .css file — and it is minified in production mode. Name the files with output.cssFilename and output.cssChunkFilename:
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
type: "css/auto",
use: ["sass-loader"],
},
],
},
output: {
cssFilename: "[name].[contenthash].css",
cssChunkFilename: "[id].[contenthash].css",
},
experiments: {
css: true,
},
};
To control how stylesheets are grouped into files (one file for every page, a separate vendor stylesheet, …), use optimization.splitChunks.
Compiling a standalone stylesheet
To emit the compiled Sass as a file without importing it from JavaScript, use Asset Modules. Webpack then writes sass-loader’s output as-is: url() and @import are not resolved or rewritten, and CSS Modules are not applied.
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"],
},
],
},
};
Legacy: mini-css-extract-plugin (deprecated)
Setups that still use the deprecated css-loader extract CSS with the deprecated mini-css-extract-plugin. Follow the Native CSS migration guide to replace both with the configuration above.
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
process.env.NODE_ENV !== "production"
? "style-loader"
: MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader",
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css",
}),
],
};
Source maps
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",
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.
Contributing
We welcome all contributions!
If you’re new here, please take a moment to review our contributing guidelines before submitting issues or pull requests.
CONTRIBUTING
License
MIT
评论