This article directory

  • Interface Module Processing
  • Dynamic registration of Vue components
  • Page performance debugging: Hiper
  • Vue Advanced component package
  • Performance optimization: eventBus encapsulation
  • Webpack plug-in: Smells good

This project is based on Vue-CLI3, please see my previous article to know how to build it correctly:

“Vue Practice” project to upgrade vue-CLI3 correct posture

1. Interface module processing

1.1 axiosSecondary packaging

The encapsulation here is based on the JWT passed in the background. Please skip the encapsulated JWT.

import axios from 'axios'
import router from '.. /router'
import {MessageBox, Message} from 'element-ui'

let loginUrl = '/login'Axios.defaults.baseurl = process.env.vue_app_API axios.defaults.headers = {'X-Requested-With': 'XMLHttpRequest'} axios. Defaults. Timeout = 60000 / / request interceptor axios. Interceptors. Request. Use (config = > {if(router.history.current.path ! == loginUrl) {let token = window.sessionStorage.getItem('token')
      if (token == null) {
        router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})
        return false
      } else {
        config.headers['Authorization'] = 'JWT ' + token
      }
    }
    return config
  }, error => {
    Message.warning(error)
    return Promise.reject(error)
  })
Copy the code

This is followed by response interceptors (that is, exception handling)

axios.interceptors.response.use(
  response => {
    return response.data
  }, error => {
    if(error.response ! == undefined) { switch (error.response.status) {case 400:
          MessageBox.alert(error.response.data)
          break
        case 401:
          if (window.sessionStorage.getItem('out') === null) {
            window.sessionStorage.setItem('out', 1)
            MessageBox.confirm('Session invalid! Please log in again '.'tip', {confirmButtonText: 'Log back in', cancelButtonText: 'cancel'.type: 'warning'}).then(() => {
              router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})
            }).catch(action => {
              window.sessionStorage.clear()
              window.localStorage.clear()
            })
          }
          break
        case 402:
          MessageBox.confirm('Login timeout! '.'tip', {confirmButtonText: 'Log back in', cancelButtonText: 'cancel'.type: 'warning'}).then(() => {
            router.replace({path: loginUrl, query: {redirect: router.currentRoute.fullPath}})
          })
          break
        case 403:
          MessageBox.alert('No permission! ')
          break/ /... Ignore default: messagebox.alert (' Connection error${error.response.status}`)}return Promise.resolve(error.response)
  }
  return Promise.resolve(error)
})
Copy the code

In this case, the processing is that the session is invalid and the login times out. The specific changes need to be made according to the business.

Finally, the base request type encapsulation is exported.

