webpack: Webpack 4 hot rebuild time twice as slow

Originally posted this issue on webpack-dev-server’s repo, but was told nearly all of HMR is handled by Webpack itself, so I am moving that issue over here.

  • Operating System: Mac OS High Sierra
  • Node Version: 8.10.0
  • NPM Version: 5.6.0
  • webpack Version: 4.1.1
  • webpack-dev-server Version: 3.1.1
  • This is a bug
  • This is a modification request

Code

Note that the webpack file I posted isn’t exactly what we have, but anything removed was just company info, me merging a common webpack file with our dev one to produce a single code snippet, and removing some unrelated/redundant info (e.g. multiple templates with similar content).

  // webpack.config.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const config = require('./webpack.common.config.js');
const Jarvis = require('webpack-jarvis');

module.exports = merge(config, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  output: {
    publicPath: '/',
    filename: '[name]-[hash].js',
    crossOriginLoading: 'anonymous',
    chunkFilename: '[name]-[hash].js'
  },
  optimization: {
    noEmitOnErrors: true,
    namedModules: true,
  },
  plugins: [
    new CopyWebpackPlugin([{
      from: 'app/assets/images/favicon/',
      to: 'favicon/',
    }]),
    new CopyWebpackPlugin([{
      from: 'app/assets/images/square/logo.png',
      to: 'logo.png',
    }]),
    new ExtractTextPlugin({
      filename: '[name]-styles-[contenthash].css',
      allChunks: true,
    }),
    new DeterministicHash(),
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    new HtmlWebpackPlugin({
      template: 'templates/index.ejs',
      filename: 'index.html',
      inject: false,
      chunks: ['common', 'main'],
      appleTouchIconSizes: [57, 72, 114, 120, 144, 152],
    }),
    new Jarvis({port: 7003}),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      _DEVELOPMENT_: true,
    })
  ],
  module: {
    rules: [
      {
        test: /\.(otf|ttf|woff|woff2|png|jpg|svg|mp3|wav|aff|dic)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: 'assets/[name]-[sha1:hash:hex:8].[ext]'
            },
          },
        ]
      },
      {
        test: /\.s?css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            'css-loader',
            {
              loader: 'postcss-loader',
              options: {
                plugins: () => [
                  require('precss'),
                  require('autoprefixer'),
                ]
              }
            },
            'sass-loader'
          ]
        }),
      },
      {
        test: /\.jsx?$/,
        use: ['babel-loader'],
      },
    ]
  },
  entry: {
    main: [
      'babel-polyfill',
      'react-hot-loader/patch',
      'webpack/hot/only-dev-server',
      'webpack-dev-server/client?https://0.0.0.0:7001',
      './app/main.js',
    ],
  }
});

  // dev-server.js
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const config = require('./webpack.devconfig');
const ssl = require('./devssl');

new WebpackDevServer(webpack(config), {
  publicPath: config.output.publicPath,
  headers: {'Access-Control-Allow-Origin': '*'},
  hot: true,
  https: true,
  clientLogLevel: 'error',
  cert: ssl.cert,
  overlay: true,
  key: ssl.key,
  historyApiFallback: true,
  disableHostCheck: true,
  watchOptions: {
    ignored: /\/node_modules\/.*/,
  },
  stats: {
    assets: false,
    cached: false,
    cachedAssets: false,
    children: false,
    chunks: false,
    chunkModules: false,
    chunkOrigins: false,
    colors: true,
    depth: false,
    entrypoints: true,
    excludeAssets: /app\/assets/,
    hash: false,
    maxModules: 15,
    modules: false,
    performance: true,
    reasons: false,
    source: false,
    timings: true,
    version: false,
    warnings: true,
  },
}).listen(7001, '0.0.0.0', function(err, result) {
  console.log(`Serving chunks at path ${config.output.publicPath}`);
});

Expected Behavior

Similar, if not faster HMR

Actual Behavior

Hot reload time increased from ~5s to ~10s when upgrading from Webpack 3 to Webpack 4.

For Bugs; How can we reproduce the behavior?

Unsure if it reproduces in every case, but in our case we have a large webapp that spits out multiple entry points, with sass processing, along with a lot of other webpack features.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 66
  • Comments: 63 (8 by maintainers)

