Code Splitting in Vanilla JS applications

Code Splitting in Vanilla JS applications

??Why do we need code splitting?

As you might know that Webpack is mostly used in order to take all our modules and put them together into a single JavaScript bundle. As a result, this bundle contains all our JavaScript code. This approach is widely used when there is a need to create a single-page-application (SPA). But it's not the only usage of Webpack.

In some projects, we need to have more than one HTML page, especially if these HTML pages are rendered on the server side, then most probably your server will send different pages to the browser depending on the URL that we write in the navigation bar.

In this article, we are going to learn how to split our JavaScript code into multiple bundles and how to create multiple HTML files for different pages of our website.

?Project Setup: I've already created a starter project to help you to code along this article and focus on concepts, not wasting time on writing JS components. In order to start with, you need to clone this GitHub Repo.

It is a very basic setup with minimal webpack configuration. This application mainly is going to have two routes -

  1. hello-world-page route: This is where the JS file hellow-world-page.js is going to be served, which in turn renders the components heading & hello-world-button.
  2. apple-page route: In this route, the JS file apple-page.js is going to be served, which in turn renders the components heading & apple-image.

At this moment, webpack only generates one big JS bundle that holds JS codes for both of those two routes, which is what we don't want. Look inside the dist folder (Webpack's output directory by default) in your IDE.

No alt text provided for this image

Instead, we want our webpack to generate two different JS bundles which're then going to be served separately for those two different routes. As a result, the JS bundle for individual routes becomes smaller and hence the users enjoy a much faster page-load experience.

Let's see all of above said things in action -

??Splitting out Multiple JS and CSS Bundles

As said above, we have two different JavaScript files

No alt text provided for this image

that basically represent two different entry points that should be included inside two different HTML pages. Let's tell webpack to create two separate JavaScript bundles out of those two files.

In order to do that, you need to go to the webpack.production.config.js file. And here I need to change how the entry point is defined.

No alt text provided for this image

We are going to put here two entry points instead of one. Basically, what we need to do is to replace the string with a JavaScript object, and this object is going to have two properties, each of the properties will point to one of the pages we are going to have.

First, I'll define the "hello-world" Page. It will point to the hellow-world-page.js file. The second property will be called "apple", and it will point to give the apple-page.js file.

No alt text provided for this image

Since we have two entry points instead of one, we need to specify somehow that their names should be different. In general, the filename option determines the name of each output bundle. This bundle is written to the folder specified by the path property of the output object.

No alt text provided for this image

From this current configuration, both our bundles will be named "bundle." followed by an md5 hash code and then ".js", which would be different for each of those pages, but I think it would be much nicer if we could name them differently.

Actually, we can tell webpack to use file names that we specified in our "entry" points. We just need to replace the static name "bundle" with a dynamic substitute "name" in square brackets like this - [name], whereby it will take the keys of the object assigned to "entry" as the value for "name" and put it here.

No alt text provided for this image

By the way, we can do the same trick for CSS files. But this filename option of the output object will not affect the CSS files because those files are generated by a dedicated plugin called MiniCssExtractPlugin.

No alt text provided for this image

However, we can change the names of the generated CSS files in the same way. We just need to go to the MiniCssExtractPlugin configuration and change the filename string as we just did for JS bundles.

No alt text provided for this image

Let's run that webpack in the terminal by running this command:

npm run build        

?Once the build process finishes, check what we get after these changes in our dist folder.

No alt text provided for this image

We see that we now have 2 JavaScript files & 2 CSS files inside our dist folder and they match the filename they're actually generated from. Hence, I can say that you have learned how to split your code into multiple bundles.

??How To Generate Multiple HTML Files

In the previous section, we learned how to split our JavaScript code into multiple bundles, but you might notice that all our bundles are included inside one HTML file index.html.

No alt text provided for this image

let's open the file and see inside it.

No alt text provided for this image

