Episode 6 of 6

SASS Loaders

Add SASS/SCSS support to Webpack — chain sass-loader, css-loader, and style-loader to compile and bundle SASS stylesheets.

SASS Loaders

In the previous episode you learned how to import CSS files into JavaScript. Now we will add SASS/SCSS support so you can write styles with variables, nesting, and mixins — and have Webpack compile them automatically. This episode chains three loaders together to process SASS files.

The Three-Loader Chain

.scss file → sass-loader → css-loader → style-loader → DOM

// 1. sass-loader compiles SCSS to plain CSS
// 2. css-loader resolves @import and url() in the CSS
// 3. style-loader injects the CSS into the page
LoaderInputOutput
sass-loaderSASS/SCSS codePlain CSS
css-loaderPlain CSSJavaScript module with CSS string
style-loaderCSS JavaScript moduleInjected <style> tag in the DOM

Step 1: Install the Packages

npm install sass-loader sass --save-dev
PackagePurpose
sass-loaderThe Webpack loader that passes files through a SASS compiler
sassThe Dart SASS compiler (the recommended implementation)

sass (Dart SASS) is the primary implementation. The older node-sass package is deprecated. If you see tutorials using node-sass, use sass instead — it has the same API but is actively maintained.

Step 2: Add the SASS Rule

Add a new rule to webpack.config.js:

{
    test: /\.scss$/,
    use: ['style-loader', 'css-loader', 'sass-loader'],
}

Three loaders, right to left: sass-loader compiles SCSS to CSS, css-loader processes the CSS, and style-loader injects it into the page.

Step 3: Create a SASS File

src/scss/main.scss

// Variables
$bg-dark: #1a1a2e;
$card-bg: #16213e;
$accent: #667eea;
$accent-alt: #764ba2;
$text-light: #eee;
$text-muted: #9ca3af;
$radius: 16px;

// Mixins
@mixin flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

@mixin gradient-text($from, $to) {
    background: linear-gradient(135deg, $from, $to);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

// Base styles
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', system-ui, sans-serif;
    background: $bg-dark;
    color: $text-light;
    min-height: 100vh;
    @include flex-center;
}

// App container
#app {
    text-align: center;
    padding: 40px;
    background: $card-bg;
    border-radius: $radius;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);

    h1 {
        font-size: 2.5rem;
        margin-bottom: 12px;
        @include gradient-text($accent, $accent-alt);
    }

    p {
        color: $text-muted;
        font-size: 1.1rem;
    }
}

Step 4: Import the SASS File

Update src/index.js to import the SCSS file instead of CSS:

import './scss/main.scss';  // Changed from CSS to SCSS

import { greet, VERSION } from './utils';
import { render } from './dom';

const message = greet('Webpack');
render('#app', `

${message}

Version: ${VERSION}

`); console.log('App loaded successfully!');

Step 5: Build and Test

npm run build

Webpack compiles the SCSS, processes the CSS, and bundles it into the JavaScript output. The styles are applied when the page loads — all from a single import statement.

Using SASS Partials

SASS partials are files prefixed with an underscore that are meant to be imported, not compiled on their own:

src/scss/
├── main.scss           (entry — imports partials)
├── _variables.scss     (partial — variables only)
├── _mixins.scss        (partial — mixins only)
├── _base.scss          (partial — resets and base styles)
└── _components.scss    (partial — component styles)

src/scss/_variables.scss

$bg-dark: #1a1a2e;
$card-bg: #16213e;
$accent: #667eea;
$accent-alt: #764ba2;
$text-light: #eee;
$text-muted: #9ca3af;
$radius: 16px;

src/scss/main.scss

@use 'variables' as *;
@use 'mixins' as *;
@use 'base';
@use 'components';

The @use rule (modern SASS) imports partials without the underscore or extension. This keeps your styles modular and organized. sass-loader resolves these imports automatically.

sass-loader Options

{
    test: /\.scss$/,
    use: [
        'style-loader',
        'css-loader',
        {
            loader: 'sass-loader',
            options: {
                // Inject variables into every SASS file
                additionalData: `@use "src/scss/variables" as *;`,

                // Choose the SASS implementation
                implementation: require('sass'),

                sassOptions: {
                    // Output style
                    outputStyle: 'compressed',
                },
            },
        },
    ],
}
OptionPurpose
additionalDataPrepends SASS code to every file — useful for global variables
implementationSpecifies which SASS compiler to use
sassOptions.outputStyleControls CSS output formatting

Handling Both CSS and SASS

If your project has both .css and .scss files, you need two separate rules:

module: {
    rules: [
        {
            test: /\.css$/,
            use: ['style-loader', 'css-loader'],
        },
        {
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'sass-loader'],
        },
    ],
}

Each rule's test regex matches different extensions, so Webpack knows which loader chain to apply.

Complete webpack.config.js

const path = require('path');

module.exports = {
    mode: 'development',
    devtool: 'eval-source-map',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    devServer: {
        static: { directory: path.resolve(__dirname, 'dist') },
        port: 3000,
        open: true,
        hot: true,
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: { presets: ['@babel/preset-env'] },
                },
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.scss$/,
                use: ['style-loader', 'css-loader', 'sass-loader'],
            },
        ],
    },
};

The Complete Loader Pipeline

File TypeLoader ChainResult
.jsbabel-loaderTranspiled JavaScript in the bundle
.cssstyle-loader ← css-loaderCSS injected as <style> tag
.scssstyle-loader ← css-loader ← sass-loaderCompiled SASS injected as <style> tag

Key Takeaways

  • SASS support requires three chained loaders: sass-loadercss-loaderstyle-loader
  • Install sass-loader and sass (Dart SASS) — avoid the deprecated node-sass
  • Loaders execute right to left in the use array — SASS compiles first, then CSS processes, then styles inject
  • sass-loader resolves SASS @use and @import statements automatically
  • Use additionalData to inject global variables into every SASS file without manual imports
  • Keep separate rules for .css and .scss files if your project uses both
  • For production, swap style-loader with MiniCssExtractPlugin.loader to extract CSS into a separate file