SASS/SCSS

SASS with Build Tools: npm Scripts & Webpack

20 min Lesson 26 of 30

Introduction to SASS Build Tools

Modern web development requires build tools to compile SASS, optimize assets, and automate development workflows. In this comprehensive lesson, we'll explore various build tools and how to integrate SASS into your development pipeline, from simple npm scripts to advanced bundlers like Webpack and Vite.

Setting Up npm Scripts for SASS Compilation

npm scripts provide a simple, dependency-free way to compile SASS files. They're defined in your package.json file and can be executed with npm run.

Installing SASS

First, install SASS as a development dependency:

Package Installation

# Install Dart Sass (recommended)
npm install --save-dev sass

# Or install node-sass (legacy, not recommended)
npm install --save-dev node-sass
Note: Dart Sass is the primary implementation and is actively maintained. node-sass is deprecated and should be avoided in new projects.

Basic npm Scripts Configuration

Add compilation scripts to your package.json:

package.json - Basic Scripts

{
  "name": "my-project",
  "version": "1.0.0",
  "scripts": {
    "build:css": "sass src/scss/main.scss dist/css/main.css",
    "watch:css": "sass --watch src/scss:dist/css",
    "build:css:compressed": "sass src/scss/main.scss dist/css/main.min.css --style=compressed"
  },
  "devDependencies": {
    "sass": "^1.69.0"
  }
}

Advanced npm Scripts

Create more sophisticated scripts with source maps, error handling, and parallel execution:

package.json - Advanced Scripts

{
  "scripts": {
    "sass:dev": "sass src/scss:dist/css --source-map --watch",
    "sass:build": "sass src/scss:dist/css --no-source-map --style=compressed",
    "sass:lint": "stylelint \"src/scss/**/*.scss\" --fix",
    "prebuild": "npm run sass:lint",
    "build": "npm run sass:build",
    "dev": "npm-run-all --parallel sass:dev serve",
    "serve": "lite-server"
  },
  "devDependencies": {
    "sass": "^1.69.0",
    "npm-run-all": "^4.1.5",
    "lite-server": "^2.6.1",
    "stylelint": "^15.10.3",
    "stylelint-config-standard-scss": "^11.0.0"
  }
}

PostCSS Integration with SASS

PostCSS is a tool for transforming CSS with JavaScript plugins. It works perfectly with SASS to add vendor prefixes, minify output, and apply other transformations.

Installing PostCSS

PostCSS Installation

npm install --save-dev postcss postcss-cli autoprefixer cssnano

PostCSS Configuration

Create a postcss.config.js file:

postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')({
      overrideBrowserslist: ['last 2 versions', '> 1%', 'IE 11']
    }),
    require('cssnano')({
      preset: ['default', {
        discardComments: {
          removeAll: true
        },
        normalizeWhitespace: false
      }]
    })
  ]
};

Combined SASS and PostCSS Pipeline

package.json - SASS + PostCSS

{
  "scripts": {
    "sass:compile": "sass src/scss:dist/css --no-source-map",
    "postcss:process": "postcss dist/css/*.css --dir dist/css --replace",
    "build:css": "npm run sass:compile && npm run postcss:process",
    "watch:sass": "sass --watch src/scss:dist/css",
    "watch:postcss": "postcss dist/css/*.css --dir dist/css --watch",
    "dev": "npm-run-all --parallel watch:sass watch:postcss"
  }
}

Webpack Configuration with SASS

Webpack is a powerful module bundler that can handle SASS compilation along with JavaScript bundling, image optimization, and more.

Installing Webpack and SASS Loaders

Webpack Dependencies

npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev sass-loader sass css-loader style-loader
npm install --save-dev mini-css-extract-plugin css-minimizer-webpack-plugin

Basic Webpack Configuration

webpack.config.js - Basic Setup

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'styles.css'
    })
  ]
};

Advanced Webpack Configuration