By default, Webpack minifies the generated HTML file in production mode. That's why there is only one long line in this file. However, it's hard to see the contents of each HTML as long as it is minified. Let's turn off the minification of HTML files for now.

In order to achieve this, we can add an extra option to the HtmlWebpackPlugin configuration in the webpack configuration file. This option is called Minify. By default, this option equals to true in production mode. However, it's easy to change if you need to.

No alt text provided for this image

Let's run that webpack one more time

npm run build        

and have a look at the generated HTML again.

No alt text provided for this image

Now, you can easily see that all our JS & CSS is included inside one HTML file. However, we need to generate 2 different HTML pages if we need to have a proper multiple-page application. One page would contain JS & CSS only for the hello-world-page and another page would contain JS & CSS for the apple-page.

In this section, we will learn how we can generate multiple HTML files and include different bundles inside different HTML files. Let's go to our webpack.production.config.js file and check the configuration for HtmlWebpackPlugin.

if you want to generate two HTML files, we need to include this plugin twice in the plugin array. So I will add a second instance of HtmlWebpackPlugin right below the first one.

No alt text provided for this image

But now we need to differentiate somehow between two instances of HtmlWebpackPlugin. The best solution here is to provide a different "filename" property because this property decides the name of the generated HTML file.

Before doing that, we need to rename the handlebars template file index.hbs to a more generic name page-template.hbs.

No alt text provided for this image

Now let's go back to the first HtmlWebpackPlugin instance. It should have a filename hello-world.html. The title property would be "Hello World!". And description would be 'Hello World Page'. As for the template, update the reference to the renamed file page-template.hbs.

No alt text provided for this image

So now we need to adjust the second instance of HtmlWebpackPlugin, we also need to provide a filename here and it will be given 'apple.html'. I will also change the title to "Apple!". And I will change the description as well. I also need to update the template here.

No alt text provided for this image

But now how does Webpack going to know which JavaScript bundles to be included in which instance of HtmlWebpackPlugin? In order to solve this problem, HtmlWebpackPlugin has a special option called chunks, using this option, we can specify which bundle we need to include where.

Let's add a chunks option to both our instances. The chunks property is an array, so we can specify multiple chunks for each HTML file, but in our case, we need only one chunk. In the first instance of the HtmlWebpackPlugin, we would use 'hello-world'. And in the second instance, we would use 'apple'.

No alt text provided for this image

Actually, the chunks names are pointing to the keys in the entry point object.

No alt text provided for this image

Now run the webpack from the terminal and check how many HTML files we will get afterward.

npm run build        

Let's go to the dist folder and check what we have there if you have a look at the dist folder. You'll notice that now we have 2 HTML files here.

No alt text provided for this image

To make sure that the chunks property actually works, let's have a look inside the hello-world.html file.

No alt text provided for this image

Here we can see here that it has only references to the JS & CSS files that belong to the hello-world page, not all JS & CSS files.

Similarly, the apple.html file only contains the JS & CSS files that belong to the apple page, not all JS & CSS files.

No alt text provided for this image

This is how you can generate multiple HTML files and include different bundles inside different HTML files.

??Extracting Common Dependencies While Code Splitting

In this section, you are going to learn how to handle common dependencies between multiple bundles. Earlier, you have seen how you can split your JavaScript code between multiple bundles and how to generate multiple HTML pages. But sometimes all your website pages depend on some common library or framework, and you probably don't want that library to be included in each and every generated bundle. Let's have a look at the following example.

Imagine that you want to show the name of the current page in the headline. In order to do this, we need to modify our heading component. I will go to the file where we defined the heading component.

No alt text provided for this image

And here I'm going to modify the contents of the h1 element.

No alt text provided for this image

I will add here the pageName variable. Since we are using a pageName variable here, we need to add it as an argument to the render function.

No alt text provided for this image

Now since we added this argument to the render function, we need to change all the invocations of this function. There are two places where we use this component.

  1. hellow-world-page.js
  2. apple-page.js

