webpack: updating to version 1.8.10 causes new error in CommonsChunkPlugin

Version 1.8.9 succeeds but when updating to 1.8.10 I get an error:

ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk

The bundle js is not output.

My build output from deploying to Heroku (this error is on line 514): https://gist.github.com/bdefore/838bbe5be65dcb114014

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 26 (8 by maintainers)

Most upvoted comments

As I’ve learned the hard way, order matters when defining your vendor chunks in the CommonsChunkPlugin. for example…

{
   entry: {
        "vendor1": [
            "jquery", 
            "lodash"
        ],
        "vendor2" : [
            "angular"
        ],
        "bundle": "./src/app.js"
    },
  plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: ["vendor2", "vendor1"],
                        minChunks: Infinity
        }),
        new HtmlWebpackPlugin({
            title: 'My App Title',
            template: 'index.html',
            favicon: './assets/images/favicon.ico',
            inject: 'body',
            chunks: [
                "vendor1",
                "vendor2",
                "bundle"
            ]
        })
    ]
}

This configuration works. Notice how in the new webpack.optimize.CommonsChunkPlugin invocation the vendor chunks are in reverse order. If i swap them I get errors in my resulting index.html Seems like a bug in the CommonsChunkPlugin internals. Thoughts?

So through a lot of trial and error, I’ve ended up with a rather monolithic config for multiple common chunks with cache busting (truncated where possible…):

import webpack from 'webpack';
import AssetsPlugin from 'assets-webpack-plugin';
import ChunkManifestPlugin from 'chunk-manifest-webpack-plugin';
import WebpackMd5Hash from 'webpack-md5-hash';


export default {
  entry: {
    polyfills: ['babel-polyfill'],
    react: ['react', 'react-dom'],
    page: '(insert path here…)',
    // insert more pages here…
  },
  plugins: [
    // Generates path-based module ids instead of fluctuating integer ones
    new webpack.NamedModulesPlugin(),
    // Without this, the entry chunk hash changes due to the changing manifest
    new WebpackMd5Hash(),
    // Extracts the manifest from the entry chunk
    new ChunkManifestPlugin(),
    // Moves every node module into a vendor chunk,
    // And caches the names of their original chunks for further chicanery
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: (module) => {
        const { userRequest, chunks } = module;
        const check = typeof userRequest === 'string' &&
          userRequest.indexOf('/node_modules/') >= 0;
        // eslint-disable-next-line no-param-reassign
        module.originalChunkNames = chunks.map(({ name }) => name);
        return check;
      },
    }),
    // Moves the react and react-dom modules (and their deps)
    // back into their original chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'react',
      chunks: ['vendor'],
      minChunks: ({ originalChunkNames }) => originalChunkNames.includes('react'),
    }),
    // Moves babel-polyfill (and its deps) back into their original chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'polyfills',
      chunks: ['vendor', 'react'],
      minChunks: ({ originalChunkNames }) => originalChunkNames.includes('polyfills'),
    }),
    // Moves the entry (webpack bootstrap) out into its own dedicated chunk.
    // This could be rolled into polyfills but at this point it created
    // less headaches during the debugging of this whole thing…
    new webpack.optimize.CommonsChunkPlugin({
      name: 'entry',
      chunks: ['vendor', 'react', 'polyfills'],
      minChunks: Infinity,
    }),
    // Creates a JSON map of the chunk names to their built files
    new AssetsPlugin(),
    // May be redundant now that NamedModulesPlugin is used…?
    new webpack.optimize.OccurenceOrderPlugin(),
  ],
};

Which enables loading of the manifest (via inline script) followed by entry, polyfills, react, & vendor… before finally loading one of the desired page entry points. Each of these 4 chunks will only change their hash when their contents change. (e.g. only vendor and page will get a new hash if a new vendor module is added to page, and react only changes if the library does.) Each one depends on the others higher-up the chain, so as mentioned earlier, some of them could be rolled together for less complexity (entry & polyfills).

Some observations after going through all this:

  1. The calculation of which chunk became the special “entry” chunk that has the webpack bootstrap was extremely confusing. It took me a long while to grasp that there was some sort of baton-passing or re-thievery of the bootstrap going on between multiple instances of the CommonsChunkPlugin plugin. It wasn’t just the order, they also had to be chained together to move the bootstrap down the chain to the last one…
  2. The bug/side effect of the entry chunk’s hash changing even after the manifest was removed added another level of headache on top of trying to understand the movements of the bootstrap. Hence why it ended up siphoned off into it’s own entry chunk.
  3. It was only through trial and error I managed to get a bit of a handle on how the chunks option works, and the children option is still a mystery to me. The chunks documentation mentions “The chunk must be a child of the commons chunk”, and children says “If true all children of the commons chunk are selected”, but what exactly is a parent/child relationship in this plugin? And how do these options relate to “While running in normal mode it’s not allowed to use a non-entry chunk”? “Normal mode” (or “mode” in general) is not mentioned outside of the error message.
  4. It feels ugly that I’ve had to do a lot of custom magic via minChunks and module mutation just to be able to create multiple common chunks. It’s easy to make examples of multiple static common chunks(via names), or one dynamic common chunk (via a minChunks function), but it feels very hard trying to combine those two configs together in a way that makes sense.
  5. Due to the scale of debugging, I still haven’t accounted yet for creating a common chunk for shared internal code…
  6. babel-polyfill modifies globals, so has a special need to be loaded early before other libraries, but there isn’t much documentation on how to load special cases like this for non-trivial entry points.

(Sorry for the barrage of words…)