Improving The Performance Of Vue Sites With Tree Shaking

OpenReplay Tech Blog - Aug 17 - - Dev Community

by Esther Christopher

Performance optimization in web development involves implementing techniques and best practices to enhance websites' speed and user experience. This includes monitoring the site’s performance and identifying ways to make it function better. Some of these techniques for improving performance include caching, lazy loading, image optimizations, minimizing HTTP requests, and tree shaking; the latter is discussed in this blog post.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.

OpenReplay

Happy debugging! Try using OpenReplay today.


Optimizing websites results in faster loading times, increased user engagement, better SEO rankings, and an overall enhanced user experience.

Tree Shaking is an optimization technique that developers can rely on to enhance their applications' performance. It eliminates unused code and reduces bundle sizes, resulting in faster load times. By leaving only the necessary code, the application is more efficient and responsive.

Understanding Tree Shaking

Tree shaking involves analyzing your JavaScript files to detect and remove unused or unnecessary code so that your application can function.

It is a static analysis technique used by JavaScript bundlers like Webpack and Rollup to eliminate dead code, making the file smaller. It works with ES2015 (ES6) modules using import and export statements. The bundler in ES modules checks which imports are used and drops the unused ones, optimizing the final file size.

A good way to think of tree shaking is by an illustration from Webpack documentation.

“You can imagine your application as a tree. The source code and libraries you actually use represent the green, living leaves of the tree. Dead code represents the brown, dead leaves of the tree that are consumed by autumn. In order to get rid of the dead leaves, you have to shake the tree, causing them to fall.”

This results in the final output being smaller and faster.

Tree shaking offers several advantages that significantly enhance the performance of web applications. Let’s explore the key benefits of tree shaking for web applications.

  • Reduces Bundle Size: Tree shaking removes the unused code from JavaScript, leading to smaller file sizes. The advantages of smaller bundle sizes are faster user downloads and improved initial load times for web applications.

  • Improves Load Times: Because of less code, web pages can download and execute more quickly, resulting in improved initial load times. This is particularly beneficial for users on slower networks.

  • Improved Runtime Performance: Smaller bundles mean the browser has less JavaScript to process, resulting in faster execution and improved performance.

  • Enhanced User Experience: Quick loading times contribute to a smoother and more responsive user experience, leading to user satisfaction.

  • Reduced Bandwidth Usage: Tree shaking reduces the amount of code sent over the network, lowering bandwidth usage, which is particularly beneficial for mobile users or those with limited data plans.

Other benefits of Tree Shaking include a cleaner codebase, decreased parsing times, and lower memory consumption.

Introduction to ES6 Modules

ES6 modules were introduced in ECMAScript 2015(ES6). Modules are JavaScript code that are contained in files, which include variables, functions, and objects. Introducing modules in JavaScript makes the code easier to maintain and reusable across different parts of a project. Modules rely on two keywords, export and import, which enable code to be shared between files. An explanation of these keywords is provided below.

Exports

They are used to export functions, variables, or objects from a module so they can be available for use in another module.

// math.js
export const add = (a, b) => a + b;
Enter fullscreen mode Exit fullscreen mode

Imports

They are used to bring functions, variables, or objects from other modules into the current module to be used.

// main.js
import { add } from './math.js';
console.log(add(2, 3)); // Outputs: 5
Enter fullscreen mode Exit fullscreen mode

In main.js, the add function is imported and used to add 2 and 3, resulting in the output of 5.

Why ES6 Modules Enable Tree Shaking

It is the static structure of export and import statements that ES6 modules rely on to enable tree shaking.

Static structure means that the imports and exports are identified at compile time, not runtime. This way, the bundlers analyze the code structures and their dependencies during the build process. This is possible because the export and import statements are placed at the top of the file and cannot be conditional statements.

The functions, variables, or objects to be imported are also specified so the bundlers can easily identify the parts of code in use and those that are not.

Since the ES6 modules specify which parts of the code are in use, the bundler can safely remove dead code.

It allows the JavaScript bundlers to eliminate unused code during the build process.

Comparison with CommonJS Modules

