-
-
Notifications
You must be signed in to change notification settings - Fork 384
Prune empty JS bundles when plugin is used in conjunction with code splitting #339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prune empty JS bundles when plugin is used in conjunction with code splitting #339
Conversation
|
49ef3a9
to
2962f42
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This problem should be solved on webpack
side.
Anyway couple new eyes will be great.
/cc @sokra @ooflorent
Also need fix CI problems and add tests
@evilebottnawi thanks for the quick reply. Do you have some thoughts on how this should be fixed in webpack? RE: CI failures. Are they worth fixing if this isn't the right approach? The failure is in regard to
|
@rowanoulton webpack doesn't should emit empty chunks, it is should be fixed in |
Update lock file and commit |
I am still getting this issue. Has it been fixed? Is there a workaround? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need tests for this
@evilebottnawi I took your earlier comment to mean this isn't the right approach. The main issue to patch being this one. Is that right? if so, I'll close this PR. |
For those following along, it looks like this is the fix. |
Relevant commit here |
This PR contains a:
Motivation / Use-Case
This is a proposed fix for #147. It's a stab in the dark and, while it seems to work, it may be the wrong approach entirely. Please advise!
For reference: the code samples below are from this reproduction shared by @dcousineau.
Outline of the issue
When MiniCssExtractPlugin is used in conjunction with code splitting and the split chunk contains nothing other than CSS, webpack will still emit a JS file. Consider the following configuration:
This will move all CSS across the entire compilation into a single file,
styles.bundle.css
. However, the compilation will also create a JavaScript bundle for this chunk under the same name:styles.bundle.js
.Often these bundles are more or less empty, save for some boilerplate:
This makes sense. The bodies of these modules are CSS, and the plugin has extracted them leaving only a comment behind.
Problematic bundles
These empty JavaScript bundles are problematic in that they must be loaded — and loaded in the correct order — for the application to boot properly. This is due to the way webpack boots, the logic of which is contained in webpack's boilerplate code. Here're the relevant parts:
This code is responsible for installing modules: executing the function each module is contained in and returning its exports. Specifically, the install handler (
webpackJsonpCallback
) is executed for every module that was loaded before the main script.Let's break it down. Here's a simplified version of
styles.bundle.js
again:You can see that, as each file loads, it injects its modules into
window['webpackJsonp']
.When the main script finally runs, it iterates over these same modules and installs them via
webpackJsonpCallback
:After all the modules are installed, webpack checks it has all the modules it needs (ie. "deferred modules"):
Note the mention of a module called "styles": this is that empty script we saw earlier, produced by the combination of MiniCssExtractPlugin and webpack's code splitting functionality.
Here's what webpack does when it checks deferred modules:
It checks that every module in the array after
./src/index.js
has been installed, or "fulfilled", before it does anything else.This is critical: If the module "styles" has not been installed already, the boot will stall. This is because the
if(fulfilled)
branch doesn't execute, and it contains the first invocation of__webpack_require__
, which is responsible for executing the main entry point and therefore the whole application.Proposed fix
Here's what we want to do:
And, if possible:
Fixing the boot flow
The problem is what's written to webpack's boilerplate code:
We need to catch and prune empty files before they're included here.
Those exact lines are written out by the JsonpMainTemplate plugin, invoked by the creation of chunk assets during compilation. This is convenient as MiniCssExtractPlugin already taps that event:
This block of code is responsible for extracting the CSS from each module. Once that extraction has happened, it seems as if we're free to do whatever we like with the leftover module. This presents an opportune moment to search for split chunks that are purely CSS and perform some tidyup:
For any purely CSS-based split chunks we reverse the work SplitChunksPlugin did by moving the empty JS modules back to their origin chunks then remove the split chunk from the module graph altogether. By this method we prevent them from being referenced by the boilerplate boot code.
Keeping the stylesheets in play
With the first problem solved, we encounter another: by removing empty split chunks from the dependency graph, the stylesheets are emitted by webpack but are ignored by plugins like
HtmlWebpackPlugin
. In practice this can mean HTML rendered without the appropriate<link>
tags.To combat this, we have to retain a dependency on our CSS split chunks. If we track which groups a given chunk was removed from, we can restore the connections after the boilerplate has been generated. First this requires a bit of extra book-keeping:
The first event after the boilerplate code has been generated is
compilation.hooks.chunkAsset
, so we do our reconnection work there:I'm especially unsure about this code but the intention is to reinsert the CSS files into every chunk that depends on those styles, and this appears to be enough to appease
HtmlWebpackPlugin
.Discarding the empty JS files
From here it's just a small jump to discard the empty JS bundles altogether. We simply branch from the previous file extension check and, if it's JavaScript, delete it:
This is undoing the work that would have just happened in the webpack compiler.
Outcome
Applying these changes to the aforementioned reproduction gives us the result we want:
styles.css
)styles.js
)I've confirmed the same is true of a webpack-dev-server build, and tested with both
html-webpack-plugin@latest
andhtml-webpack-plugin@next
.Breaking Changes
Unclear. This change will mean empty JS bundles are no longer output for every CSS split chunk. That may break workarounds people have been using.
Additional information
I'm not convinced this is the best way to fix this problem, and could use some input from someone better versed in this plugin and with webpack in general.