export default {
  get (url, param) {
    if(param ! == undefined) { Object.assign(param, {_t: (new Date()).getTime()}) }else {
      param = {_t: (new Date()).getTime()}
    }
    return axios({method: 'get'// Use this getData (url, param) {getData (url, param) {return axios({method: 'get', url, params: param})
  },
  post (url, param, config) {
    return axios.post(url, param, config)
  },
  put: axios.put,
  _delete: axios.delete
}
Copy the code

A timestamp parameter is added to the GET request to avoid fetching data from the cache. In addition to the basic request types, there are many other requests, such as downloads and uploads, that require special headers, which can be encapsulated according to your needs.

Browser caching is based on URL caching. If the page is allowed to cache, the browser will not send the request to the server again within a certain period of time (before the cache aging time). Instead, the browser will directly obtain the specified resource from the cache.

1.2 Request merge by module

import http from '@/utils/request'
export default {
  A (param) { return http.get('/api/', param) },
  B (param) { return http.post('/api/', param) }
  C (param) { return http.put('/api/', param) },
  D (param) { return http._delete('/api/', {data: param}) },
}
Copy the code

utils/api/index.js:

import http from '@/utils/request'
import account from './account'/ / ignore... const api = Object.assign({}, http, account, \*... Other modules *\)export default api
Copy the code

1.3 Processing in global.js

In global.js introduce:

import Vue from 'vue'
import api from './api/index'/ / a little... const errorHandler = (error, vm) => { console.error(vm) console.error(error) } Vue.config.errorHandler = errorHandlerexportDefault {install (Vue) {// add component // add filter})$throw = (error) => errorHandler(error, this)
    Vue.prototype.$http= API // Other configuration}}Copy the code

When writing the interface, it can be simplified as:

async getData() { const params = {/*... key : value... * /}let res = await this.$http.A(params)
    res.code === 4000 ? (this.aSata = res.data) : this.$message.warning(res.msg)
}
Copy the code

2. Automate global registration of basic components

From @sherlocked93: Tips on Vue’s use

Official documentation: Automated global registration of the underlying components

When we write a component, we usually need to introduce another component:

<template>
    <BaseInput  v-model="searchText"  @keydown.enter="search"/>
    <BaseButton @click="search">
        <BaseIcon name="search"/>
    </BaseButton>
</template>
<script>
    import BaseButton from './baseButton'
    import BaseIcon from './baseIcon'
    import BaseInput from './baseInput'
    export default {
      components: { BaseButton, BaseIcon, BaseInput }
    }
</script>
Copy the code

It’s ok to write small projects, but once the projects get bloated… Gee. Here, with the help of WebPack, you create your own module context using the require.context() method to implement an automatic dynamic require component.

This method takes three parameters:

  • The directory of the folder to search
  • Whether its subdirectories should also be searched
  • A regular expression that matches a file.

Create a new componentRegister.js in the root of your base component folder:

import Vue from 'vue'/** * Capitalize the first letter * @param STR String * @example heheHaha * @return {string} HeheHaha
 */
function capitalizeFirstLetter (str) {
  returnStr.charat (0).toupperCase () + str.slice(1)} /** * Match'xx/xx.vue'Component format the component from the component name * @ param STR fileName * @ example ABC/BCD/def/basicTable vue * @return {string} BasicTable
 */
function validateFileName (str) {
  return /^\S+\.vue$/.test(str) &&
    str.replace(/^\S+\/(\w+)\.vue$/, (rs, The $1) => capitalizeFirstLetter(The $1))
}
const requireComponent = require.context('/'.true, /\.vue$/) // Find the.vue file in the component folder. If the file name is index, Requirecomponent.keys ().foreach (filePath => {const componentConfig = requireComponent(filePath)) const fileName = validateFileName(filePath) const componentName = fileName.toLowerCase() ==='index'
    ? capitalizeFirstLetter(componentConfig.default.name)
    : fileName
  Vue.component(componentName, componentConfig.default || componentConfig)
})
Copy the code

Finally we’re in main.js

import 'components/componentRegister.js'

We can use these basic components anytime, anywhere, without having to manually introduce them.

3. Page performance debugging: Hiper

When we write a single page application, it is quite tedious to see the performance changes after the page changes. Sometimes the only way to know whether it is a “positive optimization” or a “negative optimization” is to manually refresh the network. Hiper is a good solution to this pain point (in fact, Hiper is quietly running Chromium in the background to achieve non-sensitive debugging).

Hiper official documentation

After we develop a project or optimize the performance of a project, how to measure the performance of this project?

Our common approach is to look at the data in Performance and Network in Dev Tool, record a few key performance indicators, and refresh a few times before looking at those performance indicators.

Sometimes we find that the optimized project is slower than before because of the small sample size and the current “network,” “CPU,” and “memory” busyness.

If we have a tool that requests N web pages at once, and then averages the performance metrics, we can know with great accuracy whether the optimization is a “positive optimization” or a “negative optimization.”

And, you can also do comparison, to get the exact “how much optimization.” This tool is designed to address that pain point.

Global installation

sudo npm install hiper -g
# or yarn:
# sudo yarn global add hiper
Copy the code

Performance indicators

Key Value
DNS Query time domainLookupEnd – domainLookupStart
TCP Connection Time connectEnd – connectStart
The time it takes the first Byte to reach the browser responseStart – requestStart
Page download time responseEnd – responseStart
The time it takes to continue downloading resources after DOM is Ready domComplete – domInteractive
Bad time domInteractive – navigationStart
The DOM Ready time consuming domContentLoadedEventEnd – navigationStart
Total page loading time loadEventEnd – navigationStart

Developer.mozilla.org/zh-CN/docs/…

Use case configuration

 When we omit the protocol header, the default is to add 'https://' before the URL

 # Simplest use
 hiper baidu.com
 # How do URLS contain any parameters, please use double quotation marks
 hiper "baidu.com?a=1&b=2"
 Load the specified page 100 times
 hiper -n 100 "baidu.com?a=1&b=2"
 # Disable cache to load the specified page 100 times
 hiper -n 100 "baidu.com?a=1&b=2" --no-cache
 # Ban JavaScript from loading specified page 100 times
 hiper -n 100 "baidu.com?a=1&b=2" --no-javascript
 Load the specified page 100 times using GUI format
 hiper -n 100 "baidu.com?a=1&b=2" -H false
 Load the page 100 times using the specified userAgent
 hiper -n 100 "baidu.com?a=1&b=2" -u "Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
Copy the code

In addition, Cookie access can be configured

module.exports = {
    ....
    cookies:  [{
        name: 'token'.value: process.env.authtoken,
        domain: 'example.com'.path: '/'.httpOnly: true}],... }Copy the code
# load the above configuration file (assuming the configuration file is under /home/)
hiper -c /home/config.json

Alternatively, you can use js files as configuration files
hiper -c /home/config.js
Copy the code

4. Vue advanced component package

The common
and

are high-level (abstract) components.

export default {
  name: 'keep-alive',
  abstract: true. }Copy the code

All higher-order (abstract) components are declared by defining the abstract option. Higher-order (abstract) components do not render the real DOM. A normal abstract component would look like this:

import { xxx } from 'xxx'
const A = () => {
    .....
}

export default {
    name: 'xxx',
    abstract: true,
    props: ['... '.'... '], // lifecycle hook functionscreated() {... },...destroyed() {... },render() {
        const vnode = this.$slots.default
        ....
        return vnode
    },
})
Copy the code

4.1 Shaking/throttling abstract components

I don’t need to go into details about what stabilization and throttling are. The component code is posted here:

Adapted from: Vue implement function anti – shake component

const throttle = function(fn, wait=50, isDebounce, ctx) {
  let timer
  let lastCall = 0
  return function(... params) {if (isDebounce) {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        fn.apply(ctx, params)
      }, wait)}else {
      const now = new Date().getTime()
      if (now - lastCall < wait) return
      lastCall = now
      fn.apply(ctx, params)
    }
  }
}