CommonJS is a system for sharing JavaScript code that predates the adoption of ESM (ECMAScript Modules). Unlike ESM, which uses import and export statements at the top of files, CommonJS uses require and module.exports for module reuse. The require statement in CommonJS can be used conditionally or nested within functions, making it dynamic. This dynamic nature prevents static analysis tools from effectively analyzing dependencies at compile time, hindering optimizations like tree shaking.

module.exports is a single object that contains everything a module exports. This makes the code less modular and clear, unlike ES6 modules that use multiple named exports.

When a CommonJS module is require'd, execution is paused while the required module is loaded and analyzed. This is a synchronous process, meaning the code waits for the module to be fully loaded before proceeding with execution.

These limitations with CommonJS led to adopting ES modules, which support asynchronous loading, static analysis, native support with modern browsers, and better performance optimization, such as tree shaking.

Setting Up Tree Shaking with Vue CLI

Tree shaking is a crucial optimization technique that removes unused code from your JavaScript bundles, making your applications faster and more efficient. Enabling tree shaking is straightforward with Vue CLI and can significantly improve your project's performance. This guide'll walk you through the steps to set up tree shaking in your Vue CLI project.

Here, we are going to enable tree shaking in Vue CLI. Follow the steps below, and we’ll walk you through setting up tree shaking in your Vue CLI project.

Ensure you have the following to follow along:

  • Node.js and npm are installed on your computer.

  • Install the Vue CLI if you don’t already have it installed

npm install -g @vue/cli

  • A code editor, Vscode or Sublime Text

  • Basic knowledge of JavaScript and Vue.js

Enabling tree shaking with Vue CLI

Now, create a new project and name it whatever you prefer. Here, it’s named tree-shaking-demo. Open your terminal and input the following command.

vue create tree-shaking-demo
Enter fullscreen mode Exit fullscreen mode

Follow the prompt and choose the default setup. In your project folder, check the babel.config.js file. The content should look like this:

module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
};
Enter fullscreen mode Exit fullscreen mode

This will ensure Babel can support tree shaking.

To demonstrate how tree shaking works, install the library below, lodash:

npm install lodash

lodash will be used to demonstrate how tree shaking removes unused code from the final bundle.

In your folder structure, navigate to your components and create a new component, e.g., TestComponent.

Input the following code in it.

<template>
  <div></div>
</template>

<script>
import debounce from "lodash/debounce";

export default {
  mounted() {
    const func = () => console.log("debounced function");
    const debouncedFunc = debounce(func, 200);
    debouncedFunc();
  },
};
</script>

<style scoped></style>
Enter fullscreen mode Exit fullscreen mode

In this code snippet, only the debounce function is imported from the lodash library. Its debounce funcion is used to create a debounced version of func. The execution of func is delayed for 200 milliseconds.

5BC16808-7032-436E-BFC2-D4A197253644_4_5005_c

A function func is defined which logs 'debounced function' to the console. Next, import the TestComponent in your App.vue by inputting the following:

<template>
  <div id="app">
    <TestComponent />
  </div>
</template>

<script>
import TestComponent from "./components/TestComponent.vue";

export default {
  name: "App",
  components: {
    TestComponent,
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

The content of your main.js should look like this.

import { createApp } from 'vue'; import App from './App.vue';
createApp(App).mount('#app');
Enter fullscreen mode Exit fullscreen mode

The build process will then remove the unused part of the lodash library.

Build tools such as Webpack Bundle Analyzer and Rollup are used to verify that tree shaking has removed the unused code.

Webpack is the default module bundler for Vue CLI and it will be used in this tutorial. To use it, first install it as a dev dependency using the command below.

npm install --save-dev webpack-bundle-analyzer
Enter fullscreen mode Exit fullscreen mode

Next, go to your vue.config.js to add the plugin.

const { defineConfig } = require("@vue/cli-service");
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    plugins: [new BundleAnalyzerPlugin()],
  },
    optimization: {
          usedExports: true
        },
});
Enter fullscreen mode Exit fullscreen mode

This code snippet adds the BundleAnalyzerPlugin to analyze the bundle size. In the optimization, usedExports is also set to true to enable tree shaking, which allows Webpack to remove unused code from the final bundle, therefore optimizing the bundle size.

Navigate to your terminal and input the following command to see the tree shaking in action. This command will create an optimized production build.