Most upvoted comments

i dislike typesciprt. i tried it, angularjs is the best for me. typescript is so slow, oh my god, i don’t want java in javascript => typescript. i like js and c++, it can do everything you want. sure, you think like this 👎

see https://pages.corifeus.com , based on typescript angular 6. to work on the code and see my browser, it has to compile everything, when js can do everything i need. this is my last angular 6 system. once i have 10ghz cpu and 32 core and it can split compiling using multiple cores/hyperthreads, ok!

or if they have a typescript c++ based interpreter, fine, still JS is the functional best for me!

but my workstation, with 8 hyperthreads, 4.5ghz, 32 GB ram 2133 mhz and compiling looks like recompile everything for 1 line. hate it!!!

👎

@Sojborg let try adding this line to the config:

"mode": process.env.NODE_ENV === "production" ? "production" : "development",

WARNING in configuration

The ‘mode’ option has not been set, webpack will fallback to ‘production’ for this value. Set ‘mode’ option to ‘development’ or ‘production’ to enable defaults for each environment.

You can also set it to ‘none’ to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

I did two things that hugely improved my incremental build times:

  1. The aforementioned trick around output: { ..., pathInfo: false }, helps
  2. Moving my dev sourcemaps from devtool: 'source-map' to 'cheap-module-eval-source-map' increased build time by 10x (!!!)

