Can someone explain Webpack's CommonsChunkPlugin
Asked Answered
R

1

84

I get the general gist that the CommonsChunkPlugin looks at all the entry points, checks to see if there are common packages/dependencies between them and separates them into their own bundle.

So, let's assume I have the following configuration:

...
enrty : {
    entry1 : 'entry1.js', //which has 'jquery' as a dependency
    entry2 : 'entry2.js', //which has 'jquery as a dependency
    vendors : [
        'jquery',
        'some_jquery_plugin' //which has 'jquery' as a dependency
    ]
},
output: {
    path: PATHS.build,
    filename: '[name].bundle.js'
}
...

If I bundle without using CommonsChunkPlugin

I will end up with 3 new bundle files:

  • entry1.bundle.js which contains the complete code from entry1.js and jquery and contains its own runtime
  • entry2.bundle.js which contains the complete code from entry2.js and jquery and contains its own runtime
  • vendors.bundle.js which contains the complete code from jquery and some_jquery_plugin and contains its own runtime

This is obviously bad because I will potentially load jquery 3 times in the page, so we don't want that.

If I bundle using CommonsChunkPlugin

Depending on what arguments I pass to CommonsChunkPlugin any of the following will happen:

  • CASE 1 : If I pass { name : 'commons' } I will end up with the following bundle files:

    • entry1.bundle.js which contains the complete code from entry1.js, a requirement for jquery and does not contain the runtime
    • entry2.bundle.js which contains the complete code from entry2.js, a requirement for jquery and does not contain the runtime
    • vendors.bundle.js which contains the complete code from some_jquery_plugin, a requirement for jquery and does not contain the runtime
    • commons.bundle.js which contains the complete code from jquery and contains the runtime

    This way we end up with some smaller bundles overall and the runtime is contained in the commons bundle. Pretty ok but not ideal.

  • CASE 2 : If I pass { name : 'vendors' } I will end up with the following bundle files:

    • entry1.bundle.js which contains the complete code from entry1.js, a requirement for jquery and does not contain the runtime
    • entry2.bundle.js which contains the complete code from entry2.js, a requirement for jquery and does not contain the runtime
    • vendors.bundle.js which contains the complete code from jquery and some_jquery_plugin and contains the runtime.

    This way, again, we end up with some smaller bundles overall but the runtime is now contained in the vendors bundle. It's a little worse than the previous case, since the runtime is now in the vendors bundle.

  • CASE 3 : If I pass { names : ['vendors', 'manifest'] } I will end up with the following bundle files:

    • entry1.bundle.js which contains the complete code from entry1.js, a requirement for jquery and does not contain the runtime
    • entry2.bundle.js which contains the complete code from entry2.js, a requirement for jquery and does not contain the runtime
    • vendors.bundle.js which contains the complete code from jquery and some_jquery_plugin and does not contain the runtime
    • manifest.bundle.js which contains requirements for every other bundle and contains the runtime

    This way we end up with some smaller bundles overall and the runtime is contained in the manifest bundle. This is the ideal case.

What I do not understand/I am not sure I understand

  • In CASE 2 why did we end up with the vendors bundle containing both the common code (jquery) and whatever remained from the vendors entry (some_jquery_plugin)? From my understanding, what the CommonsChunkPlugin did here was that it gathered the common code (jquery), and since we forced it to output it to the vendors bundle, it kind of "merged" the common code into the vendors bundle (which now only contained the code from some_jquery_plugin). Please confirm or explain.

  • In CASE 3 I do not understand what happened when we passed { names : ['vendors', 'manifest'] } to the plugin. Why/how was the vendors bundle kept intact, containing both jquery and some_jquery_plugin, when jquery is clearly a common dependency, and why was the generated manifest.bundle.js file created the way it was created (requiring all other bundles and containing the runtime) ?

Rhomboid answered 17/9, 2016 at 14:53 Comment(4)
For case 1 I think you should specify the minChunks property.Lemcke
I've learned so much from your question, thanks a lot!Sienkiewicz
Thank you so much for asking this and clearing up my confusions all this time on this plugin ❤Deter
Maybe someone know, how these example will be looking like in Webpack 4?Snowfall
H
107

This is how the CommonsChunkPlugin works.

A common chunk "receives" the modules shared by several entry chunks. A good example of a complex configuration can be found in the Webpack repository.

The CommonsChunkPlugin is run during the optimization phase of Webpack, which means that it operates in memory, just before the chunks are sealed and written to the disk.

When several common chunks are defined, they are processed in order. In your case 3, it is like running the plugin twice. But please note that the CommonsChunkPlugin can have a more complex configuration (minSize, minChunks, etc) that impacts the way modules are moved.

