20

Webpack From Zero to Hero - Chapter 4: Dynamic Imports and Code Splitting

 5 years ago
source link: https://www.tuicool.com/articles/777nMnr
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Webpack From Zero to Hero

Chapter 4:Dynamic Imports and Code Splitting

maAru2Y.jpg!web3QjeamJ.jpg!web
Big chunk — Picture under Creative Commons License (source:  Flickr )

This article is part of the Webpack from Zero to Hero series , for more background or for the index you can check theChapter 0: History.

Previous - Chapter 3: Everything is a Module .

Next - Chapter 5: Route Based Code Splitting with React (coming soon).

Introduction

S ometimes you want to make your app more flexible, but responsive design is not enough and your UI needs not only a different look and feel but another behavior on different platforms.

You may want to swap business logic at runtime , depending on your user actions. Or your app became too big and you may just want to load things only when necessary.

For all this, we have a solution, and the most important thing is an ECMA proposal !

Webpack supports it (of course with some differences, same as with the official static import) and adds a nice behavior to it: code splitting.

eaqA3iq.gif

How it works

The syntax is pretty straightforward, we have a function that returns a Promise.

Let’s say we have a dumb module called module-1.js :

JfAvInm.jpg!web

To import it dynamically we do something like this:

JfAvInm.jpg!web

But there’s a problem if you try to run it:

BjqUJjv.png!webia2iE3r.png!web

Enabling Dynamic Imports on Babel

The problem is because dynamic imports is only an ECMA proposal , there’s a good chance that this will be a reserved syntax of JavaScript, so BabelJS will “complain” when parsing it. In fact, if you remove the babel-loader rule, you can see that Webpack works fine with it:

iaQrqaU.png!webqIB7v2q.png!web
If you comment out the babel-loader rule, Webpack will bundle with no errors

But hey, if you look up the error, Babel gives you this tip :