export default {
    name: 'Throttle',
    abstract: true,
    props: {
      time: Number,
      events: String,
      isDebounce: {
        type: Boolean,
        default: false}},created () {
      this.eventKeys = this.events.split(', ')
      this.originMap = {}
      this.throttledMap = {}
    },
    render() {
        const vnode = this.$slots.default[0]
        this.eventKeys.forEach((key) => {
            const target = vnode.data.on[key]
            if (target === this.originMap[key] && this.throttledMap[key]) {
                vnode.data.on[key] = this.throttledMap[key]
            } else if (target) {
                this.originMap[key] = target
                this.throttledMap[key] = throttle(target, this.time, this.isDebounce, vnode)
                vnode.data.on[key] = this.throttledMap[key]
            }
        })
        return vnode
    },
})
Copy the code

The third parameter, isDebounce, is used to control the switching of the anti-shake throttling. Finally, in main.js, quote:

import Throttle from '.. /Throttle'. Vue.component('Throttle', Throttle)
Copy the code

use

<div id="app">
    <Throttle :time="1000" events="click">
        <button @click="onClick($event1)">click+1 {{val}}</button>
    </Throttle>
    <Throttle :time="1000" events="click" :isDebounce="true">
        <button @click="onAdd">click+3 {{val}}</button>
    </Throttle>
    <Throttle :time="3300" events="mouseleave" :isDebounce="true">
        <button @mouseleave.prevent="onAdd">click+3 {{val}}</button>
    </Throttle>
</div>
Copy the code
const app = new Vue({
    el: '#app'.data () {
        return {
            val: 0
        }
    },
    methods: {
        onClick ($ev, val) {
            this.val += val
        },
        onAdd () {
            this.val += 3
        }
    }
})
Copy the code