First I will go to the hellow-world-page.js file. I need to pass one argument here, and this is going to be the name of the page, Which is "hello world" in this case.

For demonstration purposes, I'm going to a library called lodash. It allows us to make the first letter of this string uppercase. Please note that this is not the best solution for making uppercase strings, but it suits our example. Ideally, in order to use the lodash library, we need to install it.

npm install lodash --save        

Now we can import this library and call the upperFirst on a string.

No alt text provided for this image

Similarly, we need to make the same adjustments to the apple-page.js file.

No alt text provided for this image

So now we need to run Webpack.

npm run build        

Now, if you have a look at the files web generated for us,

No alt text provided for this image

We can notice that our JavaScript bundles became pretty big -

  • apple.js has a size of 72 KB
  • hello-world.js has a size of 71 KB

Well, 70 kilobytes is too much for 10 or 15 lines of code that we have in those modules. So what happened?

Remember, we included lodash at the beginning of the file. It seems webpack has included lodash in both JavaScript bundles and the lodash is quite huge. Now every time we change something in JS then a new 70-kilobyte bundle will be generated and our users will need to download it again and again. Sounds not very nice, right?

Webpack has a built-in feature that can extract lodash and any other common dependency into its own bundle. Let's see how we can do it. We need to go to the webpack.production.config.js file and add a new option here right below the mode option. This new option is called optimization.

No alt text provided for this image

This is a JavaScript object, and inside this object, we will specify another option which is called splitChunks. And inside we need to specify yet another option, which is called chunks. This option indicates which chunks will be selected for optimization. In this case, we want to tell Webpack to optimize all types of chunks.

So after we added this new configuration option, let's run Webpack again and see what happens.

No alt text provided for this image

If you have a look into Webpack output, you will see that -

  • apple.js has a size of 3.95 KB
  • hello-world.js has a size of 3.61 KB.

, which is pretty nice. And we have a short JavaScript bundle that has a name starting from some numbers that consume around 69 kilobytes. This bundle includes lodash and this bundle will be cached separately. And now our users don't need to download it. When we change something in our code, Webpack will include this bundle automatically into every HTML page that depends on it. In our case, both our pages need this bundle, so Webpack will include it in both HTML files. We can easily check this?-

If you open the generated hello-world.html file, you'll notice that bundle reference.

No alt text provided for this image

Similarly, If you open the generated apple.html file, you'll notice that bundle reference.

No alt text provided for this image

So in this case, Webpack included this bundle in both HTML files. But does this mean that Webpack will always include this bundle in all our HTML files, even if those files don't need lodash? Let's check if this is true.

In order to check this, I will go to the apple-page.js file and remove all the usages of lodash. Let's run that webpack one more time and see if lodash is still included in the newly generated apple.html file.

No alt text provided for this image

As you can see, there are no extra JavaScript bundles here anymore. And if I go to the hello-world.html file, you can see that this extra bundle is still included here.

No alt text provided for this image

This means that webpack only includes this bundle in those files that really need it. Let's put that lodash usage back to the apple-page.js page. Let's run webpack one more time and then open those two HTML files in the browser one by one.

No alt text provided for this image
No alt text provided for this image

As we can see, both our pages work perfectly. And if you have a look into developer tools in the network tab, we will see that in addition to the key JavaScript bundle and CSS bundle, the results of this additional bundle include lodash.

No alt text provided for this image

In case of any code changes that may happen in the future, this bundle will be cached separately, and if the lodash library wasn't changed between your deployments, your users will not download this bundle every time you deploy your code.

This is how you can handle common dependencies between multiple pages. We can also use this mechanism if you want to extract some external dependencies, like react, for example, into a separate bundle even if you don't have multiple pages. This may be useful for browser caching as well.

??Setting Custom Options for Code Splitting

In the previous section, we learned how to extract common dependencies into a separate bundle, we used lodash in our example and Wabpack extracted it into a separate bundle. Let's try to do absolutely the same with react.

