vue优化首屏加载速度

711 阅读9分钟

image.png

image.png

明显看到app.js过大,浏览器运行6.8M耗时13.62s

优化1: 按需加载

import Home from '@/components/Index'

改为

const Index = resolve => require(['@/components/Index'], resolve)

上面那种普通的加载方式的缺点是把所有的路由打包在一个js文件里面页面多的话这个文件会非常大加载的很慢。

下面这种方式是按需加载在访问的时候只加载相关的路由,又被叫做(懒加载)

const Login = resolve => require(['@/views/Login.vue'], resolve);
const Work = resolve => require(['@/views/Work.vue'], resolve);

{
      path: '/conversation',
      name: 'conversation',
      component: resolve => require(['@/views/communication/conversation'], resolve)
    },
    {
      path: '/work',
      name: 'work',
      component: Work
    },

优化2:第三方依赖包的引入

main.js

import Vue from 'vue'
import router from './router'
import Element from 'element-ui'
import echarts from 'echarts'


Vue.use(Element)
Vue.prototype.echarts = echarts

new Vue({
  el: '#app',
  store,
  router,
  components: {App},
  template: '<App/>'
})

这样引入依赖包会把所依赖的第三方包全都打包起来从而造成打包后的 app.js和vendor.js文件过大加载的很慢,把这种方式改为CDN引入。

首先把main.js种引入的第三方包注释掉,vue不用注释

import Vue from 'vue'
//import router from './router'
//import Element from 'element-ui'
//import echarts from 'echarts'


//Vue.use(Element)
//Vue.prototype.echarts = echarts

new Vue({
  el: '#app',
  store,
  router,
  components: {App},
  template: '<App/>'
})

在vue.conf.js中增加以下设置,这样就不会把依赖包打包到自己的项目里面了

module.exports = {

  configureWebpack: config => {
    config.externals = {
      'vue': 'Vue',
      'element-ui': 'ELEMENT',
      'echarts': 'echarts'
    };
  }

}

然后在index.html中用CDN的方式引入

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.11.0/theme-chalk/index.css">
  <title></title>
  
  <!-- 引入组件库 -->
   <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
   <script src="https://cdn.bootcss.com/element-ui/2.13.0/index.js"></script>
   <link href="https://cdn.bootcss.com/element-ui/2.4.5/theme-chalk/index.css" rel="stylesheet">
   <script src="https://cdn.bootcss.com/echarts/4.5.0/echarts.min.js"></script>
   
</head>
<body>
   <div id="app"></div>
</body>
</html>

这样在打包的时候就不会把这些依赖包打包到里面,vendor.js有之前的2M多变成了200多K。

优化3:vue-router懒加载功能

使用初衷是为了减少首页http请求过多,默认预加载了全部的js文件

官网文档提出 PreloadPrefetch,vue-cli3默认使用俩大功能

vue.config.js增加如下配置,取消prefetchpreload,这样就是实现加载当前所需

chainWebpack(config) {
    // 删除预加载(打包会将文件打包成0.js),使用按需加载(加载模块文件)
    config.plugins.delete('preload') 
    config.plugins.delete('prefetch') 
  }

1、Preload用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload
2、Prefetch用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

// webpackPrefetch 开启home页的预加载 ,未设置的默认执行路由懒加载
// webpackChunkName 指定about页面在当前home加载完提前加载
component: () => import(/* webpackChunkName: "about",webpackPrefetch: true */ "../views/Home.vue"),

优化4:压缩代码配置

uglifyjs-webpack-plugin

// cli3 默认不包含此依赖,需要下载引入
npm i uglifyjs-webpack-plugin --save

vue.config.js添加以下代码

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
........此次省略其他配置
    configureWebpack(config){
        //压缩代码
        config.plugins.push(
            new UglifyJsPlugin({ // 更多配置请参考 uglifyJs官方文档
                uglifyOptions: {
                      compress: {
                        drop_debugger: true, //生产环境自动删除debugger
                        drop_console: true,  //生产环境自动删除console
                      },
                      warnings: false,
                },
                sourceMap: false,//是否生成map文件
                parallel: true,//使用多进程并行运行来提高构建速度。默认并发运行数:os.cpus().length - 1。
            })
        )
    }
}

CSS代码压缩

CSS代码压缩使用css-minimizer-webpack-plugin,效果包括压缩、去重

代码的压缩比较耗时间,所以只用在打包项目时,所以只需要在webpack.prod.js中配置

npm i css-minimizer-webpack-plugin -D
// webpack.prod.js

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

  optimization: {
    minimizer: [
      new CssMinimizerPlugin(), // 去重压缩css
    ],
  }

JS代码压缩

代码压缩使用terser-webpack-plugin,实现打包后JS代码的压缩

代码的压缩比较耗时间,所以只用在打包项目时,所以只需要在webpack.prod.js中配置

npm i terser-webpack-plugin -D
// webpack.prod.js

const TerserPlugin require('terser-webpack-plugin')

  optimization: {
    minimizer: [
      new CssMinimizerPlugin(), // 去重压缩css
      new TerserPlugin({ // 压缩JS代码
        terserOptions: {
          compress: {
            drop_consoletrue, // 去除console
          },
        },
      }), // 压缩JavaScript
    ],
  }

tree-shaking

tree-shaking简单说作用就是:只打包用到的代码,没用到的代码不打包,而webpack5默认开启tree-shaking,当打包的modeproduction时,自动开启tree-shaking进行优化

module.exports = {
  mode: 'production'
}

source-map类型

source-map的作用是:方便你报错的时候能定位到错误代码的位置。它的体积不容小觑,所以对于不同环境设置不同的类型是很有必要的。

  • 开发环境

开发环境的时候我们需要能精准定位错误代码的位置

// webpack.dev.js

module.exports = {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map'
}
  • 生产环境

生产环境,我们想开启source-map,但是又不想体积太大,那么可以换一种类型

// webpack.prod.js

module.exports = {
  mode: 'production',
  devtool: 'nosources-source-map'
}

打包体积分析

使用webpack-bundle-analyzer可以审查打包后的体积分布,进而进行相应的体积优化

只需要打包时看体积,所以只需在webpack.prod.js中配置

npm i webpack-bundle-analyzer -D
// webpack.prod.js

const {
  BundleAnalyzerPlugin
} = require('webpack-bundle-analyzer')

  plugins: [
    new BundleAnalyzerPlugin(),
]

优化5: 代码分割配置优化 splitChunks

1.node_modules全部打包成chunk-libs并设置优先级为10
2.将vantUI单独拆包,设置优先级为20
3.将自己的comnponents打包成common设置优先级为5
vue.config.js 添加以下代码

module.exports = {
........此次省略其他配置
      chainWebpack(config) {
            config.optimization.splitChunks({
              chunks: 'all', // 优化所有模块的复用性,哪些modules被用于优化到不同chunks中,优化后的chunks可以包括异步和非异步modules
              minSize: 30000, // 代码分割的最小值,默认30k
              minChunks: 1, // 模块被引用次数多少时才会进行代码分割,默认为1;
               // 缓存组,将所有加载模块放在缓存里面一起分割打包
              cacheGroups: {
                    libs: {
                        name: 'chunk-libs',
                        test: /[\/]node_modules[\/]/,
                        priority: 10,
                        chunks: 'initial' // only package third parties that are initially dependent
                    },
                    elementUI: {
                      name: 'chunk-elementUI', // split elementUI into a single package
                      priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
                      test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
                    },
                     // 如果想把vantUI单独打包出来 可以通过当前方式设置  其他后引入的依赖也可以通过这种方式单独打包成一个js文件
                    vant: {
                      name: 'chunk-vant',
                      priority: 20, // 优先级,先打包到哪个组里面
                      test: /[\\/]node_modules[\\/]_?vant(.*)/
                    },
                    hotentUI: {
                      name: 'chunk-hotentUI',
                      priority: 5, // 优先级,先打包到哪个组里面
                      test: /[\\/]node_modules[\\/]_?hotent-ui(.*)/
                    },
                    commons: {
                      name: 'chunk-commons',
                      test: resolve('src/components'), // can customize your rules
                      minChunks: 3, //  minimum common number 模块至少使用次数
                      priority: 5,
                      reuseExistingChunk: true
                    }
                }
            })
      }
}

启动报错:resolve is not defind
莫慌这是因为引用失败,vue.config.js 文件上方加入引用

const path = require('path');
function resolve(dir){
    return path.join(__dirname, dir)
}

ok了,当前启动成功

优化6:使用gzip的形式进行访问优化

利用 compression-webpack-plugin 开启gzip

// 安装依赖
npm i compression-webpack-plugin --save-dev

vue.config.js文件添加如下配置

const CompressionWebpackPlugin = require('compression-webpack-plugin')//开启gzip
module.exports = {
......此处省略一万行
    chainWebpack(config) {
            config.plugin('compression').use(CompressionWebpackPlugin).tap(() => [
                    {
                        test: /.js$|.html$|.css/, // 匹配文件名
                        threshold: 1024, // 超过1k进行压缩
                        deleteOriginalAssets: false // 是否删除源文件
                    }
            ])
    }
}

gzip压缩率还是很高的,下方是对比图\

image.png gzip压缩率对比

优化7:合并css文件(经过测试没发现明显优化)

主要是借助ExtractTextPlugin插件
优化思路是减少css文件的请求数量

css: {
        // 是否将组件中的 CSS 提取至一个独立的 CSS 文件中,当作为一个库构建时,你也可以将其设置为 false 免得用户自己导入 CSS
        // 默认生产环境下是 true,开发环境下是 false
        extract: !(regDev.test(process.env.NODE_ENV))? true : false,
        // 当为true时,css文件名可省略 module 默认为 false
        modules: false,
        // 是否为 CSS 开启 source map。设置为 true 之后可能会影响构建的性能
        sourceMap: false,
        //向 CSS 相关的 loader 传递选项(支持 css-loader postcss-loader sass-loader less-loader stylus-loader)
        loaderOptions: { css: {}, less: {} }
    }

为什么要CSS分离,开启CSS分离之后每个组件的css会单独打包,造成页面上有大量请求,所以在正式环境中将CSS分离关闭

启动项目发现提示warning警告
WARN "css.modules" option in vue.config.js is deprecated now, please use "css.requireModuleExtension" instead.
css-loader升级v3后使用css.requireModuleExtension代替css.modules

css: {
        ........
        // 当为true时,css文件名可省略 requireModuleExtension默认为 false
        requireModuleExtension: false,
        .......
    }

优化8:构建时间的优化

1、thread-loader

多进程打包,可以大大提高构建的速度,使用方法是将thread-loader放在比较费时间的loader之前,比如babel-loader

由于启动项目和打包项目都需要加速,所以配置在webpack.base.js

npm i thread-loader -D
// webpack.base.js

{
        test/.js$/,
        use: [
          'thread-loader',
          'babel-loader'
        ],
      }
}

2、cache-loader

缓存资源,提高二次构建的速度,使用方法是将cache-loader放在比较费时间的loader之前,比如babel-loader

由于启动项目和打包项目都需要加速,所以配置在webpack.base.js

npm i cache-loader -D
// webpack.base.js

{
        test/.js$/,
        use: [
          'cache-loader',
          'thread-loader',
          'babel-loader'
        ],
},

3、开启热更新

比如你修改了项目中某一个文件,会导致整个项目刷新,这非常耗时间。如果只刷新修改的这个模块,其他保持原状,那将大大提高修改代码的重新构建时间

只用于开发中,所以配置在webpack.dev.js

// webpack.dev.js

//引入webpack
const webpack = require('webpack');
//使用webpack提供的热更新插件
   plugins: [
   new webpack.HotModuleReplacementPlugin()
    ],
    //最后需要在我们的devserver中配置
     devServer: {
+     hottrue
    },

4、exclude & include

  • exclude:不需要处理的文件
  • include:需要处理的文件

合理设置这两个属性,可以大大提高构建速度

webpack.base.js中配置

// webpack.base.js

      {
        test/.js$/,
        //使用include来指定编译文件夹
        include: path.resolve(__dirname, '../src'),
        //使用exclude排除指定文件夹
        exclude/node_modules/,
        use: [
          'babel-loader'
        ]
      },

优化9:用户体验优化

小图片转base64

对于一些小图片,可以转base64,这样可以减少用户的http网络请求次数,提高用户的体验。webpack5url-loader已被废弃,改用asset-module

webpack.base.js中配置

// webpack.base.js

{
   test/.(png|jpe?g|gif|svg|webp)$/,
   type'asset',
   parser: {
     // 转base64的条件
     dataUrlCondition: {
        maxSize25 * 1024// 25kb
     }
   },
   generator: {
     // 打包到 image 文件下
    filename'images/[contenthash][ext][query]',
   },
},

合理配置hash

我们要保证,改过的文件需要更新hash值,而没改过的文件依然保持原本的hash值,这样才能保证在上线后,浏览器访问时没有改变的文件会命中缓存,从而达到性能优化的目的

webpack.base.js中配置

// webpack.base.js

  output: {
    pathpath.resolve(__dirname, '../dist'),
    // 给js文件加上 contenthash
    filename: 'js/chunk-[contenthash].js',
    clean: true,
  },

完整代码:vue.confog.js

const path = require('path');
const themePath = path.resolve(__dirname, 'src/styles/van-ui.less');
function resolve(dir) {
  return path.join(__dirname, dir);
}
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');

module.exports = {
  baseUrl: '/',
  runtimeCompiler: true,
  productionSourceMap: false,
  lintOnSave: false,
  css: {
    loaderOptions: {
      // 对应适配
      postcss: {
        // 这是rem适配的配置  注意: remUnit在这里要根据lib-flexible的规则来配制,如果您的设计稿是750px的,用75就刚刚好。
        plugins: [
          require('postcss-px2rem')({
            remUnit: 37.5
          })
        ]
      },
      less: {
        modifyVars: {
          hack: `true; @import "${themePath}";`
        }
      }
    }
  },
  chainWebpack(config) {
    // 删除预加载(打包会将文件打包成0.js),使用按需加载(加载模块文件)
    config.plugins.delete('preload'); // TODO: need test
    config.plugins.delete('prefetch'); // TODO: need test

    // set svg-sprite-loader
    config.module
      .rule('svg')
      .exclude.add(resolve('src/icons'))
      .end();
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end();

    // config
    //   // https://webpack.js.org/configuration/devtool/#development
    //   .when(process.env.NODE_ENV === 'dev',
    //   // cheap-source-map--错误代码准确信息,源代码的错误位置 、source-map--在外部生成一个文件,在控制台会显示 错误代码准确信息 和 源代码的错误位置
    //     config => config.devtool('cheap-source-map')
    //   );

    config
      .when(process.env.NODE_ENV !== 'development',
        config => {
          config
            .plugin('ScriptExtHtmlWebpackPlugin')
            .after('html')
            .use('script-ext-html-webpack-plugin', [{
            // `runtime` must same as runtimeChunk name. default is `runtime`
              inline: /runtime\..*\.js$/
            }])
            .end();
          // 如果使用了某些长期不会改变的库,像 element-ui ,打包完成有 600 多 KB ,
          // 包含在默认 vendor 中显然不合适,每次用户都要加载这么大的文件体验不好,所以要单独打包
          config
            .optimization.splitChunks({
              chunks: 'all', // 优化所有模块的复用性,哪些modules被用于优化到不同chunks中,优化后的chunks可以包括异步和非异步modules
              minSize: 30000, // 代码分割的最小值,默认30k
              minChunks: 1, // 模块被引用次数多少时才会进行代码分割,默认为1;
              // 缓存组,将所有加载模块放在缓存里面一起分割打包
              cacheGroups: {
                // libs: {
                //   name: 'chunk-libs',
                //   test: /[\\/]node_modules[\\/]/,
                //   priority: 10,
                //   minSize: 1024,
                //   chunks: 'all' // only package third parties that are initially dependent
                // },
                elementUI: {
                  name: 'chunk-elementUI', // split elementUI into a single package
                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
                },
                vant: {
                  name: 'chunk-vant',
                  priority: 20, // 优先级,先打包到哪个组里面
                  test: /[\\/]node_modules[\\/]_?vant(.*)/
                },
                hotentUI: {
                  name: 'chunk-hotentUI',
                  priority: 5, // 优先级,先打包到哪个组里面
                  test: /[\\/]node_modules[\\/]_?hotent-ui(.*)/
                },
                commons: {
                  name: 'chunk-commons',
                  test: resolve('src/components'), // can customize your rules
                  minChunks: 3, //  minimum common number 模块至少使用次数
                  priority: 5,
                  reuseExistingChunk: true
                }
              }
            });
          config.optimization.runtimeChunk('single');

          config.optimization.minimizer = [
            new UglifyJsPlugin({ // 更多配置请参考 uglifyJs官方文档
              uglifyOptions: {
                compress: {
                  drop_debugger: true, // 生产环境自动删除debugger
                  drop_console: true // 生产环境自动删除console
                },
                warnings: false
              },
              sourceMap: false, // 是否生成map文件
              parallel: true // 使用多进程并行运行来提高构建速度。默认并发运行数:os.cpus().length - 1。
            })
          ];

          config.plugin('compressionPlugin')
            .use(new CompressionWebpackPlugin({
              test: /\.js$|\.html$|\.css/, // 匹配文件名
              threshold: 1024, // 超过1k进行压缩
              deleteOriginalAssets: false // 是否删除源文件
            }));

          // config.plugin('preload').tap(() => [
          //   {
          //     rel: 'preload',
          //     fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
          //     include: 'initial'
          //   }
          // ]);
        }
      );
  },
  configureWebpack: config => {
    config.externals = {
      'vue': 'Vue',
      'element-ui': 'ELEMENT',
      'echarts': 'echarts'
    };
  }

};

参考文档: www.jianshu.com/p/f9f796750… www.cnblogs.com/lzweb/p/117…

分析包内容

cli

在通常情况下,我们无法判断的优化点,都是在打包后,我们无法分析出,那些东西不是我们在首屏必须需要的,从而不能做出针对新的优化,为了解决当前问题,各大bundle厂商也都有各自的分析包的方案

以vue-cli 为例

"report": "vue-cli-service build --report"

我们只需要在脚手架中提供以上命令,就能在打包时生成,整个包的分析文件

图片

Lighthouse

lighthouse[1] 是 Google Chrome 推出的一款开源自动化工具,它可以搜集多个现代网页性能指标,分析 Web 应用的性能并生成报告,为开发人员进行性能优化的提供了参考方向。

说起Lighthouse在现代的谷歌浏览器中业已经集成

图片

\