Proxy mode: lazy loading of third-party libraries such as ecarts in Vue

Time:2021-5-8

1. Basic knowledge

(1) Agency model

Let’s start with a simple example. As shown in the code

//Note: if you are running the program in node environment, please install node fetch
// const fetch=require('node-fetch') 
//Request data according to keyword
function request(keyword=''){    
    return new Promise((resolve,reject)=>{
        fetch(`http://example.com/data/${keyword}`)
        .then(response=> {
            resolve(response.json())
        })
    })
}
//Main procedure
async function main(){
    let res
    try{
        res=await request('a')
    }catch(e){

    }
    ..........
}

Usually in the scenario of asynchronous request, we will write the asynchronous request logic in an independent function, such as the request function in the above code, and then the main function main gets the data by calling the request function.

However, if the interface has high overhead, frequent calls and repeated query conditions, we can add a layer of cache agent between main and request. If the incoming parameters are consistent with the previous ones, we can directly return the previous query results in the next call. The code is as follows:

//Request data according to keyword
function request(keyword=''){    
    return new Promise((resolve,reject)=>{
        fetch(`http://example.com/data/${keyword}`)
        .then(response=> {
            resolve(response.json())
        })
    })
}

//Cache agent
const requestProxy=(function(){
    let cache={}
    return async function(keyword){
        if(cache[keyword])
            return cache[keyword]
        else{
            cache[keyword]=await request(keyword)
            return cache[keyword]
        }    
    }
})()

//Main procedure
async function main(){
    let res
    try{
        //The first time: get the result asynchronously
        res=await requestProxy('a')
        //The second time: get the result through cache
        res=await requestProxy('a')    }catch(e){

    }
    ..........
}

In addition, there are many scenarios in which the proxy mode can be used. For example, before the IMG node loads the image, it loads the chrysanthemum graph first. After the chrysanthemum graph is loaded, it starts the onload callback function to set the SRC of img back to the original image to be loaded.

To sum up with a sentence in “JavaScript Design Pattern and development practice”: proxy pattern is to provide a substitute or a place holder for an object while controlling the access to it.

(2)ES6 Proxy

seeMDN ProxyIt will be more direct. I’ll skip the description here.

2. The practice of agent mode in Vue project

(1) Demand analysis

A significant disadvantage of spa application is that it takes a long time to load the home page, and then one of the solutions is to reduce the size of the entry file by dividing a relatively large third-party independent library.

When maintaining the company’s SaaS project, I found that the company basically packaged large third-party libraries such as ecarts and video.js in app.js, which can be separated by dynamic loading through import. However, it is troublesome to dynamically introduce it into every file that needs to be referenced to ecarts and so on.

In order not to modify the existing stable operation of the module at the same time to reduce the requirements of the entry file, I use the proxy mode to achieveInert loading

(2) Function realization

Take ecarts as an example to realize lazy loading. First, paste the code directly

//echarts.js
let handler
let echartFactory
const echart = {
    init: (...args) => {
        let container = {
            //Initflag indicates whether the ecart has been initialized. False means uninitialized and true means initialized
            initFlag: false,
            //The method to be executed in the main function will be stored in FNS before the record is initialized
            fns: [],
            //After loading the instance to be initialized, all methods will be implemented in ecart
            echart:undefined
        }
        echartFactory.create(args)
            .then(echart => {
                container.echart = echart
                container.fns.forEach(({ fn, args }) => {
                    if(typeof(echart[fn])==='function')
                        echart[fn].apply(echart, args)
                })
                container.initFlag = true
            })
        return new Proxy(container, handler)
    }
}

handler = {
    get: function (target, key) {
        if (!target.initFlag) {
            //When the ecart in container is not loaded, the name and parameters of the program to be executed are collected and stored in FNS as objects
            return (...args) => {
                target.fns.push({
                    fn: key,
                    args
                })
            }
        } else {
            //When ecarts is initialized, the method is returned directly
            if (key in target.echart) {
                return target.echart[key]
            } else {
                return undefined
            }
        }
    }
}

