Webpack and Dynamic Imports: Doing it Right

May 04, 2018 0 Comments

Webpack and Dynamic Imports: Doing it Right

 

 

In old versions of Webpack (v1), we commonly used the AMD “require” or the specific Webpack “require.ensure” to dynamic load modules. But for this article, I’m going to use the proposed ES2015 dynamic imports supported by Webpack, since the v2, through a babel plugin and the extra specific Webpack features for it.

The syntax is pretty simple. With the above ES proposal the keyword import gets more power and turns also into a function which returns a Promise:

import("module/foo").then(foo => console.log(foo.default))

The above code will load the foo module at runtime, and resolving it, will log the default export of the module. As the import is a function receiving a string, we can do powerful things like loading modules using expressions.

Let’s suppose you have an app that have a different behaviour and visuals in some feature for mobile to desktop. In this case having only a responsive design don’t cover what you want, so you build a page renderer which loads and renders the page based on the user platform. It basically uses a strategy pattern that chooses which module should be loaded on runtime. As a smart developer, you don’t want to load the entire code for desktop if the user is on mobile, and vice versa. Let’s check it on the code bellow:

🤓 - “But hey, this is a pretty simplist approach. Real world apps don’t have only one page at all! I have a component repository with a lot of pages in my app!”

True, even if we’re dynamic loading the components, this stills a pretty attached solution. Let’s refactor our function:

🤓- “Still not good! My app is made to be accessible from a lot of specific platforms like mobile, desktop, tablet, VR and can be even more in the future!”

Ok, ok. Let’s refactor again:

Now in this example, we’re taking a more functional approach. Created and exported a composite function to do the work, which is able to load for any platform we want using expressions, plus we already exposed two loaders, one for desktop and other for mobile.

In Webpack normally we load images as modules using the file loader. The file loader will basically map the emitted file path inside a module.

import(assets/images/${imageName}.jpg).then( src => ... )

The problem is if you want to dynamic load a file, in this case an image, Webpack by default generate a chunk for that module, something similar to this:

The big issue with that is when you request dynamic imported images, it will do a network request to get the chunk and then another one to get the image, adding unnecessary overhead to your app. If you’re using HTTPS is even worse!

🤓 - “Ok, I do this for a lot of images, this turned into a big problem and because of this extra requests the images are slower to load. How to solve this problem?”

Webpack adds a really nice feature to the dynamic imports, the magic comments. With that you can add some metadata, readable for Webpack, in a way that you can choose strategy on how Webpack generates and loads the chunks.

To solve the problem of dynamic loading files, we can simply choose the loading strategy by doing this:

import(/* webpackMode: "eager" / assets/images/${imageName}.jpg)

This will force Webpack to include the file chunk inside the parent bundle/chunk, forcing it to not create a separated chunk for that. This way, all the file paths will be promptly available when your app loads the parent bundle/chunk.

There are four different methods (lazy, lazy-once, eager, weak). You can take a look into the descriptions in more details here.

🤓 - “Hey, I noticed that Webpack just put numbers to generated chunks. This makes debugging harder, as I don’t know if one specific chunk were loaded or not!”

As we can control the loading strategy, we can also use the magic comments to control the generated chunk names too, simply doing this:

import(/ webpackChunkName: "foo-image" / "assets/images/foo.jpg");
import(/
webpackChunkName: "bar-module" / "modules/bar");

Instead of numbers, Webpack will use the chosen names to the generated chunks.

Note: This feature was added on Webpack v4.6

If you’re using HTTP2 is better to break the big bundles in smaller pieces. So, is better to preload that small image chunks than add it to the bigger bundle/chunk right?

To do so, we can simply use, instead of webpackMode: “eager” the webpackPrefetch: true which makes the browser download the chunks after the parent bundle/chunk.

🤓 - “But what is the diference between prefetch and preload?”

Webpack docs explains in more details:

- A preloaded chunk starts loading in parallel to the parent chunk. A prefetched chunk starts after the parent chunk finish.
- A preloaded chunk has medium priority and instantly downloaded. A prefetched chunk is downloaded in browser idle time.
- A preloaded chunk should be instantly requested by the parent chunk. A prefetched chunk can be used anytime in the future.
- Browser support is different.

As prefetch makes the chunk be loaded on the idle time, you can add numbers as the parameter to say to Webpack what is the priority of each one:

// 0 is same as true
import(/
webpackPrefetch: 0 / "assets/images/foo.jpg");
// this loads first as 1 > 0 (or true)
import(/
webpackPrefetch: 1 / "modules/bar");
// this one will be the last!
import(/
webpackPrefetch: -100 */ "modules/slowpoke");

The bar.js module have higher priority to load, so will be prefetched before foo.jpg and slowpoke.js will be the last one(priority -100).

If you want to check the “how to” make a lazy loaded single page application (SPA) using the discussed dynamic import, you can check out two of mine previous articles on this subject. Although the articles uses React and React+Redux on the examples, you can apply the same very idea in any SPA based framework/library:

Code splitting is a powerful thing to make you application faster, smartly loading it dependencies on the run. But as uncle Ben once said:

Know how the tool works in essential to use it maximum performance, and I hope I helped you to know a little more about it now! And don’t forget, if you liked, show your support giving some claps 👏!


Tag cloud