Abstract components are a great way to take over from mixins and implement the common functionality of abstract components without contaminate the DOM with component usage (adding unwanted div tags, etc.), wrapping arbitrary single child elements, etc

Whether or not to use abstract components is a matter of opinion.

5. Performance optimization: eventBus encapsulation

The essence of eventBus is to create a VUE instance and use an empty VUE instance as a bridge to communicate between vUE components. It is a solution to achieve communication between non-parent and child components.

The eventBus implementation is very simple

import Vue from 'Vue'
export default new Vue
Copy the code

One of the things that we tend to ignore the most when we use it, but we must not forget it, is the removal of eventBus.

If you do not clear it manually, it will always exist, so that the current execution will repeatedly enter the component that received the data to obtain the data, and the retrieval operation that was performed only once will be performed multiple times. It’s a serious problem when something that would have been triggered and executed only once becomes multiple times.

After a few minutes of continuous operations, the page would get stuck and use a lot of memory.

Therefore, in the vUE life cycle beforeDestroy or Destroyed, you need to clear eventBus with the $off method of the Vue instance

beforeDestroy(){
    bus.$off('click')}Copy the code

But when you have more than one eventBus, you need to redo the $off task. Encapsulating an eventBus is the better solution.

5.1 Having a life cycleeventBus

We can learn from Vue source Vue. Init:

 Vue.prototype._init = function(options? : Object) {const VM: Component = this // a UID The unique identifier of the VM instance vm. _UID = UI ++ //.... }Copy the code

Each Vue instance has its own _UID as a unique identifier, so we associate EventBus with _UID and transform it:

The implementation comes from: let EventBus used in Vue also have a lifecycle

class EventBus {
  constructor (vue) {
    if(! this.handles) { Object.defineProperty(this,'handles', {
        value: {},
        enumerable: false})} this.vue = Vue // mapping of _uid to EventName this.eventMapUID = {}}setEventMapUid (uid, eventName) {
    if(! this.eventMapUid[uid]) this.eventMapUid[uid] = [] this.eventMapUid[uid].push(eventName) // Push the name of each _UID subscribed event into the respective UID array}$on(eventName, callback, vm) {// this is used to fetch _uid when the component is used internallyif(! this.handles[eventName]) this.handles[eventName] = [] this.handles[eventName].push(callback)if (vm instanceof this.Vue) this.setEventMapUid(vm._uid, eventName)
  }
  $emit () {
    let args = [...arguments]
    let eventName = args[0]
    let params = args.slice(1)
    if (this.handles[eventName]) {
      let len = this.handles[eventName].length
      for (leti = 0; i < len; i++) { this.handles[eventName][i](... params) } } }$offVmEvent (uid) {
    let currentEvents = this.eventMapUid[uid] || []
    currentEvents.forEach(event => {
      this.$off(event)
    })
  }
  $off(eventName) {delete this. Handles [eventName]}}$EventBus) to uselet $EventBus = {}

$EventBus.install = (Vue, option) => {
  Vue.prototype.$eventBus = new EventBus(Vue)
  Vue.mixin({
    beforeDestroy() {// intercept beforeDestroy hooks that automatically destroy all of their subscribed events this.$eventBus.$offVmEvent(this._uid) 
    }
  })
}

export default $EventBus
Copy the code

Use:

/ / the main js... import EventBus from'./eventBus.js'
Vue.use(EnemtBus)
...
Copy the code

