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
- Basic understanding of HTML and Typescript
- Basic understanding of Vuejs and Class-Style Vue Components
- Install vue-cli and yarn
- Install some code-editor/IDE (strongly suggested to use VSCode + Vetur)
Difficulty
- Intermediate
Tutorial Contents
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:
yarn build --target lib --name led **/Led.vue
File | Size | GZipped |
---|---|---|
dist/led.umd.min.js | 537.81 kb | 156.70 kb |
dist/led.umd.js | 1747.78 kb | 391.02 kb |
dist/led.common.js | 1747.35 kb | 390.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:
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.
yarn build --target wc --name hw-led **/Led.vue
File | Size | GZipped |
---|---|---|
dist/hw-led.min.js | 541.32 kb | 158.12 kb |
dist/hw-led.js | 1759.46 kb | 395.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
):
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.
options | size | Gzipped | minified size | minified GZipped |
---|---|---|---|---|
mathjs full | 1759.46 kb | 395.33 kb | 541.32 kb | 158.12 kb |
mathjs custom bundling ( compile + unit + createUnit ) | 941.57 kb | 203.99 kb | 318.84 kb | 90.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
.
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
.
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.
options | size | Gzipped | minified size | minified GZipped |
---|---|---|---|---|
mathjs full | 1759.46 kb | 395.33 kb | 541.32 kb | 158.12 kb |
mathjs custom bundling ( compile + unit + createUnit ) | 941.57 kb | 203.99 kb | 318.84 kb | 90.10 kb |
mathjs custom bundling ( unit + createUnit ) | 485.09 kb | 106.47 kb | 145.26 kb | 42.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
- Dimensional Analysis in Programming Languages (highly recommend reading this to better understand UoM benefit)
- F# Unit of Measure
- What is a Content Delivery Network (CDN)?
- mathjs custom bundling
- vue-cli build target