webpack.config.js - Production Setup

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';

  return {
    mode: isProduction ? 'production' : 'development',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'bundle.[contenthash].js',
      clean: true
    },
    devtool: isProduction ? 'source-map' : 'eval-source-map',
    module: {
      rules: [
        {
          test: /\.scss$/,
          use: [
            MiniCssExtractPlugin.loader,
            {
              loader: 'css-loader',
              options: {
                sourceMap: true,
                importLoaders: 2
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                sourceMap: true,
                postcssOptions: {
                  plugins: [
                    ['autoprefixer'],
                    ['cssnano', { preset: 'default' }]
                  ]
                }
              }
            },
            {
              loader: 'sass-loader',
              options: {
                sourceMap: true,
                sassOptions: {
                  outputStyle: isProduction ? 'compressed' : 'expanded',
                  includePaths: [path.resolve(__dirname, 'src/scss')]
                }
              }
            }
          ]
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i,
          type: 'asset/resource'
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: 'asset/resource'
        }
      ]
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: isProduction ? 'styles.[contenthash].css' : 'styles.css'
      })
    ],
    optimization: {
      minimizer: [
        `...`,
        new CssMinimizerPlugin()
      ]
    },
    devServer: {
      static: './dist',
      hot: true,
      port: 3000
    }
  };
};

Using SASS in Webpack Entry

src/index.js - Import SASS

// Import your main SASS file
import './scss/main.scss';

// Your JavaScript code
console.log('Application initialized');

Vite and SASS Integration

Vite is a modern build tool with native SASS support. It requires minimal configuration and offers incredibly fast hot module replacement (HMR).

Installing Vite

Vite Installation

npm install --save-dev vite sass

Vite Configuration

Vite works with SASS out of the box, but you can customize it:

vite.config.js