echartFactory = {
    //Store ecarts module loaded from outside
    _echart: undefined,
    create: async function (args) {
        if (!this._echart) {
            this._echart = await import(/* webpackPrefetch: true */ 'echarts')
        }
        return this._echart.init.apply(undefined, args)
    }
}

export default echart

The ecart exported from the module is an object with only the init method. When the init method is executed, an object named container will be created each time, and then the ecart variable in the container will be initialized through the Create method in ecartfactory. This process will be carried out in promise, that is, in the micro task queue. The init method returns a proxy instance with container as the proxy object and handler as the processor. At this time, the ecart in the container has not been initialized.

The Create method of the ecartfactory object first checks its own private variable during execution_ Ecart. When it is undefined, the third-party library of ecarts will be loaded. After loading, the init method is executed through apply and the result is returned.

In the micro task queue, the returned results are copied to the echart in the container object to complete the initialization. After initialization, the functions and parameters in FNS will be executed successively through apply. After execution, assign initflag to true, which means that the whole initialization process has been completed.

After that, you can directly import it into the app.js entry file.

//App.js entry file

//Refer to ecart.js above
import echarts from './echarts.js'
Vue.prototype.$echarts = echarts

(3) Implementation effect

Proxy mode: lazy loading of third-party libraries such as ecarts in Vue
From the partition graph generated by bundleanalyzer, we can see that ecarts is indeed separated from app. JS.

//When using ecarts, there is no need to change the logic of the original file
...
<script>
export default {
    ...
    methods:{
        initEcharts(){
            this.echarts = this.$echarts.init(document.getElementById('echarts'))
            this.echarts.clear()
            let option={....}
            this.echarts.setOption(option)
        }
    }
    ...
}
</script>

When ecarts is used in the above Vue file, the following GIF effect can be achieved.
Proxy mode: lazy loading of third-party libraries such as ecarts in Vue
It can be seen from GIF that the 28. JS package with ecarts is loaded only after switching to the route using ecarts.

The same principle can also be used to realize the lazy loading of video. JS. I will paste the code here without explanation.

import 'video.js/dist/video-js.css'
let handler
let videoFactory

const video = function (...args) {
  let container = {
    initFlag: false,
    fns: [],
    video: undefined
  }
  videoFactory.create(args)
    .then(video => {
      container.video = video
      container.fns.forEach(({ fn, args }) => {
        video[fn].apply(video, args)
      })
      container.initFlag = true
    })
  return new Proxy(container, handler)
}

videoFactory = {
  _video: undefined,
  create: async function (args) {
    if (!this._video) {
      this._video = await import(/* webpackPrefetch: true */ 'video.js')
      this._video = this._video.default
    }
    return this._video.apply(undefined, args)
  }
}

handler = {
  get: function (target, key) {
    if (!target.initFlag) {
      return (...args) => {
        target.fns.push({
          fn: key,
          args
        })
      }
    } else {
      if (key in target.video) {
        return target.video[key]
      } else {
        return undefined
      }
    }
  }
}

export default video

Through the above separation, the entry file is reduced from more than 9m to about 4.5m.

Loading time before separation
Proxy mode: lazy loading of third-party libraries such as ecarts in Vue
Loading time after segmentation:
Proxy mode: lazy loading of third-party libraries such as ecarts in Vue
Many comparisons show that in the production environment, the loading time of the entry file after segmentation is 1 ~ 1.5s less than that before segmentation.

3. Development

Limit the number of concurrent requests by proxy mode

Recommended Today

The use of springboot Ajax

Ajax overview What is Ajax? data Ajax application scenarios? project Commodity system. Evaluation system. Map system. ….. Ajax can only send and retrieve the necessary data to the server, and use JavaScript to process the response from the server on the client side. data But Ajax technology also has disadvantages, the biggest disadvantage is that […]