CASE 1:

  1. There are 3 entry chunks (entry1, entry2 and vendors).
  2. The configuration sets the commons chunk as a common chunk.
  3. The plugin processes the commons common chunk (since the chunk does not exist, it is created):
    1. It collects the modules that are used more than once in the other chunks: entry1, entry2 and vendors use jquery so the module is removed from these chunks and is added to the commons chunk.
    2. The commons chunk is flagged as an entry chunk while the entry1, entry2 and vendors chunks are unflagged as entry.
  4. Finally, since the commons chunk is an entry chunk it contains the runtime and the jquery module.

CASE 2:

  1. There are 3 entry chunks (entry1, entry2 and vendors).
  2. The configuration sets the vendors chunk as a common chunk.
  3. The plugin processes the vendors common chunk:
    1. It collects the modules that are used more than once in the other chunks: entry1 and entry2 use jquery so the module is removed from these chunks (note that it is not added to the vendors chunk because the vendors chunk already contains it).
    2. The vendors chunk is flagged as an entry chunk while the entry1 and entry2 chunks are unflagged as entry.
  4. Finally, since the vendors chunk is an entry chunk, it contains the runtime and the jquery/jquery_plugin modules.

CASE 3:

  1. There are 3 entry chunks (entry1, entry2 and vendors).
  2. The configuration sets the vendors chunk and the manifest chunk as common chunks.
  3. The plugin creates the manifest chunk as it does not exist.
  4. The plugin processes the vendors common chunk:
    1. It collects the modules that are used more than once in the other chunks: entry1 and entry2 use jquery so the module is removed from these chunks (note that it is not added to the vendors chunk because the vendors chunk already contains it).
    2. The vendors chunk is flagged as an entry chunk while the entry1 and entry2 chunks are unflagged as entry.
  5. The plugin processes the manifest common chunk (since the chunk does not exist, it is created):
    1. It collects the modules that are used more than once in the other chunks: as there are no modules used more than once, no module is moved.
    2. The manifest chunk is flagged as entry chunk while the entry1, entry2 and vendors are unflagged as entry.
  6. Finally, since the manifest chunk is an entry chunk it contains the runtime.
Horary answered 20/9, 2016 at 17:58 Comment(13)
A few things I want to ask/clarify, please add these points to your answer too: 1) Can you do the same step by step explanation for CASE 1 just so the answer is 1000% complete? 2) Running the plugin with { names : ['vendors', 'manifest'] } is like running it twice, once with { name : 'vendors' } and once with { name : 'manifest' }, correct? 3) When we say "The plugin processes a common chunk" we mean it creates the contents it'll spit in the bundle.js file, in memory, correct? 4) Until it's "processed all common chunks" it hasn't written any output to a file at all, it's all in memoryRhomboid
5) Considering 2) from above and your explanation "It collects the modules that are used more than 1 time in the other chuncks: as there is no modules used more than 1 time, no module is moved." this means that the second invocation of the plugin works with the in memory results of the first invocation of the plugin (the jquery module has already been removed from the other chunks, so it just finds one instance where the complete jquery code exists, that is the contents of the vendors chunk), right? 6) "chunks" == in memory, "bundles" == output, right?Rhomboid
7) the plugin assigning the final "common" chunk as an entry chunk instead of a normal one, is, I suppose, default behaviour, right? Thanks so much for your answerRhomboid
(actually ignore 7) :) )Rhomboid
I have one more question, if you feel like answering. Let's say that in my example from above, entry1.js and entry2.js had another common file between them, other than the jquery file, let's call it ownLib.js. In CASE 2 and CASE 3, ownLib.js would end up in the vendors.bundle.js correct? How would you make it so that common files other than vendor files are separated into their own chunk, apart from the vendors chunk? Sorry for bothering you, but I am still learning how to use webpackRhomboid
Yes that is right: ownLib.js would be placed in the first common chunck. If you want to collect common dependencies in another chunck, you have to pass something like this: { names : ['common', 'vendors', 'manifest'] }.Horary
Great question, great answer, great discussion. Seems like I get it finally.Ruby
@LaurentEtiemble: new webpack.optimize.CommonsChunkPlugin({ names: ['common', 'vendor', 'manifest'], chunks: ['main', 'Home'], }), this is my config but webpack runtime is present in all common chunks.Can you explain why?Donell
@BhargaviGunda I am guessing it has something to do with the fact that you only tell it to run the plugin on chunks main and Home instead of everything?Rhomboid
I have spent the last day reading CommonsChunkPlugin docs and this is the first place I have read that, after executing, the processed chunks "are unflagged as entry". That basically explains everything I was having trouble with -- if I could upvote more than once, I would.Overrate
What would be really useful for this answer is the final configuration that achieves what the OP was after. There's so many variations mentioned in the question and answer that it's very hard to put together what the actual result looks like!Spathe
Thanks for great explanation. By any chance maybe somebody knows how to transfer those 3 variants to Webpack 4?Snowfall
Can you explain this? 'Finally, since the commons chunk is an entry chunk it contains the runtime and the jquery module.'Alexine

© 2022 - 2024 — McMap. All rights reserved.