← Return Home

Code splitting

Code splitting is a Webpack feature that enables a JS bundle within a single build to be split up and loaded on-demand in smaller parts. Code splitting is appropriate within a single page and build.

Basic Example

(Example source available at: github.com/FormidableLabs/formidable-playbook/tree/master/examples/frontend/src/es5)

Let’s start with some source code (the same as we will use for the shared library).

foo.js

module.exports = function (id, msg) {
  return "<h1 id=\"" + id + "\">" + msg + "</h1>";
};

app1.js

var foo = require("./foo");

document.querySelector("#content").innerHTML += foo("app1", "App 1");

app2.js

var foo = require("./foo");

document.querySelector("#content").innerHTML += foo("app2", "App 2");

… so basically two separate “apps” that will add the headings App 1 and App 2 to a page using the same foo() method…

Code Splitting Example

(Example build / dist code available at: github.com/FormidableLabs/formidable-playbook/tree/master/examples/frontend/webpack-code-splitting)

Code splitting allows us to extract the common parts of both entry points, which in our case is the foo.js file. We can accomplish this with a single webpack configuration:

webpack.config.js

var path = require("path");
var webpack = require("webpack");

module.exports = {
  context: path.join(__dirname, "../src/es5"),
  entry: {
    app1: "./app1.js",
    app2: "./app2.js"
  },
  output: {
    path: path.join(__dirname, "dist/js"),
    filename: "[name].js",
    pathinfo: true
  },
  plugins: [
    // Abstract a common file between apps.
    new webpack.optimize.CommonsChunkPlugin({
      name: "commons",
      filename: "commons.js"
    })
  ]
};

This produces three files:

Let’s look at these files in detail:

dist/js/commons.js

/******/ (function(modules) { // webpackBootstrap
/******/  // SNIPPED
/******/  window["webpackJsonp"] = // DEFINITION
/******/  // SNIPPED
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/* unknown exports provided */
/* all exports used */
/*!****************!*\
  !*** ./foo.js ***!
  \****************/
/***/ function(module, exports) {

module.exports = function (id, msg) {
  return "<h1 id=\"" + id + "\">" + msg + "</h1>";
};


/***/ }
/******/ ]);

dist/js/app1.js

webpackJsonp([1],[
/* 0 */,
/* 1 */
/* unknown exports provided */
/* all exports used */
/*!*****************!*\
  !*** ./app1.js ***!
  \*****************/
/***/ function(module, exports, __webpack_require__) {

var foo = __webpack_require__(/*! ./foo */ 0);

document.querySelector("#content").innerHTML += foo("app1", "App 1");


/***/ }
],[1]);

dist/js/app2.js

webpackJsonp([0],{

/***/ 2:
/* unknown exports provided */
/* all exports used */
/*!*****************!*\
  !*** ./app2.js ***!
  \*****************/
/***/ function(module, exports, __webpack_require__) {

var foo = __webpack_require__(/*! ./foo */ 0);

document.querySelector("#content").innerHTML += foo("app2", "App 2");


/***/ }

},[2]);

The commons.js file does indeed contain our common code and bootstrap loader, leaving us with very small app1|2 files.

Once we build these files, we can load the common chunks and both apps with the following webpage:

index.html

<!DOCTYPE html>
<html>
  <body>
    <div id="content" />
    <script src="./dist/js/commons.js"></script>
    <script src="./dist/js/app1.js"></script>
    <script src="./dist/js/app2.js"></script>
  </body>
</html>
Automatic Splitting

(Working example available in the github.com/FormidableLabs/formidable-playbook/tree/master/examples/frontend/webpack-code-splitting-ensure directory.)

It is worth noting that while code splitting is often used in conjunction with lazy loading, the two are not synonymous. The previous example splits a shared commons.js bundle using the CommonsChunkPlugin, but is not lazily loaded. The example HTML page has three normal <script> tags to load the entry points.

The code could be re-written to manually lazy load app1 or app2 on demand. Or, as an even easier option, Webpack can create entry points automatically from a single root entry point if desired. These inferred entry points can then be lazily or conditionally loaded with a little extra in-application JavaScript logic.

So, we could tweak our example configuration of:

  entry: {
    app1: "./app1.js",
    app2: "./app2.js"
  },

to point to a new file that async loads both apps like:

// Note 1: must specify dep in array, then can require in callback.
// Note 2: callback param **must** be named `require`.
require.ensure(["./app1"], function (require) {
  require("./app1");
});
require.ensure(["./app2"], function (require) {
  require("./app2");
});

and then reconfigure Webpack to just find the hypothetical entry point:

entry: {
  entry: "./entry.js"
},

and Webpack will mostly split things up the same way. This approach is particularly useful for the very common case of React application-based routes (via any router) so that you have (1) a common chunk of code load first, then (2) only the specific code needed for a route is lazily loaded on demand.

Advantages
  • Terse Common Bundle: Webpack takes care of only adding the libraries to the common bundle that are actually common to multiple chunks / entry points.

  • Single build step: Webpack generates the common and entry point chunks as part of a single build.

Disadvantages
  • Cannot be shared across projects: The common bundle created with code splitting deals with indexes based on the entry points in a single build. The resulting bundle cannot be shared across projects / builds.

  • Cache hits: Because the common bundle uses only what is defined in the constituent apps and is reliant on index ordering, it is unlikely to have repeated cache hits across code changes without significant external Webpack hacking.