Create Vue Component based on Real Hardware/Thing #LED - part 2: reduce the bundle size

Repository

What Will I Learn?

This tutorial is the continuation of my previous tutorial to make WebComponent of a real LED. In this tutorials, we will try to reduce the bundle size of our component. Overall you will learn about:

  • How to build Vue components
  • How to utilize mathjs to do only UoM (Unit of Measurement) check
  • How to utilize partial import to reduce the bundle size
  • Analyze bundle size of various build target
  • How to identify and strip down parts of the third party library that we don't need

Requirements

Difficulty

  • Intermediate

Tutorial Contents

banner

On the previous tutorial, we learnt how to utilize mathjs to do Unit of Measurement (UoM) check and also automatic Conversion of Units (CoU). However, when we are building our component it will bundle whole mathjs package into our component which will make our component bloated. In this tutorials, we will solve that problem while also doing some analysis about the bundle size of various target build.

Bundling our component into different target

In our previous tutorial, we just import 3 specific function in mathjs which is unit, createUnit, and compile. However, the reality is we are importing all mathjs function and only select those 3 function to be available in LED.vue. Let's check the bundle size when by compile it into different target build:

Table 1. Result of executing yarn build --target lib --name led **/Led.vue
FileSizeGZipped
dist/led.umd.min.js537.81 kb156.70 kb
dist/led.umd.js1747.78 kb391.02 kb
dist/led.common.js1747.35 kb390.86 kb

In Table 1 we build Led.vue as Vue component by setting the argument --target lib. This mode will build our component into 3 file which has their use case and format. The file with extension umd.min.js and umd.js is meant to be served via CDN. The min word in umd.min.js denotes that this file is the minified version of umd.js to reduce the download size which is suitable for production environment. To use led.umd.min.js or led.umd.min.js we need to add it in <script> tag and register our component to Vue instance. For example:

demo
Fig.1 - code copied from dist/demo.html which was outputed from executing yarn build --target lib --name led **/Led.vue

Here we see that after loading led.umd.js via <script> will make the object led available at the global scope. To use led component, we need to register it to Vue instance and here we see that led component register as demo then we can declare demo inside an element that Vue instance has been bind (<div id="app">). The reactivity system only available inside <div id="app">. If you declare <demo></demo> outside <div id="app">, the component will not be rendered.
The extension common.js is to denote that the bundled file are in CommonJS format which are meant to be used in conjunction with bundler like webpack, parcel, or rollup.

Table 2. Result of yarn build --target wc --name hw-led **/Led.vue
FileSizeGZipped
dist/hw-led.min.js541.32 kb158.12 kb
dist/hw-led.js1759.46 kb395.33 kb

In Table 2 we build Led.vue component as WebComponent by setting the argument --target wc (wc stands for WebComponent). Because all standard HTML element consists of only one word, to avoid naming collision with this, then we must name our component in 2 or more word. In this case, we are naming our WebComponent <hw-led>. In this build target, our component will be built into 2 files, minified and not minified. The usage of WebComponent file has some similar characteristics to umd.js file which you served it via CDN. The min word in hw-led.min.js denotes that this file is the minified version of our WebComponent to reduce the download size which is suitable for production environment. To use hw-led.min.js or hw-led.js we need to add it in <script> tag then it's ready to use without registering our component to Vue instance. For example (copied from dist/demo.html):

demo
Fig.2 - code copied from dist/demo.html which was outputed from executing yarn build --target wc --name hw-led **/Led.vue

Here we see that we don't need to register our component into Vue instance, instead we use it directly. One of the caveats of building Vue component into WebComponent is we still need vue runtime (https://unpkg.com/vue) because the component still needs some function that is only available in Vue object instance.

Use partial import

From Table 1 and Table 2, the bundle size of our component is not acceptable which are about ~1.75 MB. The problem that our component become bloated because as I mentioned before, we are accidentally importing all mathjs functionality. To trim down our component, we need to load functionalities that we only meant to use. There is many name for this technique like A-La-Carte, transform-import, partial import (we will stick with this), and custom bundling. Each third party library has a different way to do partial-import. In mathjs you need to use built-in function math.import for cherry-pick feature/functionality. Based on mathjs documentation we can write our code like:

Create file src/math.ts with code

import core from 'mathjs/core'

const math = core.create()

math.import(require('mathjs/lib/type/unit'))
math.import(require('mathjs/lib/expression/function')) // compile, eval
math.import(require('mathjs/lib/function/arithmetic')) // basic arithmetic like divide, exponent, etc

// export which functions to use
export const unit = math.unit
export const createUnit = math.createUnit
export const compile = math.compile