But first, let's remove lodash and instead import react into the hellow-world-page.js file. And we also need to remove the upperFirst function because we do not import lodash anymore.

No alt text provided for this image

Now I need to do the same code adjustments inside the apple-page.js file.

No alt text provided for this image

Now we need to install react,

npm install react --save        

Now, let's run our build in the terminal

npm run build        

and see what happens.

No alt text provided for this image

If you have a look at the bundles that were generated, you can see that there is no additional JavaScript bundle here other than hello-world.js & apple.js. But previously we had an additional bundle, including a lodash dependency. So why Wabpack didn't create a similar bundle for react?

You see, by default, webpack extracts common dependencies only when they exceed 30 KB before minification. The lodash library is more than 30 KB and that's why it's extracted into a separate bundle by webpack, whereas react is way less than 30 KB. So what do we do if you want to extract react into a separate bundle?

There is a way to change that magic number - 30 KB. We can specify custom options for splitting chunks. Let's make this magic number a bit smaller, let's say 3 KB. I will add here a new option called minSize inside the property optimization we just added in the last section.

No alt text provided for this image

Now let's run our build again

npm run build        

and see if anything changes.

No alt text provided for this image

You can see that now there is a separate bundle here, this bundle is 1.39 KB in size and it contains react. Therefore, this is how you can set custom criteria for extracting common dependencies.

?Setting up the Development Build (webpack dev server)

In the previous section, we learned how to set up production configurations for multiple-page applications. As a result, after every build, we are getting multiple HTML files for multiple web pages.

You may or may not know that using a webpack dev server is the most convenient way to get fast feedback about our code changes without running the build process again and again. In this section, you will learn how to set up development configurations for multiple-page applications.

Let's go to the webpack.dev.config.js file and change something there. First, we need to put two entry points instead of one.

No alt text provided for this image

So I will replace this string with a JavaScript object, which you may already know. We are going to have two properties here 'hello-world', which will point to the hellow-world-page.js file, & 'apple' which will point to the apple-page.js file. In case, if you don't know anything about production build vs development build, please check out this article, where I discussed these things in great detail.

No alt text provided for this image

Since we are having two entry points now, we need to adjust our output configuration. I'm going to use the [name] placeholder here. We don't have to use md5 contenthash in this case because this is the development configuration, and we don't need to care about caching strategy at this point. We only need to use contenthash in a production configuration, which we are already doing.

No alt text provided for this image

We also need to add a second HtmlWebpackPlugin to the list of plugins array because we have two HTML pages now.

No alt text provided for this image

It will look similar to the production configuration that we already have. First, I need to adjust our current configuration and change the filename option to the apple.html file, the chunks option to 'apple', the title option to "Apple!", as well as change the description to 'Apple Page'.

No alt text provided for this image

Basically, that's all we need to do. We don't need to split common dependencies into a separate bundle because this is the development configuration, and all we care about is the performance of the build process itself, not the application performance. We should care about the application performance when we are dealing with production configuration.

Let's run our development build in the terminal

npm run dev        

and have a look at what we have in the terminal.

No alt text provided for this image

Webpack tells us that the project is running at https://localhost:9000, so copy this URL and open it in the browser.

No alt text provided for this image

As we can see, there is nothing on our main URL now. But now we can open different pages using different URLs.

For example: if you want to go to the Hello World page, put /hello-world.html just after https://localhost:9000.

No alt text provided for this image

Actually, it allows the browser to access the generated hello-world.html file which lives inside the dist folder. Similarly, if you want to go to the apple page, put /apple.html just after https://localhost:9000.

No alt text provided for this image

Finally, I can say that you learned how to set up development configurations for multiple-page applications. So at this point, you have a fully fletched webpack configuration for both production and development environments, specifically targeting multiple page applications.

要查看或添加评论,请登录

??Prasenjit Sutradhar的更多文章

社区洞察

其他会员也浏览了