Webpack: Chunking with Plugins
TIL how to use webpack plugins like ExtractTextPlugin
and CommonsChunkPlugin
to create smaller chunks out of my webpack bundles.
Click here for my previous TIL on webpack loaders.
Webpack plugins aim to fill in the gaps for what other loaders cannot accomplish. If there isn’t a loader available to accomplish your bundle-related goals, there likely exists a plugin.
The following gist contains the webpack config I will reference throughout this post.
Configuring Plugins
Plugins are added under the plugins
top level key in your webpack config. The plugins key is set to an array of the plugins to be used by webpack. You can add additional plugins to the array based on environment (like Uglify in production). Plugins are installed via NPM like any other package, and should be saved as dev dependencies.
The plugins I added to my project were used to chunk my main bundle in to smaller chunks. Chunks were created to keep similarly typed code together, and with long term caching potential in mind.
The CommonsChunkPlugin
The CommonsChunkPlugin
is an opt-in feature that comes with webpack, so it doesn’t need to be installed via npm. It is used to detect common code shared between entry points in your app and split that common code out into its own bundle. This is useful for performance, as common code can be long-term cached separately in the browser, and can be downloaded once instead of re-downloaded as a part of each bundle.
It can also be used to bundle vendor files together into their own bundle. This is the use case in my config above: the modules referenced by the vendor entry point are split out into their own bundle called vendor.min.js
. This way, common vendor code which is unlikely to change often can be separated from the main application bundle, and can be cached long term.
This both reduces the size of my main application bundle, allows for parallel loading by the browser on initial page visit, and allows for long term browser caching of vendor code that is not likely to change.
The most important property of the CommonsChunkPlugin config is the minChunks
property: this property defines the minimum number of chunks which need to contain a module before it is moved into the common bundle. In the case of the vendor bundle above, we define minChunks
to be Infinity
, which prevents webpack from moving any additional modules besides the modules referenced in the vendor entry point into the vendor bundle.
The CommonsChunkPlugin
config above results in a bundle named vendor.min.js
that is accessible at the public path at /assets/js/vendor.min.js
and contains just the code for the react library.
ExtractTextPlugin
The ExtractTextPlugin
is used to extract .less or .css files into their own bundle. By default, .css
and .less
files required by your modules (required with a require
statement) will be inlined in the same bundle as the rest of your application code. This is undesirable: ideally we would like to have our CSS in a separate file from our JS code, so we can keep bundles small and can leverage parallel loading in the browser.
The plugin is instantiated with the name of the file we’d like our extracted code to be moved to. In the example config above, our code will be available at /assets/css/[name].min.css
. The CSS extracted from an entry point bundle will have the same name as the entry point bundle itself: e.g. bundle.min.css
for the css extracted from bundle.min.js
.
In the loader config, we use the extract
method of the plugin to configure which loaders should be used by the plugin before the CSS is extracted in to its own bundle. In our case, this includes the chain of css-loader!less-loader
since we are dealing with .less
stylesheets in our app.
In addition to extracting CSS that is required by application code, the plugin can be used to create a bundle for other globally referenced stylesheets that are not explicitly required by js modules in your bundle.
In the config above, one of our entry points is named global-css
. This entry point references a .less
file that is loaded globally in our application, but is not loaded via a required
in any of our js modules. The result of this addition is two files: one called js/global-css.min.js
and the other css/global-css.min.css
. Behind the scenes, webpack is creating a JS bundle for our CSS file and then the ExtractTextPlugin is extracting the CSS from that bundle in to a standalone CSS file.
The global-css.min.js bundle will never be requested by the client, and is just a necessary artifact of the webpack bundling process.
I’m not sure if this is a “best practice”, to use webpack to bundle and load global CSS files in this manner, but it seems to work just fine!
Wrapping up
By using the plugins, loaders, path and webpack-dev-server configuration discussed in this and previous blog posts in this series, we were able to keep our entry HTML template lean without any environment specific configuration. Our config to create bundles for development is now almost identical to our config for prod (we add additional plugins for minification and obfuscation for prod).
This effort lead to better dev/prod parity, and a single source of truth for webpack configuration in our application.
Stay tuned for a final post about some of the enhancements that webpack 2 can bring to your build process!
Comments