Mitch Karajohn Mitch Karajohn - 1 month ago 15
Javascript Question

Can someone explain Webpack's CommonsChunkPlugin

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) ?


Answer

This is how the CommonsChunckPlugin works. A common chunck "receives" the modules shared by several entry chunck. A good example of a complex configuration can be found in the WebPack repository.

The CommonsChunckPlugin is run during the optimization phase of WebPack, which means that it operates in memory, just before the chunck are sealed and written on the disk.

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

CASE 1:

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

CASE 2:

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

CASE 3:

  1. There are 3 entry chuncks (entry1, entry2 and vendors)
  2. The configuration sets the vendors chunck and the manifest chunck as a common chuncks.
  3. The plugin creates the manifest chunck as it doesn't exist
  4. The plugin processes the vendors common chunck:
    1. It collects the modules that are used more than 1 time in the other chuncks: entry1 and entry2 use jquery so the module is removed from these chuncks (note that it is not added to the vendors chunck because the vendors chunck already contains it).
    2. The vendors chunck is flagged as an entry chunck while the entry1 and entry2 are unflagged as entry.
  5. The plugin processes the manifest common chunck (since the chunck does not exist, it is created):
    1. 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.
    2. The manifest chunck is flagged as entry chunck while the entry1, entry2 and vendors are unflagged as entry.
  6. Finally, since the manifest chunck is an entry chunck it contains the runtime.

Hope it helps.