Code-splitting with dynamic import — Test and Learn

Understanding how dynamic import works from a simple demo application

Sean Oh
6 min readFeb 17, 2019
Photo by Fancycrave on Unsplash

Code-splitting and dynamic import are one of the compelling features of bundling tool like Webpack, Parcel, and Bazel.

With these features, you can split your code into small chunks then be loaded on demand only when it’s needed. This can improve the page load time of your application.

But have you ever wondered what will be your chunks looks from actual build? What will happen if the application uses dynamic import from chunks that imported dynamically? Will my code be split as a chunk when it’s dynamically imported but not from others? I was also curious about these scenarios, therefore I decided to build a simple application and test them out.

Application diagram

All of the characters are from Resident Evil.

First, we are creating multiple routes using characters from Resident evil — one of the most famous video game franchise. Each character has own inventory slot and two weapons are pre-loaded. Everyone is using the same knife but the projectile is different.

Building the demo application

To build this demo, we are using Webpack v4, React, and React-loadable for loading component with dynamic import.

The source code is available from Github.

click here for Live demo

Test #1: Static import with vendor splitting

Our first test is starting with static (regular) import to load characters from the router. Also, we are doing a basic vendor splitting from Webpack.

Now we are building the app with Webpack Bundle Analyzer plugin to understand how the codes are being split into chunks.

Webpack bundle analyzer result from test 1.

Webpack created two chunks of javaScript files. One is ‘vendors.js’ that holds all of the resources from node_modules folder. On the other side, we have ‘main.js’ (blue colored area) which is our application codes.

Test #2: Import characters dynamically

The result from the previous test seems alright, however, this will load all of the character data even if the user did browse on a single character. This may impact on load time and more as our app grows.

Now, let’s load those characters using dynamic import and see what we can gain from it.

As mentioned earlier, in this demo we are going to use React-loadable to import react component dynamically. However, you will get the same result with React.lazy or from other techniques that support dynamic import.

And here is the result.

Webpack bundle analyzer result from test 2. Vendors chunk file has filtered out from the screen.

It looks like we’ve successfully created characters to separated chunks!

Hm… but wait, there’s something that doesn’t look right. Let’s have a closer look at files.

Webpack bundle analyzer result from test 2. Showing character files only.

We can see that Knife has bundled to every character and also file named Inventory Window which it’s working as an item container. It’s happening because every character is importing these modules. As a result, it duplicates the code and application will download them all again even if those particular modules are already available from other resources.

Test #3: Creating a shareable chunk

From the last test, we were able to split the character using dynamic import. However, there are some duplicated modules placed to every character and we’d like to make them reusable. Fortunately, this can be done very easily from Webpack by using SplitChunksPlugin.

We are going to add a new cache group default in splitChunk option. minChunks is set as 2 and this will take the module that is used more than once across all characters. Options we are using here is very minimal, but this would be enough for what we are trying to achieve.

Let’s check out the result.

Webpack bundle analyzer result from test 3. Vendors and main files have filtered out from the screen.

All of the modules are being separated as we expected. Now let’s inspect the network activity from the actual application.

Network speed has throttled down to 3g to demonstrate lazy load.

It’s working correctly! Now our application is downloading characters and shareable module only when it’s needed.

Test #4: Import weapons dynamically

From the last test, we have successfully split the characters and created a chunk that holds the shared module from all characters.

Now, one of our character Leon wants to use Magnum to shoot zombies.

Leon wants Magnum now.

Okay, I think this won’t be a problem since we already have a shareable chunk. Let’s add Magnum to his inventory.

Webpack bundle analyzer result from test 4. Vendors and main files have filtered out from the screen.

As expected, the Magnum code has now become a part of default. Also, Claire is not holding any weapon because of every weapon she has in her inventory is already stashed to default chunk. It seems that our code is pretty much optimized, isn’t it?

Well, not quite. One of our character Chris does not need a Magnum to beat zombies, however, it is a part of the shareable chunk. This means users need to download Magnum code even they browse only on Chris.

Let’s import Magnum dynamically from Leon to fix this issue.

Hm… what happened here? Our bundle didn’t change a bit. Is it because we are dynamically importing from a module that is also being dynamically imported?

Oh, we’ve forgotten that Claire also has Magnum. Let’s quickly update her code to use dynamic import.

Webpack bundle analyzer result from test 4. Using dynamic import to load Magnum from all characters

Finally, Magnum code has separated from default chunk. This explains that the module should be imported dynamically from across all code otherwise bundler cannot split the code correctly.

Okay, let’s load all of the projectile weapons dynamically and check the final build.

Webpack bundle analyzer result from test 4. All of the projectile weapons are loaded dynamically.
Network speed has throttled down to 3g to demonstrate lazy load.

Based on the demo video, XHR is happening in this sequence:

1. Main
- vendor
- main
2. Leon
- default
- Leon
- Handgun
- Magnum
3. Claire
- default (ignored, using cached module fetched through Leon)
- Claire
- Magnum (ignored, using cached module fetched through Leon)
4. Chris
- default (ignored, using cached module fetched through Leon)
- Chris
- Shotgun

Conclusions

From the last 4 tests, we’ve tested and learned how to split code from different scenarios. Code-splitting provides many benefits compared to the single bundle file from this example.

However, an application using dynamic import must be carefully designed and balanced depends on what it really needs. Although a dynamic import can improve page load time, a frequent asynchronous load can impact on user experience. To circumvent this problem consider using prefetching/preloading and use the shareable chunks for storing code globally used in the application.

Thanks for your time to read this article. Happy coding!

Check out the code: Github, Demo

--

--