Vue.js: Bundle size and Performance Optimizations ๐ŸŽ๏ธ

Introduction

I’ve been working on a vue.js project using webpack and a great set of libraries. Since it’s a big project, unfortunately, I used to have trouble with bundle size and performance. I searched the internet a lot hoping to find a way to optimize webpack bundle size. Hereโ€™s my approach on how to optimize the performance and decrease bundle size in a couple of steps.

Letโ€™s start

First, we have to know which component or library is having a larger size in our code.

I analyze our bundle dependencies via webpack-bundle-analyzer. This library analyzes the webpack statistics and gives a great visualization with an interactive zoomable tree map.

We need to install it using NPM or YARN:

npm install --save-dev webpack-bundle-analyzer
yarn add webpack-bundle-analyzer -D

Then we need to include this into our webpack config file

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

When we run the app, this visualization will show up:

This helps you to understand which libraries or components are too big. For example, now we know we have trouble with moment.js. Our next steps are:

Reduce the size of moment

Moment.js is a great library to work with date objects. But if you check the size of the library in the bundle, moment.js is almost 700kb.

We can reduce the size of moment.js from 700kb to 207Kb with ContextReplacementPlugin. It specifies including only the locale files that you need for your project in the webpack config file.

module.exports = {
	plugins: [
	  // just load `moment/locale/en.js` and `moment/locale/es.js`
	  new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en|es/),
	]
}

This way we donโ€™t need to load all the locale files in our code.

Exclude comments in bundles

You can use webpack to exclude comments in bundles with information about the contained modules. This option is pathinfo, which by default is true in development and false in production mode, also you can use verbose to show more information like exports, runtime requirements, and bailout. but if you are not interested or are not helpful for you, omit these comments putting this option in false.

module.exports = {
  //...
  output: {
    publicPath: '',
    pathinfo: false,
  },
};

With this option in false, we are reducing a lot the size of our bundle.

Delete unused libraries, components and styles

If your project has grown a lot in a short time, you probably have libraries, components, and styles that are no longer used. Just delete the things that are not needed anymore, sometimes we don’t have enough time to make a good cleanup in our code, but it is really worth taking the time to do it.

Code splitting

Sometimes we don’t notice this, but Bundling all our JavaScript files into a single file does come with a potential downside. Each time you change a minor detail in our code, you must bust the cache for all users.
That means all of your vendor libraries must be re-downloaded and cached. One solution is to isolate, or extract, your vendor libraries into their own file.

We can do this, using SplitChunksPlugin like this:

  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all"
        }
      }
    }
  },

Or if you are using laravel.mix like us. You can do this:

mix.extract();

With these changes, we pay a small HTTP request penalty, in exchange for improved long-term caching of vendor code that very rarely will change. ๐Ÿ˜

At this point with all these changes in our project, we reduce our bundle size by 52%, but this is not all, we can continue improving the performance using lazyloading for our Vue routes.

Lazy Loading Components

In our SPAs (Single Page Applications) project we end up having hundreds of components that can be divided into several JavaScript bundle files. I will show you one way to do this division to load each file asynchronously, only when the component is requested from a route change. This asynchronous behavior is called Lazy loading and allows for a smaller initial bundle size.

For example, if our src/router.js file has the following code:

import Vue from "vue";
import VueRouter from "vue-router";
import About from "../views/About";
import Home from "../views/Home";
import Videos from "../views/Videos";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    component: About,
  },
  {
    path: "/videos",
    name: "Videos",
    component: Videos,
  },
];

const router = new VueRouter({
  mode: "history",
  routes,
});

export default router;

To setup lazy loading here, we change the src/router.js file to the following instead:

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

function lazyView(view) {
  return () => import(/* webpackChunkName: "lazy-view-[request]" */ `../views/${view}.vue`)
}

const routes = [
  {
    path: "/",
    name: "Home",
    component: lazyView('Home'),
  },
  {
    path: "/about",
    name: "About",
    component: lazyView('About'),
  },
  {
    path: "/videos",
    name: "Videos",
    component: lazyView('Videos'),
  },
];

const router = new VueRouter({
  mode: "history",
  routes,
});

export default router;


But what have we changed? ๐Ÿค”

  • We have removed the static imports for the home, about and videos view components.
  • We have created a new function lazyView, which uses the import function to dynamically import a Vue component.
  • In the import function we have used /* webpackChunkName: “lazy-view-[request]” */ to add a new name on each file that will be imported dynamically.
  • The route configuration uses the lazyView method, passing the name of the component.

Now we need to compile our project and here we have the result of the before and after:

Also, we can include a hash on each chunk created to avoid collisions between our different files adding this config in the output on webpack

module.exports = {
  //...
  output: {
    publicPath: '',
    chunkFilename: '[name].[chunkhash].js',
    pathinfo: false,
  },
};

in our compiled code we will see:

Final Result ๐Ÿ”ฅ

With all our previous changes and improvements by 52% plus Lazy loading for all our Vue components,  I was able to drastically improve the bundle size and performance of our application by 96% in total ๐ŸŽ‰๐ŸŽ‰.

Recommendations โœจ

Webpack provides a web page listing the actions to take to hunt what takes time. This can be helpful to improve the performance of your application as well. I strongly advise to have a look at it โžก๏ธ build-perfs .

  • Follow webpack performances recommendations
  • Split your code and use lazy loading whenever you can. This can make a big difference in the performance of your application.
  • Be very careful with the libraries that are added to your project. Many contain more than you need and do not add value.

I hope you found this post useful.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.