Two kids in a pillow fight

Webpack and LESS

  • Kristofer Giltvedt Selbekk

So a friend of mine, Frode, asked me a question about how Webpack and LESS works, and how you set that up. Instead of writing a one-off answer, I thought I’d write it as a quick article here on Medium — so other people can benefit from my answer as well.

A note about dependencies

If you want to follow along, you need to create a new folder, run npm init -y and install the following dependencies:

npm install --save-dev webpack webpack-cli less less-loader css-loader style-loader mini-css-extract-plugin

Each of these dependencies will be explained as you follow along this article.

How to get started

First things first — let’s set up a basic webpack.config.js file:

const path = require('path');

const config = {
    // First, let's define an entry point for webpack to start its crawling.
    entry: './src/index.js',
    // Second, we define where the files webpack produce, are placed
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
    },
};

module.exports = config;

Here, we specify that we want to load the file src/index.js , and create a graph of dependencies from there. Whatever we end up with, we want to bundle it all up in one big ol’ file in dist/bundle.js . If you’ve worked with Webpack at all before, you’ve probably seen something like this.

For reference, let’s assume our src/index.js looks like this:

import './my-styles.less';

document.querySelector('body').classList.add('with-styles');

And our src/my-styles.less looks like this:

@color: palevioletred;

.with-styles {
  background-color: @color;
}

Now we’re ready to start making this work!

Processing LESS files with webpack

Whenever you import a file, like we do in the index.js above, webpack runs it through a list of “rules” array and checks if there are any rules set up to handle that kind of file. In our case, we haven’t specified our rules — so only JavaScript files are supported by default.

To add support for LESS-files, we need to set up a rule that processes these as well. We want to tell webpack that anytime it’s required to import a LESS file, it should run it through the LESS compiler, resolve any relative URLs and then export it somehow.

Here is how that looks:

const path = require('path');

const config = {
    // First, let's define an entry point for webpack to start its crawling.
    entry: './src/index.js',
    // Second, we define where the files webpack produce, are placed
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
    },
    module: {
        rules: [
            { 
                test: /\.less$/,
                use: [ 
                    'style-loader',
                    'css-loader', 
                    'less-loader'
                ],
            },
        ]
    }
};

module.exports = config;

This array of rules lives inside an object called module because of reasons. Anyhow — each rule is an object with a test property, and a use -array, which specify the loaders.

The test property is a regular expression, which is run against the file name to decide whether or not the rule in question should be run on the file curerntly being processed. In this case, it checks if the file ends with .less .

The use array is a list so-called loaders — or transformations — that will be run on this file. They are read from right to left, and processes the file in a daisy-chain fashion. In our example — this is what happens:

First, the less-loader is run. This passes the content of the file on to the LESS compiler, which then returns compiled CSS.

Second, the css-loader is run. Here, any relative URLs for images, fonts etc, are resolved for us. It’s outside the scope of this article to explain the depths of it, but please have a look at its documentation if you’re interested.

Finally, the style-loader is run. This loader takes whatever text is passed to it (our compiled CSS with resolved URLs), and push it into a <style /> tag. This is great for development, and lets us update our styles when they change without refreshing the page (so called hot-reloading).

Extracting a CSS file

The style-loader is great for development, but it’s not ideal for production. We would love to extract a CSS file that we can version, cache and serve separately from our JavaScript — so that it runs even quicker!

To ensure things are different when we put things into production, we need some kind of flag to specify that we’re in “building for production” mode. The community as a whole has decided that the NODE_ENV environment set to "production" is that flag.

Now, if we are building our app for production, we want to switch out our style-loader transform with one that will extract the CSS into a file. For this, we use the mini-css-extract-plugin . It does exactly what it says on the tin — extracts your css.

To use it, we need to add it to both the loaders we use for less -files, and we need to add it as a “plugin”.

A plugin is like super-loaders, because it can do a ton of extra stuff loaders can’t — like writing files. The webpack docs explain the concept of them nicely, so I won’t jump into it here.

Once everything is added, we end up with our final webpack-config:

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

const isProduction = process.env.NODE_ENV === 'production';

const config = {
    // First, let's define an entry point for webpack to start its crawling.
    entry: './src/index.js',
    // Second, we define where the files webpack produce, are placed
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
    },
    module: {
        rules: [
            { 
                test: /\.less$/, // .less and .css
                use: [ 
                    isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 
                    'css-loader', 
                    'less-loader'
                ],
            },
        ]
    },
    // Add an instance of the MiniCssExtractPlugin to the plugins list
    // But remember - only for production!
    plugins: isProduction ? [new MiniCssExtractPlugin()] : []
};

module.exports = config;

Bonus: Source maps!

Source maps are great for debugging compiled code. They map a particular part of compiled code to the relevant part of the uncompiled code.

Webpack makes it pretty easy for you to make source maps — all you need to do is to add the devtool property. You can read about what to set it to in the documentation. Now pop open your dev-tools and jump into the source!

Bonus bonus: Read the source!

I’ve created a sample repo that implements the code above. Please look through it, and clone / install it to see it in action.

You can find the code here: https://github.com/selbekk/webpack-with-less-example

Final thoughts

Setting up LESS with webpack isn’t too hard once you know how, but it’s not very straight forward the first few times you do it.

I hope this guide helped you a bit, and that you’ll be on your merry way exploring the wonders of webpack.