Component used in:

 created () {
    let text = Array(1000000).fill('xxx').join(', ')
    this.$eventBus.$on('home-on', (... args) => { console.log('home $on====>>>'. Args) this.text = text}, this)mounted () {
    setTimeout(() => {
      this.$eventBus.$emit('home-on'.'This is the home $emit parameter'.'ee'}}, 1000),beforeDestroy() {// There is no need to manually destroy eventBus subscribed events}Copy the code

6. Webpack plug-in: Smells good

6.1 replaceuglifyjsTerser Plugin

When the project upgraded vue-cli3 in early February, it encountered a problem: uglifyjs no longer supported webpack4.0. I searched around and found Terser Plugin on Google.

I mainly use these features:

  • cacheTo enable file caching.
  • parallel, using multi-process parallelism to speed up the build.
  • sourceMapTo map the error message location to the module (which stores the location information).
  • drop_consoleWhen packing, remove allconsolestatements
  • drop_debuggerWhen packing, remove alldebuggerstatements

As a lazy front end of the management team, many times writing pages will leave console.log, which affects performance. Set drop_console to smell good. The following configurations are valid for hands-on testing.

const TerserPlugin = require('terser-webpack-plugin')... new TerserPlugin({ cache:true,
parallel: true.sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
  compress: {
    drop_console: true,
    drop_debugger: true}}})Copy the code

See the Terser Plugin for more configuration

6.2 Enabling GZIP on both Ends

What are the benefits of turning on GZIP compression?

Can reduce the file size, faster transfer speed. Gzip is an effective way to save bandwidth and speed up your site.

  • When the server sends data, you can configure Content-Encoding: gzip. The user describes the data compression mode
  • After receiving the data, the client checks the information of the corresponding field and decodes it according to the corresponding format.
  • When requested by a client, accept-encoding :gzip is used and the user specifies which compression methods are accepted.

6.2.1 Webpackopengzip

The plug-in used here is CompressionWebpackPlugin

Const CompressionWebpackPlugin = require ('compression-webpack-plugin') module.exports = {plugins: [new CompressionWebpackPlugin]}Copy the code

Specific configuration:

const CompressionWebpackPlugin = require('compression-webpack-plugin');

webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip'.test: new RegExp('\\.(js|css)$'Default: 0 Threshold: 10240, // Example: a 1024B file is compressed to a size of 768B, minRatio: 0.75 minRatio: 0.8 // Default: 0.8 // Whether to delete the source file, default:false
      deleteOriginalAssets: false}))Copy the code

6.2.2 Expanding knowledge:NginxthegzipSet up the

Open /etc/nginx/conf.d and write the following configuration.

server { gzip on; gzip_static on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_proxied any; gzip_vary on; gzip_comp_level 6; gzip_buffers 16 8k; Gzip_http_version 1.1; . }Copy the code

Nginx tries to find and send the file /path/to/bundle.js.gz. If the file does not exist, or if the client does not support gzip, Nginx sends an uncompressed version of the file.

After saving the configuration, restart Nginx:

$ sudo service nginx restart
Copy the code

6.2.3 How To Verifygzip?

Test the request response for each resource by using curl and check content-encoding:

If content-encoding: gzip is displayed, the configuration is successful.

6.2.4 Difference and Significance of double-ended Gzip

The difference:

  1. Webpack compression compresses the files once during the build run, and then saves the compressed versions to disk.

  2. Some packages may have built-in caches when Nginx compresses files on request, so the performance penalty occurs only once (or infrequently), but usually the difference is that it occurs in response to an HTTP request.

  3. For real-time compression, it is often more efficient to have upstream agents (such as Nginx) handle gZIP and caching because they are built specifically for this purpose and do not suffer from server-side program runtime overhead (many of which are written in C).

  4. The advantage of using Webpack is that each time Nginx requests, the server needs to compress the information for a long time to return, which not only increases the server overhead, but also makes the requester impatient. It is much more efficient to use Nginx as a double guarantee (when requesting other directory resources) when we generate high-compression files directly at Webpack time and put them on the server as static resources.

  5. Note: It depends on the business of the project whether the compressed file is generated on request or at build time.

Ask for a push in Shenzhen

I would have liked to thank you for the dynamic configuration form, but it was too long and difficult to write.

Ok, one more piece of water, into the topic:

At present, I am (again) ready to change my job, I hope you and HR sister can promote a reliable front-end position in Shenzhen! ICU is off the table.

A collection of the author’s nuggets

  • “Vue Practice” 5 minutes for a Vue CLI plug-in
  • “Vue practices” arm your front-end projects
  • “Advanced front end interview” JavaScript handwriting unbeatable secrets
  • “Learn from source code” answers to Vue questions that interviewers don’t know
  • “Learn from the source code” Vue source code in JS SAO operation
  • “Learn from source code” thoroughly understand the Vue option Props
  • “Vue Practice” project to upgrade vue-CLI3 correct posture
  • Why do you never understand JavaScript scope chains?

The public,