How many of your users access every single page of your app, or use every single feature of your site?
It’s probably only you and your integration tests.
Most of your users have a specific goal. For example, one user might want to browse your product catalog only to see what you have in your webshop. He is not ready to buy yet and will not see your checkout. And he will not click the “show reviews” tab on the product pages.
With one bundle for the whole app, this user has to download code that he will never run.
If you want to make fast sites, you must send as little JavaScript as possible to the browsers. Not only does it take time to download the JavaScript bundle, but the browser also has to extract the code and parse it — which also takes lots of time.
Slow sites make users leave. And it’s also bad for SEO — Google rewards quick sites.
What you should be doing is to send only the JavaScript the user needs for viewing and interacting with the pages he visits — nothing more or less. That way, the site will load much quicker and your users get a much better experience.
Well, guess what — it’s possible with webpack and dynamic imports.
When you use dynamic imports, you serve just the bare minimum of JavaScript on page load and serve the rest dynamically when (and if) the user needs it.
Dynamic loading sounds nice, but how do you configure your webpack project to support it?
There is actually no webpack configuration involved at all because dynamic imports work out-of-the-box in webpack 2 and later (if you are still on webpack 1, now is a good time to upgrade). You don’t have to do any configuration at all in your webpack.config.js
to get it to work.
What you do need to do however is to configure Babel. You also need to write your code that handles imports with a slightly new syntax. We will come back to that syntax later. First, let’s look into Babel.
Dynamic import is a TC39 proposal in stage 3. Stage 4 is the “finished” stage, so it’s getting really close to becoming a standard. Because it’s still a proposal, browsers do not support it yet. So, to get it to work, you need to configure Babel to transpile your dynamic imports.
To do that, you first need to install the Babel plugin plugin-syntax-dynamic-import
:
npm install --save-dev @babel/plugin-syntax-dynamic-import
And then use it in the .babelrc
file:
{"plugins": ["@babel/plugin-syntax-dynamic-import"]}
Now that you have configured Babel, you can use dynamic imports in your project. Before I describe how to do that, let’s see how an import looks like that is not a dynamic import.
import Text from "./Text"
This line of code should be very familiar to you. When you import this way, you can access the module inside ./Text.js
with the new variable Text
. You can access any function in the module right after the import.
With dynamic imports, the statement looks slightly different.
import("./Text").then(Text => {// you can access Text inside here.})
We still use the import keyword, but we don’t assign it to a variable with the from keyword as we did previously. Instead, import("./Text")
returns a promise. And inside that promise, you have access to the module.
What happens under-the-hood is that when you call import("Text")
it makes an Ajax request to fetch only the part of your bundle that contains the bundle Text. The app doesn’t load this bundle until it sees this statement.
This means that, if you put this code in an onClick
handler, the code for the module will not be fetched until the user presses that button.
Let’s look at how we can use this in an example app. We will use React as an example, but the same principles can be applied to any framework or no framework at all. First, we create the Text
component in Text.js
:
import React from "react";export default () => <div>This text is loaded dynamically</div>
Next, we will use it in index.js
. Instead of importing it at the top of the file like we usually do, we will import it dynamically inside the root component:
class App extends React.Component {constructor(props) {super(props);this.state = {Text: null}this.loadComponent = this.loadComponent.bind(this);}loadComponent() {import("./Text").then(Text => this.setState({Text: Text.default}));}render() {let { Text } = this.state;return (<div><button onClick={this.loadComponent}>Load component</button>{Text ? <Text/> : null}</div>)}}
When the user clicks the button, the Text
component will be dynamically imported and put in component state. App
renders the Text
component in the render
function.
If you look at the network tab of your browser, you can see that the chunk that includes the Text
component is loaded only when you press the button.
There is less code to fetch on page load, which means a faster load speed!
Now, try this out on your own project. See if you can use dynamic imports to make the size of your entry point bundle go down.
Webpack is a seriously awesome tool that gives the power to you as a dev. It’s great because you have the power to create the app your users need. But that also means you can’t blame someone else if your site starts to get slow.
There are so many things you can do to reduce the bundle size it gets overwhelming. There are millions of optimizations out there. Which one applies to you? Which one gives you the best bang for the buck?
I created a tool that gives you a custom report on what optimizations improvements you should do for your webpack project. This gives you some ideas on where to start optimizing so you don’t have to spend endless days googling and RTFM.
Be sure to try out the webpack optimization tool for your project.
Originally published at blog.jscrambler.com by Jakob Lind.