I just did the following things, which made the rebuild process n times faster.

  1. Use node v8.9.4

    nvm install 8.9.4
    
  2. If you are using typescript and awesome-typescript-loader, replace awesome-typescript-loader with ts-loader, then turn on transpileOnly and experimentalWatchApi

    module: {
      rules: [
      {
        test: /\.tsx?$/,
        use: [{
          loader: "ts-loader",
          options: {
            transpileOnly: true,
            experimentalWatchApi: true,
          }
        }],
      }
      ...
    }
    
  3. Turn on development mode and disable pathinfo

    const config = {
      output: {
        ...
        pathinfo: false,
      },
      mode: 'development',
      ...
    }
    

Most of the ideas come from https://medium.com/@kenneth_chau/speeding-up-webpack-typescript-incremental-builds-by-7x-3912ba4c1d15, an awesome article.

I use development mode and it became a snail. 👎

I get much worse build results after going from webpack 3 -> 4

Webpack 3.10.0 - Node 8.9.4 Build - 61 secs

Webpack 3.10.0 - Node 9.6.1 Build 65 secs

Webpack 4.4.1 - Node 8.9.4 Build 74 secs

Webpack 4.4.1 - Node 9.6.1 Build 106 secs

Are you guys looking into this decrease in performance from webpack 3 to 4?

I can also confirm that.

webpack 3: 4 sec. HMR webpack 4: 10-20 sec. HMR

(node 8.9.4)

I’m having a similar experience. Just went through an upgrade from 3 to 4, and went from a custom config to an ejected CRA config.

  • The project consists of ~3800 modules.
  • With 3 our incremental rebuilds were between 3-4 seconds, consistently.

After upgrading and using the CRA config I ended up with rebuilds between 8-12 seconds. Panic mode set in, started disabling all I could disable, and tried as many tweaks as I could think of, including but not limited to:

  • disabling splitChunks
  • disabling sourcemaps (this has no observable impact!?)
  • configure thread-loader with poolTimeout: Infinity
  • disable thread-loader alltogether (general rebuild time didn’t go down but they were more consistently in the lower 8-9 range after this)
  • configure cache-loader (had to disable this again on babel-loader as it seemed to have a negative impact when combined with babel’s caching)
  • combine cache-loader with thread-loader
  • get rid of cache-loader & thread-loader and use happypack instead (still ~8 seconds)
  • explicitly turn on cache, module.unsafeCache & module.cacheWithContext (but I’m guessing they’re on by default)
  • turn off pathinfo
  • disable CRA’s tap hook for custom messages

None of these changes made any considerable impact, even when combined. I have the feeling there’s some kind of bottleneck but I’m at a loss what to try next.

I tried running the webpack.debug.ProfilingPlugin but this keeps throwing on an incremental build (Error: Session is not connected), not sure how to work around this. The initial build time gets logged but doesn’t interest me as I only really care about the incremental times. The log file also only contains 142 entries which feels way off.

The current config is this one: https://gist.github.com/pleunv/6979a32fd8d77ea2dbc7a833437e0b4e

Any help/insight would be massively appreciated 😃. Or if you can guide me towards providing better tracing/debug logs, happy to do so.

Same issues with our project. What helped:

Guys, this solves the problem! (at least for me)

optimization: {
  splitChunks: {
    cacheGroups: {
      default: false
    }
  }
}

It seems, that webpack 4 use inside CommonsChunkPlugin with new default options, so you should disable defaults.

wow, I upgraded from node 8 to node 10 and rebuild time went down from 10s to 2s 🤯 (like before with webpack 3)

I traced my slowdown to the thread-loader… Either disable it or set its timeout to Infinity

Shouldn’t you use mini-css-extract-plugin instead of ExtractTextPlugin? 🤔

I am currently upgrading Webpack 3 (https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/master/gulp/tasks/webpack.js) and here is my PR (https://github.com/WikiEducationFoundation/WikiEduDashboard/pull/1981).

The Production version is fine. But the Development version is considerably slower in Webpack 4 when it was nearly instantaneous in Webpack 3.

Can anybody look into what should be changed? The config files are all in the above links. We aren’t using that many plugins either which are discussed here, so this is intriguing.

@cdeutsch Gave that a try just now, disabled all non-critical plugins and removed all loaders except for babel-loader and a catch-all file-loader. Still couldn’t get it below ~7 seconds, which is really weird. I did notice that webpack seems to be doing more work than necessary… it emits every single chunk every rebuild, while in 3 it only touches the chunks affected by the change. Anything I could be doing wrong here?

Guys.

I think key solution of this issue, devtool option. I change ‘inline-source-map’ into ‘cheap-module-eval-source-map’ on devtool option, and speed is faster from 15s to 2s. thanks @voxmatt you save my time.

Incase this helps anyone else, I was getting crazy slow hot reloads times after starting a new project based on a previous architecture and upgrading it to webpack 4.

Reading through this thread I tried to downgrade to an older version of webpack and realized that it was actually a performance degradation in flow 0.57.1. See https://github.com/facebook/flow/issues/5858

I’ve successfully upgraded to webpack 4.8.1. I’m getting reload times of ~2 seconds on a 4k line project based on material-ui/react-router. With flow 0.57.1 it was close to ~30 seconds or the initial startup time.

@voxmatt Oh man, you’ve saved my life, thanks a lot!

@edmorley Thank you! I just discovered that as well. It’s much much faster.

Related, if you’re using html-webpack-plugin, set templateParameters:

That issue has been fixed in the upcoming html-webpack-plugin v4 release (a pre-release is available - html-webpack-plugin@^4.0.0-alpha.2) - see jantimon/html-webpack-plugin#953 😃

Edit:

If you’re using html-webpack-plugin, be sure to use 4.0.0-alpha.3, moving from 3.2.0 decreased my incremental time from 13s to <1s.

Old post, you may ignore Related, if you're using html-webpack-plugin, set templateParameters:
new HtmlWebpackPlugin({
  // ..
  templateParameters: (compilation, assets, options) => ({
    compilation,
    webpackConfig: compilation.options,
    htmlWebpackPlugin: {
      files: assets,
      options,
    },
  }),
}

This leaves out a call to getStats().toJson() without options:

    webpack: compilation.getStats().toJson(),

Adding that dropped my rebuild from 13s to 5s (still too slow… still working…)

Thanks @searene! That reduced my incremental build times from ~13s to 0.5s ❤️

I’m not using TS and I’ve tried all of those things, none made a meaningful impact. Could someone help me profile my particular case? Still can’t get the ProfilingPlugin to work for a rebuild.

@p3x-robot can you show me your configuration?

Hot reload time increased from ~5s to ~10s when upgrading from Webpack 3 to Webpack 4.

Have you tried your Webpack 4 changes in an older version of node, say 8.9.4? For us it makes it fast again.