npm run build
Enter fullscreen mode Exit fullscreen mode

Analyzing Performance Metrics

The image below shows the result after running the build command.

61942DB8-0E4A-4339-A309-7D1C021182DF_1_201_a

A2ECDFC1-2962-459D-A61D-341636CA4A8C_1_201_a

The chunk-vendors.js file usually contains third-party libraries like Lodash. Comparing the size of this file when tree shaking is enabled and disabled will show the difference in how much of Lodash is included in the final bundle.

From the 'Parsed size', you can see that the file size was reduced after tree shaking, which verifies that unused code has been removed.

The interpretation:

  • Stat size is the size before tree shaking optimization.
  • Parsed size is the size after tree shaking, and unused code has been removed but before any compression.
  • Gzipped size is the size of the file after it has been compressed.

Now compare the bundle size to when usedExports is set to false in the optimization.

In your vue.config.js, change the usedExports to false like below.

optimization: {
          usedExports: false
        }
Enter fullscreen mode Exit fullscreen mode

This means Webpack will not remove unused code, and the final bundle will not be optimized.

Run the build command again to open the bundle analyzer and inspect the result.

npm run build

0028ACC9-61EC-4D68-B1EE-90214F0D569F_1_201_a

CCB73A19-9F31-40A6-8F4A-988539C97C83_1_201_a

As visible in the 'Parsed size' here, the bundle is larger, and the entire lodash library, including all unused functions, is included. This is because tree shaking was disabled.

The size of chunk-vendors.js is smaller when tree shaking is enabled (usedExports: true) as it will only include the debounce function from lodash rather than the entire library.

Consider this example code of what happens before and after tree shaking.

Before tree shaking, the code looked like this:

import _ from "lodash";
console.log(_.join(["Hello", "world"], " "));
console.log(_.capitalize("tree shaking"));
Enter fullscreen mode Exit fullscreen mode

The entire lodash library, which contains unused code, is imported. The bundle will be larger because all lodash functions, including unused ones, are imported.

After tree shaking, the unused code is shaken off, and only the necessary functions are imported.

import { join, capitalize } from "lodash";
console.log(join(["Hello", "world"], " "));
console.log(capitalize("tree shaking"));
Enter fullscreen mode Exit fullscreen mode

The effect of tree shaking reduces the bundle size because only the join and capitalize functions are included.

Best Practices and Common Pitfalls

There are best practices that are encouraged to effectively implement tree shaking and avoid unexpected behavior. We’ll discuss some of them below. By following the guidelines, you can ensure your code is optimized and efficient.

Best practices include:

  • The use of ES6 Modules: ES6 Modules are the adopted format for sharing JavaScript code, as tree shaking relies on the import and export statements.

  • Update your dependencies: Improvements and optimizations are usually added to the newer versions of your tools and libraries, so keep them updated to take advantage of the upgrade.

  • Check bundle size: Regularly analyze your bundle size using tools like Webpack Bundle Analyzer or Roller to remove unused code.

Common pitfalls to avoid include:

  • Avoid Using CommonJS: CommonJS modules do not effectively support tree shaking because they use a single module.exports object to export all content. For this reason, bundlers cannot point out and eliminate unused parts of the code. CommonJS also uses a dynamic require statement, usually placed anywhere in the code, making it hard for static analysis to figure out which modules are being used.

  • Avoid libraries that are non-tree shakeable: Certain libraries do not support tree shaking, and using these libraries can lead to larger bundle sizes as tree shaking can’t be implemented.

Conclusion

In this blog post, we discussed performance optimization in web development and a particular optimization technique: tree shaking.

The blog post details how tree shaking works and the benefits of implementing it in your code. We also discussed ES6 Modules and Common JS Modules and then went ahead to set up tree shaking with Vue CLI, demonstrating tree shaking in action using the lodash library. Lastly, we covered tools for measuring performance, best practices, and pitfalls of tree shaking.

You are encouraged to implement tree shaking to achieve smaller bundle sizes and more optimized code.

Here are resources you could consult to learn more about tree shaking.

https://webpack.js.org/guides/tree-shaking/

https://web.dev/articles/reduce-javascript-payloads-with-tree-shaking

https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player