(Webpack) How to chunk dynamic module dependencies
Asked Answered
T

2

17

I just realized that if you load modules dynamically using require.ensure(), webpack will not analyze and chunk dependencies together. This makes sense in some way that one could argue, that webpack can't know if such modules are transferred ever, but can we force webpack to do the job anyway?

Example is:

app.js:

require.ensure([ 'module1.js' ], ( require ) => {
    // at some point
    require( 'module1.js' );
}, 'Module1');

require.ensure([ 'module2.js' ], ( require ) => {
    // at some point
    require( 'module2.js' );
}, 'Module2');

module1.js

let io = require( 'socket.io-client' );

module2.js

let io = require( 'socket.io-client' );

The outcome of this compilation is, that both of these modules get the whole socket-io library "linked" into their chunks. My original expectation was, that the CommonsChunkPlugin will catch those requires and put that big library into a common chunk.

new webpack.optimize.CommonsChunkPlugin( 'common' ),

Doesn't work however. Of course I could always "resolve" this dependency manually, but I hoped that webpack can do the trick somehow?

Theiss answered 1/9, 2016 at 12:59 Comment(2)
Doe's setting minChunks to 2 in CommonsChunkPlugin options change situation?Octagon
Unfortunately not.Theiss
O
5

Answer is hidden in configuration of CommonsChunkPlugin

new webpack.optimize.CommonsChunkPlugin({
  name: 'main', // Important to use 'main' or not specify, since 'main' is default
  children: true, // Look for common dependencies in all children,
  minChunks: 2, // How many times a dependency must come up before being extracted
});

children: true is the main part of this configuration. From docs:

If true all children of the commons chunk are selected


Edit for async common chunk

If you want to download asynchronously common code in chunk, you should change above configuration with addition of async: true

new webpack.optimize.CommonsChunkPlugin({
  name: 'main',
  children: true, 
  minChunks: 2, 
  async: true, // modification
});

From docs about async:

If true a new async commons chunk is created as child of options.name and sibling of options.chunks. It is loaded in parallel with options.chunks. It is possible to change the name of the output file by providing the desired string instead of true.

Now there is created additional chunk containing only socket.io-client from your example. This is close to original example in webpack docs.

Octagon answered 6/9, 2016 at 10:39 Comment(11)
Ahh, I really hoped this was it, but as it turns out, it won't help in this scenario. All that happens if I turn on children: true is that my "common"-chunk is now linked together with main-bundle. But it won't effect the dynamic loaded bundles. Both still get that shared library/require linked into their chunk.Theiss
Does async: true do the job?Octagon
No difference. Both dynamically modules still get linked the same socket-io library each. I'm testing all on webpack 2.1.0-beta.20, so pretty much latest.Theiss
Oh I showed answer for the newest stable v1.13.2Octagon
I shouldn't make a difference, but of course .. might be. Here is my webpack config alongside the module code for this little test case: jsbin.com/lekulanuqa/edit?js Did you try yourself? Does it work for you as expected?Theiss
Please use this part: name: 'main'Octagon
Indeed that did the trick. Alongside children: true it creates the expected behavior (linking the socket-io lib into the main chunk). I thought the name property there is just some random id respectively sets the output filename... Thanks for now, that might shot the bounty too.Theiss
I was surprised too. You can omit name config option since default value is 'main'. If you want to specify filename of common code you should use filename: 'shared.js' .Octagon
I don't really get it either. If I just config the CommonChunksPlugin to minChunks: 2 and let's say name: shared, it creates an additional bundle called shared-bundle.js where only webpacks jsonPloader script is included. If I configure it like you suggested with name: main and children: true, it'll only create 3 chunks (the main-bundle and one for each dynamic module). The jsonpLoader script also goes into the main-bundle then.Theiss
The only thing which bothers me a little bit is that the socket-io library (or any module which is shared by dynamic modules) gets linked into the main-bundle. It would be much more clean and great if the shared stuff which be separated into its own bundle. That way, you don't have to transfer the shared code (probably the dynamic modules are never going to be loaded). Only if one of the dynamic modules gets required, it should transfer the shared-bundle the first time. Any idea for that?Theiss
async: true creates separate chunk with common stuff, which is required by jsonpLoader only by chunk which will use this common code.Octagon
T
0

So far I found one possible solution. If you use webpack's require.include() method to just include (not evaluate) the "shared library, here socket.io-client" also in the parent module, here app.js, the CommonChunkPlugin will now be able to sort things out correctly.

require.include( 'socket.io-client' ); // import io from 'socket.io-client'; also works
require.ensure([ 'module1.js' ], ( require ) => {
    // at some point
    require( 'module1.js' );
}, 'Module1');

require.ensure([ 'module2.js' ], ( require ) => {
    // at some point
    require( 'module2.js' );
}, 'Module2');

However, this doesn't seem right to me since this IS a manual resolving of dependencies, which is actually not what I want have to do using something like Webpack.

Theiss answered 6/9, 2016 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.