As you can see, we need to use math.import to load the needed data types and functions. It’s important to load the data types first and after that load functions and constants. Then, we can export some functions to use for convenience. The functions are dynamically built, depending on the availability of data types. To see which data types and categories are available, explore each index.js files in the folder ./lib. The function unit, createUnit, and compile now can be used (see the diff code) and if we try to build it as a WebComponent, we will get the result shown in Table 3.

Table 3. Comparison bundle size of our WebComponent
optionssizeGzippedminified sizeminified GZipped
mathjs full1759.46 kb395.33 kb541.32 kb158.12 kb
mathjs custom bundling
(compile + unit + createUnit)
941.57 kb203.99 kb318.84 kb90.10 kb

In Table 3, we see that we are able to reduce the bundle size about ~817.89 KB (before − after) which is 46.48% size reduction. This meant that our attempt to reduce the bundle size is a huge success. However, we can optimize it further by not using compile(expression).eval() which has a handy feature to do auto Conversion of Unit (CoU).

Remove compile function and handle CoU manually

Overall we have succeeded to reduce the bundle size of our WebComponent. However, we can make it smaller by replacing calculate function which uses math.compile with manually written function (vanilla way). In src/math.ts, remove mathjs subpackage that related to function compile.

Code Change 1 - Left: before removing auto CoU functionality. Right: after removing auto CoU functionalitydiff code

Function math.compile are declared in on mathjs sub-package mathjs/lib/expression/function which we unimport that package. However, sub-package mathjs/lib/function/arithmetic are mainly for doing arithmetic jobs and since we are going to do the arithmetic job in plain Javascript then we remove that sub-package as well. After we remove math.compile dependencies, we can begin to write our calculation in Javascript.
After we unimport function compile and remove global function calculate, we can begin to replace the computed properties of brigtness and height.

Code Change 2 - replacing math.calculate with manual calculation
calculate(`1.3 * ${this.size}*${this.scale}`).toNumber('pixel')

from that into

1.3 * unit(this.size).toNumber('mm') * unit(this.scale).toNumber('px/mm')

this.size = real size of LED bulb in millimeter (mm)
this.scale = scale map from real world scale into simulation scale in pixel per millimeter (px/mm)

----------------- and also -----------------

calculate(`${this.inputVoltage}*${this.inputCurrent} / ${this.maxPower}`)

from that into

unit(this.inputVoltage).toNumber('volt') * unit(this.inputCurrent).toNumber('ampere') / unit(this.maxPower).toNumber('watt')

this.inputVoltage = input voltage of LED in Volt (V)
this.inputCurrent = input voltage of LED in milliAmpere (mA)
this.maxPower = maximum power consumption of LED in milliWatt (mW)

Here we replace our previous equation (see previous tutorial) that previously formated as string (because calculate(expr: string) accept string as parameter) with manually written Javascript number operation (*, /, +, -). Because all of our props are string type, we need to convert it into number type by using function unit.toNumber(unitName) which it also can do unit conversion. For example unit(this.maxPower).toNumber('watt') with this.maxPower='43mW', calling unit(this.maxPower) will convert this.maxPower into Unit object then calling .toNumber('watt') will convert it into number type which the value will be 0.043 because 1 mW = 0.001 W.
After we have to change the code and verify it was correct, we can build our component (using the previous command) and get the result depicted in Table 4.

Table 4. Comparison bundle size of our WebComponent
optionssizeGzippedminified sizeminified GZipped
mathjs full1759.46 kb395.33 kb541.32 kb158.12 kb
mathjs custom bundling
(compile + unit + createUnit)
941.57 kb203.99 kb318.84 kb90.10 kb
mathjs custom bundling
(unit + createUnit)
485.09 kb106.47 kb145.26 kb42.10 kb

As expected shown in Table 4, we see that we are able to reduce the bundle size about ~456.48 KB (with compile − no compile) which is 48.48% size reduction. This meant that our second attempt to reduce the bundle size is a huge success. However, for long running or big codebase project, removing unit measurement check in the computational layer which is vue computed props brightness and height is not recommended.

Conclusion

In summary, we are able to reduce the bundle size of our LED WebComponent from ~1,76 mb to ~485 kb which is 72% reduction by utilizing partial import and remove auto conversion unit feature. In the next tutorials, we will implement the computation layer in Rust (see my another tutorial for more insight about mixing Vue with Rust) and use dimensioned package to have type safety in unit measurement level.

Curriculum

References

Proof of Work Done

https://github.com/DrSensor/vue-hardware/commits/led

H2
H3
H4
3 columns
2 columns
1 column
12 Comments