import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "sass:math"; @import "@/styles/variables";`,
        includePaths: [path.resolve(__dirname, 'src/scss')]
      }
    },
    devSourcemap: true
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  build: {
    sourcemap: true,
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        assetFileNames: 'assets/[name].[hash][extname]'
      }
    }
  }
});

Vite npm Scripts

package.json - Vite Scripts

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

Gulp with SASS

Gulp is a task runner that can orchestrate complex build workflows. While less popular than Webpack or Vite, it's still used in many projects.

Installing Gulp and SASS

Gulp Installation

npm install --save-dev gulp gulp-sass sass gulp-postcss autoprefixer cssnano gulp-sourcemaps

Gulpfile Configuration

gulpfile.js

const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const sourcemaps = require('gulp-sourcemaps');

// Paths
const paths = {
  scss: {
    src: 'src/scss/**/*.scss',
    dest: 'dist/css'
  }
};

// SASS compilation task
function compileSass() {
  return gulp.src(paths.scss.src)
    .pipe(sourcemaps.init())
    .pipe(sass({ outputStyle: 'expanded' }).on('error', sass.logError))
    .pipe(postcss([autoprefixer(), cssnano()]))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest(paths.scss.dest));
}

// Watch task
function watchFiles() {
  gulp.watch(paths.scss.src, compileSass);
}

// Export tasks
exports.sass = compileSass;
exports.watch = watchFiles;
exports.default = gulp.series(compileSass, watchFiles);

Source Maps: Development vs Production

Source maps help you debug compiled CSS by mapping it back to the original SASS files. However, they should be handled differently in development and production.

Development Source Maps

// Full source maps for debugging
sass src/scss:dist/css --source-map --embed-sources

// Webpack development
devtool: 'eval-source-map'

// Vite development
css: {
  devSourcemap: true
}

Production Source Maps

// External source maps (don't embed sources)
sass src/scss:dist/css --source-map

// Or disable completely for smaller file size
sass src/scss:dist/css --no-source-map

// Webpack production
devtool: 'source-map' // or false to disable

// Vite production
build: {
  sourcemap: true // or false
}
Tip: In production, you can upload source maps to error tracking services (like Sentry) without serving them to users, providing debugging capabilities without exposing source code.

Minification and Optimization

Proper minification can reduce CSS file sizes by 40-60%, significantly improving load times.

SASS Built-in Compression

SASS Output Styles

# Expanded (readable, for development)
sass src/scss:dist/css --style=expanded

# Compressed (minified, for production)
sass src/scss:dist/css --style=compressed

Advanced Optimization with cssnano

cssnano Configuration

// postcss.config.js
module.exports = {
  plugins: [
    require('cssnano')({
      preset: ['advanced', {
        discardComments: { removeAll: true },
        reduceIdents: false, // Preserve @keyframes names
        zindex: false, // Don't modify z-index values
        mergeRules: true,
        cssDeclarationSorter: true
      }]
    })
  ]
};

Hot Module Replacement (HMR)

HMR updates your styles in the browser without a full page reload, preserving application state during development.

Webpack Dev Server with HMR

webpack.config.js - HMR Setup

module.exports = {
  devServer: {
    static: './dist',
    hot: true, // Enable HMR
    liveReload: true,
    watchFiles: ['src/**/*.scss'],
    port: 3000,
    open: true
  }
};

Vite HMR

Vite has HMR enabled by default with incredibly fast updates:

Vite HMR (Automatic)

// Just run the dev server
npm run dev

// Vite automatically detects SASS changes and updates instantly
Note: Vite's HMR is significantly faster than Webpack's because it uses native ES modules and doesn't need to bundle during development.

Complete Build Setup Example

Here's a complete package.json with all scripts for a professional project:

package.json - Complete Setup

{
  "name": "sass-project",
  "version": "1.0.0",
  "scripts": {
    "clean": "rimraf dist",
    "sass:dev": "sass src/scss:dist/css --source-map --watch",
    "sass:build": "sass src/scss:dist/css --no-source-map --style=compressed",
    "postcss:dev": "postcss dist/css/*.css --dir dist/css --watch",
    "postcss:build": "postcss dist/css/*.css --dir dist/css --replace",
    "lint:scss": "stylelint \"src/scss/**/*.scss\" --fix",
    "dev": "npm-run-all clean --parallel sass:dev serve",
    "build": "npm-run-all clean lint:scss sass:build postcss:build",
    "serve": "browser-sync start --server dist --files \"dist/**/*\"",
    "webpack:dev": "webpack serve --mode=development",
    "webpack:build": "webpack --mode=production",
    "vite:dev": "vite",
    "vite:build": "vite build",
    "vite:preview": "vite preview"
  },
  "devDependencies": {
    "sass": "^1.69.0",
    "postcss": "^8.4.31",
    "postcss-cli": "^10.1.0",
    "autoprefixer": "^10.4.16",
    "cssnano": "^6.0.1",
    "stylelint": "^15.10.3",
    "stylelint-config-standard-scss": "^11.0.0",
    "npm-run-all": "^4.1.5",
    "rimraf": "^5.0.5",
    "browser-sync": "^2.29.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "sass-loader": "^13.3.2",
    "css-loader": "^6.8.1",
    "mini-css-extract-plugin": "^2.7.6",
    "css-minimizer-webpack-plugin": "^5.0.1",
    "vite": "^5.0.0"
  }
}

Exercise 1: Set Up Basic npm Scripts

Create a new project with npm scripts for SASS compilation:

  1. Initialize a new npm project: npm init -y
  2. Install SASS: npm install --save-dev sass
  3. Create a src/scss/main.scss file with some styles
  4. Add scripts to package.json for building and watching SASS
  5. Test the scripts: npm run build:css and npm run watch:css

Exercise 2: Integrate PostCSS

Add PostCSS to your SASS pipeline:

  1. Install PostCSS, autoprefixer, and cssnano
  2. Create a postcss.config.js file
  3. Update your npm scripts to run PostCSS after SASS compilation
  4. Add a CSS property that needs prefixing (like user-select: none;) and verify autoprefixer adds vendor prefixes
  5. Compare file sizes before and after cssnano optimization

Exercise 3: Configure Webpack or Vite

Set up a complete build system with either Webpack or Vite:

  1. Choose either Webpack or Vite and install required dependencies
  2. Create the configuration file (webpack.config.js or vite.config.js)
  3. Configure SASS loading with source maps
  4. Set up development and production modes
  5. Test hot module replacement by running the dev server and editing SASS files
  6. Build for production and verify output is minified and optimized