“Add @babel/plugin-syntax-dynamic-import (https://git.io/vb4Sv) to the ‘plugins’ section of your Babel config to enable parsing.”

So let’s install it:

yarn add @babel/plugin-syntax-dynamic-import --dev

…and enable it in .babelrc :

JfAvInm.jpg!web

Enabling babel-loader rule again and running yarn start:dev , now all works fine!

What Happens to the Bundle?

If you check the build logs, you’re going to see this:

yyieqyV.png!webVnYFbaz.png!web

That 1.js means Webpack created a “chunk” and included our dynamic imported module inside of it.

If you run the dev server and put a breakpoint right on the `import()` you will not see the 1.js on the network tab, but as soon you release the breakpoint, the request to fetch it is done.

Visualizing it

When the project grows, you probably will find it necessary to check how your bundle ended up, and how to optimize it: “ what if I do this tweak ?”. But not everyone is a Hackerman and is able to read this output extracting all the information from it.

bYFfueQ.gif

However, Webpack has plenty of options for visualizing the final output and making it more readable. The one that I like most is Webpack Bundle Analyser . Let’s check how it works. First, install it:

yarn add webpack-bundle-analyzer --dev

Add this to the npm scripts on package.json :

JfAvInm.jpg!web

There are two things happening here:

  • Command composition: the analyse script will result in:
webpack --mode=production --env.analyse
  • We’re giving an environment variable to Webpack, and we’ll be able to access it within the configuration.

Now, let’s setup our webpack.config.js and use the given --env.analyse parameter on it:

JfAvInm.jpg!web

Now just run the npm script and the bundle analyzer will automatically open a new tab in your default browser:

yarn analyse

You’re going to see this:

VNJZZ3b.png!webEvMFje2.png!web
Above we can see the main bundle (main.js) and the module-1 chunk (1.js)

Loading Using Expressions

So now we can see that Webpack splits your code by default when you do a dynamic import, but let’s go further and play around it.

Let’s create a new module called module-2.js which exports only this function:

JfAvInm.jpg!web

You can see that there’s a pattern for our module names, where every module is marked by its number. This way it is possible to use an expression to import them:

JfAvInm.jpg!web

This will output the default functions of both modules 1 and 2 as a result:

This function does nothing and I’m totally useless! Deal with it! :sunglasses:

And if you analyze the bundle, you will see that Webpack automatically creates a context based on your expression :

aUVvEjU.jpg!web3MjmQ3E.jpg!web

You see the power of functional programming — we’re basically transforming a set of numbers to their respective module default export function outputs! As long our modules keep the same signature we can play around like this.

This opens a lot of new possibilities, for example as we said before — loading UI components depending on the platform (or browser), taking different actions after the user chooses or inputs some value into your app, etc.

Lazy Loading

Most of the time you will want to defer the module loading so that you could use the module only when it is necessary. There are some techniques to do so, let’s check them. :eyes:

Event-based loading

Let’s create a file called lazy-one.js that exports a string:

JfAvInm.jpg!web

Now at the end of index.js, paste this snippet:

JfAvInm.jpg!web

We have just appended a button with a dynamic import on the “click” event.

Loading the page and looking at the network tab shows us that it loads the page, and we can see it requesting the two chunks from before:

eE7rmyN.jpg!web

And then, after clicking the button:

qYVBrm2.jpg!web

Webpack “Magic Comments”

As Webpack adds all this behavior to dynamic imports, it would be nice if we had some control over it, right?

But we do! :tada:

Webpack provides a nice way to configure these imports through comments following a certain pattern.

Chunk name

:confused: — “Hey, but the name 2.js isn’t really helping to know what we have inside it, can we rename it?”

Yep! Let’s name it after myAwesomeLazyModule :

JfAvInm.jpg!web

This will result in:

NZreUfz.png!webrmyUfej.png!web

Preload Module

Let’s say our lazy-module is super important and we don’t want a delay on the button content change after the user clicks it.

We can choose to preload this resource as we’re pretty sure it will be used on the current page:

JfAvInm.jpg!web
6V3iymQ.png!web

This will make webpack add a `<link rel=”preload”>` at the start of the page, making this resource to be loaded (but not executed) before the `main.js`.

This is pretty useful for small resources where users have a `high latency network`, causing a big delay on the execution of small chunks not because of it’s size, but because of the HTTP protocol process (being even worse on HTTPS because of the “handshake”).

“This will give a little load time boost since it only needs one round-trip instead of two.” — Webpack Docs

Prefetch Module

:confounded: — “What!? What’s the difference between preload and prefetch ?

It looks similar, but prefetch will not load before the bundle/chunk that requests this resource, it will load after on the browser idle time .

This makes it perfect for resources that will very probably be used in future . For instance, you have the data (through analytics) that the majority of unsubscribed users who visit your “products” page will with a high probability access the “subscribe” page. Since you’re a smart developer, you set a prefetch import on “products”, making the browser fetch and cache this resource in its spare time:

JfAvInm.jpg!web

As you can see, it works similar to the preload, activating it just by adding the “magic comment” above.

And here is a tip (works for preload too): if you have multiple prefetch/preload resources, you can define their priority:

<em>/* webpackPrefetch:</em><strong><em> 42</em></strong><em> */</em>

Chunk Mode

You may be asking yourself:

I have a 100 of those modules-${id} and as I’m requesting them on the page directly, I don’t want to request 100 separate files, since most of my users will suffer because of high latency .

For that we also can control how Webpack resolves and splits the chunks for dynamic imports, using the webpackMode magic comment. From the docs :

“lazy” (default) : Generates a lazy-loadable chunk for each import()ed module.

“lazy-once” : Generates a single lazy-loadable chunk that can satisfy all calls to import(). The chunk will be fetched on the first call to import(), and subsequent calls to import() will use the same network response. Note that this only makes sense in the case of a partially dynamic statement, e.g. import(`./locales/${language}.json`), where there are multiple module paths that could potentially be requested.

“eager” : Generates no extra chunk. All modules are included in the current chunk and no additional network requests are made. A Promise is still returned but is already resolved. In contrast to a static import, the module isn’t executed until the call to import() is made.

“weak” : Tries to load the module if the module function has already been loaded in some other way (i. e. another chunk imported it or a script containing the module was loaded). A Promise is still returned but, only successfully resolves if the chunks are already on the client. If the module is not available, the Promise is rejected. A network request will never be performed. This is useful for universal rendering when required chunks are always manually served in initial requests (embedded within the page), but not in cases where app navigation will trigger an import not initially served.

Let’s try out lazy-once on our dynamic import with an expression:

JfAvInm.jpg!web

And we see the result:

uqYZJnE.jpg!webuEFbqif.jpg!web

You see, all chunks which were separate are now in the same common chunk, making it easy for the application, as it loads all modules with one single request.

If instead, we use the “ eager ”, all these modules will be included inside the one which is importing them, in our case main.js .

W rapping up, in this chapter we went deep into Webpack and used the dynamic-imports, tweaking the way the code is being split in the process (while visualizing everything with the bundle analyzer).

We’re almost done and the only thing left to us is to take advantage of what we learned in this chapter while using React and the new lazy/Suspense API. See you in the next chapter!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK