Vue-cli performance optimization

The problem

Is the loading time of the first screen too long?

Why is this a problem?

What’s the solution…?

The following methods can optimize the project structure to a large extent and reduce the loading time of the first screen

How does a Webpack build package split code

There are currently three rules for code packaging and splitting

  1. Entrance starting point, according toentryConfigure to split code
  2. Dynamic import: Import by moduleimport()Segmentation code
  3. splitChunks: Code splitting Configures rules and splits codes according to rules

Create a new project using vue create and output the following file after build

Q1: Why do you pack up these files?

Here’s a look at the default packaging configuration for the Vue CLI

The key code is posted below

{// entry configuration: {app: ['./ SRC /main.js']}, // exit configuration output: {path: 'E:\\demo\\vue-cli-vue2-async-components-demo\\dist', filename: 'js/[name].[contenthash:8].js', publicPath: '/', chunkFilename: 'js/[name].[Contenthash :8].js'}, // Code split configuration Optimization: {splitChunks: {cacheGroups: {vendors: { name: 'chunk-vendors', test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' }, common: { name: 'chunk-common', minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } } } } }Copy the code

You can see it here:

It is clear that app.[Contenthash :8].js is segmented by entry configuration entry due to the segmentation rule of rule 1

What about about.[contenthash:8].js?

It is time to check the lazy loading of the Vue Router

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '.. /views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/'.name: 'Home'.component: Home
  },
  {
    path: '/about'.name: 'About'.// route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () = > import(/* webpackChunkName: "about" */ '.. /views/About.vue') // Key code}]const router = new VueRouter({
  mode: 'history'.base: process.env.BASE_URL,
  routes
})

export default router
Copy the code

This is the route configuration for initializing the project

Component: () => import(/* webpackChunkName: “about” */ ‘.. /views/About.vue’)

What is webpackChunkName.?

Webpack magic annotation, function: split into which chunk

The final chunk-vendors.[Contenthash :8].js is, therefore, a package cut out by splitChunks that contains the addons referenced in node_modules

The code segment

In the Vue project, with the exception of the first routing page, all routes are imported using import(), which makes lazy loading of routes more efficient by loading components only when the routes are accessed

It makes use of Vue’s asynchronous component (opens New Window) and Webpack’s code splitting function (opens New Window) to implement lazy loading of routing components easily

One important point is the magic notes for Webpack

const Foo = () = > import(/* webpackChunkName: "group-foo" */ './Foo.vue')
Copy the code

Webpack separates the code according to the name of the magic comment. After the configuration above, a chunk named group-Foo is packaged separately into a file. This effect can be applied not only to route lazy loading, but also to single-file components

Component split

In the parent component, if a popover is required to pop up after clicking the button, the popover is generally encapsulated as a single single-file component, as follows:

Import components directly, registering them directly into components

<template> <div class="home"> <img alt="Vue logo" src=".. / assets/logo PNG "/ > < el - button @ click =" $refs. HomeAdd. DialogVisible = true "> show < / el - button > < the HelloWorld MSG =" Welcome to Your Vue.js App" /> <HomeAdd ref="homeAdd"></HomeAdd> </div> </template> <script> // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' import HomeAdd from './HomeAdd.vue' export default { name: 'Home', mounted() { console.log('Home') }, components: { HelloWorld, HomeAdd } } </script>Copy the code

After writing the code, the package looks like this:

You can see app.[contenthash:8].js increased from 6.57 KB to 6.86 KB

Because packaging packs in incoming components, it keeps increasing the size of the main package as more pages grow

Instead, it could be written like this:

<template> <div class="home"> <img alt="Vue logo" src=".. / assets/logo PNG "/ > < el - button @ click =" $refs. HomeAdd. DialogVisible = true "> button < / el - button > < the HelloWorld MSG =" Welcome to Your Vue.js App" /> <HomeAdd ref="homeAdd"></HomeAdd> </div> </template> <script> // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'Home', mounted() { console.log('Home') }, components: { HelloWorld, HomeAdd: () => import(/* webpackChunkName: "home.add" */ './HomeAdd.vue') } } </script>Copy the code

In this way, popovers can be separately packaged into chunks of home.add, so that the service can be divided into more and smaller packages to avoid excessive loading time caused by a large package

Plug-in segmentation

If we need to use a method in LoDash, we’ll import the package, call it, and see what the packaged project looks like.

! [build-1.4](E: project\MyNote\_img\build\build-1.4.png)<template> <div class="home"> <img Alt ="Vue logo" SRC =".. /assets/logo.png" /> {{ value }} <HelloWorld msg="Welcome to Your Vue.js App" /> <HomeAdd ref="homeAdd"></HomeAdd> </div> </template> <script> // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' import _ from Lodash export default {name: 'Home', mounted() { this.compute() console.log('Home') }, data() { return { value: '' } }, methods: {compute() {const arr = ['Turtle', 'Hello', 'World'] this.value = _. Join (arr, '-') // Call loadsh join method}}, components: { HelloWorld, HomeAdd: () => import(/* webpackChunkName: "home.add" */ './HomeAdd.vue') } } </script>Copy the code

Chunk-vendors.[Contenthash :8].js has increased from 133KB to 212KB

Splitting can also be applied to references to javascript plug-ins

<template> <div class="home"> <img alt="Vue logo" src=".. PNG "/> < button@click ="compute"> </button> {{value}} <HelloWorld MSG ="Welcome to Your vue.js App" /> <HomeAdd ref="homeAdd"></HomeAdd> </div> </template> <script> // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'Home', mounted() { console.log('Home') }, data() { return { value: '' } }, methods: { async compute() { const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash') const arr = ['Turtle', 'Hello', 'World'] this.value = _.join(arr, '-') } }, components: { HelloWorld, HomeAdd: () => import(/* webpackChunkName: "home.add" */ './HomeAdd.vue') } } </script>Copy the code

Lodash is then packaged separately, reducing the chunk-vendors.[Contenthash :8].js

You can look closely at the output of DevTools and see that the JS resource is requested after the click

The code segment

In Vue projects, the common JS are packaged into a chunk-vendors.[Hash]:8.js file, which gets bigger and bigger as vendors.[HASH]: vendors. Solution: Use directional code separation to separate some plug-ins from the file and create a separate file

Technical points: Use splitChunks provided with Webpack to configure segmentation

Configuration items vue. Config. Js

module.exports = {
  chainWebpack(config){ config.when(process.env.NODE_ENV ! = ='development'.config= > {
      config.optimization.splitChunks({
        chunks: 'all'.cacheGroups: {
          elementUI: {
            name: 'chunk-elementUI'.priority: 20.test: /[\\/]node_modules[\\/]_? element-ui(.*)/}}})})}}Copy the code

A typical project will introduce a UI entirely, for example, elementUI,

The above configuration packages elementUI separately, generating a chunk-elementui.[hash]:8.js file

It’s about 600KB in size, so you can reduce the bulk of the main package by that much

splitChunksCode split configuration details

This configuration object represents the default configuration of SplitChunksPlugin

splitChunks: {
    chunks: "async".minSize: 30000.minChunks: 1.maxAsyncRequests: 5.maxInitialRequests: 3.automaticNameDelimiter: '~'.name: true.cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
    	default: {
            minChunks: 2.priority: -20.reuseExistingChunk: true}}}Copy the code

Each of these items is explained here

chunks

  • Meaning: Packaging mode, mode:asyncAsynchronous,allall
  • Default value:asyncAsynchronous: Only modules introduced asynchronously are segmented
  • all: Whether the module is imported asynchronously or synchronously, the code is split

minSize

  • Meaning: Code splitting is performed when imported modules need to exceed this value
  • Default value: 30000 bytes

minChunks

  • Meaning: How many times must a module be imported before code splitting is performed
  • Default value: 1
  • Note: This configuration item only applies to synchronously imported modules, as asynchronous modules are code split

maxAsyncRequests

  • Description: How many modules are loaded at the same time
  • Example: When we introduce 10 libraries asynchronously, it is normal for each library to split the code into a separate JS filemain.js), which generates divisionmain.jsOutside of the 10 split JS files, ifmaxAsyncRequests: 5When packaging, the first 5 class libraries will be divided into code to generate the corresponding 5 JS files, and the last 5 class libraries still existmain.jsIn, no code segmentation is performed
  • Default value: 5

maxInitialRequests

  • Meaning: To the entry fileentryThe maximum number of files that can be partitioned is the maximum number of files that can be partitioned
  • Default value: 3

maxAsyncRequestswithmaxInitialRequestsThe difference between

  • maxAsyncRequestsContains both the import file and the modules imported from the import dependency file (which is actually a module)maxAsyncRequestsSet the value of the
  • maxInitialRequestsOnly the modules imported directly from the entry file are counted

automaticNameDelimiter

  • Meaning: The link symbol of the file name
  • Default value:~

name

  • Meaning: Determines the cache groupcacheGroupsWithin thenameWhether to take effect
  • Default value: true

cacheGroups

Cache group

Q: What is a cache group?

A:

  1. It only works on synchronous imported modules, which are partitioned and cached according to relevant configurations
    1. If no cache group is configured, the cache group will be split based on the default configuration
    2. If there is a cache group, the modules that meet the configuration items of the cache group in each module are put into the cache group first. After all modules are analyzed, the modules that meet the configuration items of the cache group are packaged together
  2. This does not work for asynchronous imported modules because asynchronously imported modules generate a separate module

CacheGroups inherits the values of all attributes in splitChunks, Such as chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, automaticNameDelimiter, and name, we can also reassign values in cacheGroups, Overrides the value of splitChunks

In addition, some attributes are only available in cacheGroups: test, Priority, and reuseExistingChunk

  • text
    • Description: Regular matching conditions
  • priority
    • Priority of the cache group
    • The higher the number, the higher the rank
  • reuseExistingChunk
    • If a module is packaged, the previous module will be reused when the same module is encountered

opengzipThe compression

Gizp compression is an HTTP request optimization that improves load speed by reducing file size

HTML, JS, CSS files and even JSON data can be compressed with it, reducing the size by more than 60%

compression-webpack-plugin

Gzip compression can be implemented with the Compression Webpack Plugin when webpack is packaged, which needs to be installed first

yarn add -D compression-webpack-plugin
Copy the code

Configure it in vue.config.js

const CompressionPlugin = require('compression-webpack-plugin')

module.exports = {
  configureWebpack: () = > {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new CompressionPlugin({
            test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png/.// The type of file to compress
            threshold: 10240.// Compress over 10K
            deleteOriginalAssets: false // Whether to delete the original file})]}}}}Copy the code

Packaging error after installation

TypeError: Cannot read property 'tapPromise' of undefined

The reason is that the version of compression-webpack-plugin is too high, and the version has to be lowered

Yarn add - D [email protected]Copy the code

nginxconfiguration

Deploying to Nginx directly after configuring the Vue part will not take effect. You must also enable the Gzip function of Nginx to do so

First you need to prepare to configure nginx, in HTTP:

// nginx enable gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; Gzip_http_version 1.1; Gzip_types text/plain text/ CSS application/json application/ X-javascript text/ XML application/ XML application/xml+rss text/javascript image/jpeg image/gif image/png image/jpg;Copy the code

Introduced the CDN

We are used to NPM importing packages, but importing packages will be packed in at packaging time, thus increasing the size of the package

When the browser loads, some packages are too large, resulting in a long blank screen and poor experience

At this point, some packages can be loaded into the project in the form of CDN import

The configuration is as follows:

const env = process.env.NODE_ENV === 'development' ? ' ' : '.min'
const cdn = {
  css: [
    '/ / unpkg.com/[email protected]/lib/theme-chalk/index.css'.'/ / cdn.bootcdn.net/ajax/libs/animate.css/3.5.1/animate.css'.'/ CDN/iconfont / 1.0.0 / index. The CSS'.'/ CDN/avue / 2.7.3 / index. The CSS'].js: [
    '/util/aes.js'.` / / cdn.jsdelivr.net/npm/[email protected]/dist/vue${env}.js`.` / / cdn.jsdelivr.net/npm/[email protected]/dist/vuex${env}.js`.` / / cdn.jsdelivr.net/npm/[email protected]/dist/vue-router${env}.js`.` / / unpkg.com/[email protected]/dist/axios${env}.js`.'/ / unpkg.com/[email protected]/lib/index.js'.'/ CDN/avue / 2.7.3 / avue. Min. Js'.'/ / cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js'.'/ / cdn.jsdelivr.net/npm/[email protected]/lodash.min.js'.'/ / unpkg.com/[email protected]/dist/xlsx.min.js']}module.exports = {
  pages: {
    index: {
      entry: 'src/main.js'.cdn: cdn
    }
  },
  chainWebpack: config= > {
    // Ignore the package file
    config.externals({
      vue: 'Vue'.'vue-router': 'VueRouter'.vuex: 'Vuex'.axios: 'axios'.'element-ui': 'ELEMENT'.moment: 'moment'.lodash: '_'.xlsx: 'XLSX'}}})Copy the code
<! DOCTYPEhtml>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = 0">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no">
  <meta http-equiv="X-UA-Compatible" content="chrome=1"/>
  <! Key traversal -->
  <% for (let i in htmlWebpackPlugin.options.cdn.css) { %>
    <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />The < %} % ><title>XXX</title>
</head>

<body>
<div id="app"></div>
<! Key traversal -->
<% for (let i in htmlWebpackPlugin.options.cdn.js) { %>
  <script
    type="text/javascript"
    src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"
  ></script>The < %} % ></body>

</html>

Copy the code

Key points:

  1. Config. externals, the imported package will have a globally injected variable, so when the page references this package, it will refer to this variable

    'vue-router': 'VueRouter'

    When importing vue-router, find the VueRouter variable

    How do I see which variable the package is injecting? Only by looking at the source code, which variable is exported

  2. Pages. Index. CDN: the CDN is injected into a variable called pages. Name ‘The name can be arbitrary, just a traversal identifier