Brief introduction to scope hoisting in Webpack

On its third major release, Webpack introduced a new feature: scope hoisting. Many developers are already exposing data showing great positive impacts on the initial execution time of their bundles.

But what is it, and how it works?

To answer this questions we first need to understand what happens with our code when we bundle it.

This article assumes solid understanding of closures and modules notation in Javascript.

Your bundles, under the hood

While modules make their way to be natively supported, bundlers like Webpack transform our import and export statements into valid JavaScript code that can run in browsers today.

Here is a quick example of this transformation:

… which turns into this:

Basically modules get wrapped into functions isolating their scope while imports get transpiled into variables holding the result of a Webpack require function.

All our wrapped modules go to an array at the end of our bundle, that’s why this featured function receives an index as a parameter.

On top of this array, Webpack adds some overhead code to go over this array and run our code.

Here is a simplified version of it.

Simplified version of Webpacks code to handle modules

A lot it’s happening here. Let’s break it down!

First, an object is created to save the result from already accessed modules.

Below, the method which replaces import statements gets declared. This method calls the function wrapping the module and populates the exports object, or returns its cached value if it was already required.

At the end, this method is called again, but this time with our entry point module index to kick off our application.

Experience and tradeoffs

Imagine a situation where a module imports a method, which needs to import another method from another module… and so on.

In our bundle, each import translates into an extra function call and a property access to the modules array as Webpack gets to the end of this import chain.

These constraints have been meassured in the past, like Sam Saccone detecting 400ms were spent only in Browserify module require in Tumblr or Nolan Lawson exposing brenchmarks on different bundlers.

But we still need modules. On big projects performance is a price we pay in exchange for better codebase scalability and encapsulation.

What is scope hoisting?

This new feature was introduced to detect where these import chaining can be flatten and converted into one inlined function without compromising our code.

Let’s picture the previously described situation where a method needs to import another.

If scope hoisting is enabled, Webpack here will see the opportunity to save one require method call by inlining the helper method like this:

No call to Webpack’s require function, no access to the modules array… Faster!

We not only save an extra function call, but also an access to the modules array, so our code runs faster than before.

How to enable scope hoisting

To use this feature you will need the latest webpack package version and add the ModuleConcatenationPlugin to your config.

This optimising technique might break in some edge cases, that’s why Webpack did a major release.

Make sure to run some tests and share the results with the Webpack team, they will love your feedback!


No time to help contribute? Want to give back in other ways? Become a Backer or Sponsor to webpack by donating to our open collective. Open Collective not only helps support the Core Team, but also supports contributors who have spent significant time improving our organization on their free time! ❤