Karolis
Karolis

Reputation: 2618

Improve webpack (4) css build performance

I am migrating from gulp to webpack setup. I want webpack process my js and css assets and pack them into bundles. I have set up 2 webpack config files: one for js and one for css.

The total sizes of css and js assets in my project are similar: rougly 70 files (400kb minified) in each section.

My question is related to poor webpack performance when processing css assets compared to js.

To compare:

CSS builder doesn't perform very well here. Also, the larger the bundles, the longer it takes to update them, even though a change is done in a small file which should be compiled instantly.

Furthermore, it seems that increasing the number of entry points also negatively affects update times. More entries = slower updates, even though the update only affects one tiny file.

I've tried playing with cache-loader and placing it before and after extract plugin. Honestly it doesn't help much. When cache loader is placed in front of extract plugin, no css is being emitted in watch mode (only the js files). Placing cache loader after the extractor improves production build a bit, but slows down watch mode updates.

My current guess is that sass loader doesn't use caching and that on every module update, the entire bundle has to be compiled from scratch and go through sass > postcss > css > extract pipe all over again. I wonder if delegating import management to postcss-import and running sass after postcss would do a better job. I have tried to write a sass-compatible import resolver for postcss-import. It seems to work a bit faster in a small test environment, but using it in our real project causes webpack to crash :( Didn't have enough time to find out why that happens yet.

How can I improve CSS build times of my current setup?

My JS and CSS configs are below.

JS:

const path  = require('path');
const merge = require('webpack-merge');
const EntryPlugin = require('webpack-watched-glob-entries-plugin');
const dir = require('./node/dirconfig');

// use NODE_ENV var to switch between production and development
const devel = (process.env.NODE_ENV == 'development');

// base config for both prod and devel modes
let config = 
{
    name: 'scripts',

    // webpack preset
    // this should take care of production optimizations automatically
    mode: devel ? 'development' : 'production',

    // use all js files inside bundle dir as entries
    entry: EntryPlugin.getEntries(
        dir.assets + '/js/bundle/*.js'
    ),

    output: {
        path: dir.static + '/js',
        filename: "[name].js"
    },

    externals: {
        jquery: 'jQuery'
    },

    resolve: {
        modules: [dir.assets + '/js', 'node_modules']
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                    },
                },
            },
        ]
    },

    plugins: [
        new EntryPlugin(),
    ],
};


// additional config for development mode
const development = 
{
    // add eslint loader
    module: {
        rules: [
            {
                enforce: "pre", // make sure this rule gets executed first
                test: /\.js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'eslint-loader',
                    options: {
                        cache: true,
                    },
                },
            },
        ]
    }
};

module.exports = merge(config, devel ? development : {});

CSS:

const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const EntryPlugin = require('webpack-watched-glob-entries-plugin');
const dir = require('./node/dirconfig');

// use NODE_ENV var to switch between production and development
const devel = (process.env.NODE_ENV == 'development');

// base config for both prod and devel modes
let config = 
{
    name: 'styles',

    // webpack preset
    mode: devel ? 'development' : 'production',

    // use all .scss files which don't start with _ as entries
    entry: EntryPlugin.getEntries(
        dir.assets + '/sass/**/!(_*).scss'
    ),

    output: {
        path: dir.static + '/css',
        filename: "[name].js"
    },

    // enable sourcemaps in devel mode
    devtool: devel ? 'inline-source-map' : false,

    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    // 'cache-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: devel,
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            sourceMap: devel,
                        },
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: devel,
                        }
                    },
                ]
            },
        ]
    },

    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].css", // relative to path setting in the output section
        }),
        new EntryPlugin()
    ],
};

module.exports = config;

Upvotes: 0

Views: 951

Answers (1)

Guy L.
Guy L.

Reputation: 163

  1. Add thread-loader to js handling loaders.
  2. Replace css-loader with fast-css-loader and sass-loader with fast-sass-loader.
  3. Place cache-loader as the first loader of js files and after extract plugin in the css loaders.

Upvotes: 1

Related Questions