0


Vue首屏加载优化

打包优化

移除 preload(预载) 与 prefetch (预取)

vue 脚手架默认开启了 **preload **与 prefetch,对于小项目可以提升体验感,但当我们项目很大时,首屏加载就会很慢很慢。

先简单了解一下 **preload **与 prefetch

**preload **与 **prefetch **都是一种资源预加载机制;

**preload **是预先加载资源,但并不执行,只有需要时才执行它;

**prefetch **是意图预获取一些资源,以备下一个导航/页面使用;

**preload **的优先级高于 prefetch

配置文件:vue.config.js

chainWebpack: config => {
    // 移除 preload(预载) 插件
    config.plugins.delete('preload')
    // 移除 prefetch(预取) 插件
    config.plugins.delete('prefetch')
}

可视化资源分析工具

安装:npm install webpack-bundle-analyzer --save-devg

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

chainWebpack: config => {
    // 添加资源可视化工具
    config.plugins.push(
        new BundleAnalyzerPlugin()
    )
}

它可以查看资源模块的体积分布,然后可以对应做优化处理。

开启gzip压缩

安装:npm install compression-webpack-plugin --save-dev

// gzip压缩插件
const CompressionWebpackPlugin = require('compression-webpack-plugin')

chainWebpack: config => {
    // 添加gzip压缩插件
    config.plugins.push(
      new CompressionWebpackPlugin(
        {
          filename: info => {
            return `${info.path}.gz${info.query}`
          },
          algorithm: 'gzip',
          threshold: 5120, // 只有大小大于该值的资源会被处理 10240
          // test: new RegExp('\\.(' + ['js'].join('|') + ')$'),
          test: /\.js$|\.html$|\.json$|\.css/,
          minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
          deleteOriginalAssets: false // 删除原文件
        }
      )
    )
}

开启gzip压缩,需要配置nginx,打开nginx.config文件,写入以下(在http块内或者在单个server块里添加)

#开启gzip
gzip on;#低于1kb的资源不压缩 
gzip_min_length 1k;#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多
gzip_comp_level 9;#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable "MSIE [1-6]\."; 
#是否添加“Vary: Accept-Encoding”响应头
gzip_vary on;

开启gzip压缩,服务器为tomcat,修改server.xml文件

<Connector 
    port="8080" 
    protocol="HTTP/1.1"
    connectionTimeout="20000"
 
    compression="on"   
    compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,application/javascript"
    useSendfile="false"/>
// compression="on" 打开压缩功能 
// compressableMimeType="text/html,text/xml" 压缩类型
// useSendfile="false" 设置该属性将会压缩所有文件,不限阙值,不然可能按照阙值部分压缩

启用gzip压缩打包之后,会变成下面这样,自动生成gz包。目前大部分主流浏览器客户端都是支持gzip的,就算小部分非主流浏览器不支持也不用担心,不支持gzip格式文件的会默认访问源文件的,所以不要配置清除源文件。


配置好之后,打开浏览器访问线上,F12查看控制台,如果该文件资源的响应头里显示有Content-Encoding: gzip,表示浏览器支持并且启用了Gzip压缩的资源

公共代码抽离

  1. 代码抽离前 默认只生成两个文件,且vendors文件特别大,因为里面包含了我们所有的依赖包资源

  2. 代码抽离后 它会根据定义的模块规则进行代码抽离,可以看出抽离后的文件明显体积变小

  3. 执行抽离模块chainWebpack: config => { 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() config .optimization.splitChunks({ chunks: 'all', cacheGroups: { libs: { name: 'chunk-libs', test: /[\\/]node_modules[\\/]/, priority: 10, chunks: 'initial' // 当为initial时,只能分离初始化的模块,异步的模块无法分离 }, elementUI: { name: 'chunk-elementUI', // 将 elementUI 拆分为单个包 priority: 20, // 权重需要大于 libs 和 app 否则会被打包到 libs 或 app 中 test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // 匹配文件 }, echarts: { name: 'chunk-echarts', // 将 echarts 拆分为单个包 priority: 20, test: /[\\/]node_modules[\\/]_?echarts(.*)/ }, commons: { name: 'chunk-commons', test: resolve('src/components'), // 可以自定你的规则 minChunks: 3, // 当某个模块满足minChunks引用次数时,才会被打包。 priority: 5, reuseExistingChunk: true // 默认为false,关闭表示拆分出复用部分的模块,给双方引用 } } }); // 最小化代码 config.optimization.minimize(true); // 如果我们要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: 'single',否则还会遇到这里所述的麻烦。 // https://bundlers.tooling.report/code-splitting/multi-entry/ // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk config.optimization.runtimeChunk('single') } );}

  4. 配置解析chunks:可用值为“all”、“async”、“initial”,默认值是“async”

             all:拆分同步和异步文件
    
             async:只对异步文件作处理
    
             initial:只对入口文件进行拆分,不去处理异步文件里的模块
    

** minSize**:控制最小包的大小,大于这个值才会去拆分,
如果拆分的公共模块小于这个大小,那就复制成多份,
直接打包到引用该模块的包里

** minChunks**:模块的重复调用次数大于等于minChunks值时,就会满足这项拆包条件,
但只看入口模块导入的,不看动态加载模块中导入的(import(‘…’)),
即使设置的chunks为“all”。

** maxInitialRequests**:入口文件最大请求的文件数量(import等方式)

            入口文件本身算一个请求

            入口文件动态加载的模块不算在内

            通过runtimeChunk拆分出来的runtime文件不算在内

            只算js,css不算在内

            如果同时有两个模块满足cacheGroups的拆分规则,
             但maxInitialRequests只允许再拆分一个,那么会拆出体积

            更大的那个模块。

** maxAsyncRequests**:用于限制异步模块内部的并行最大请求数

            import文件本身算一个请求

            只算js、css不算在内

            如果同时有两个模块满足cacheGroups的规则需要拆分,
             但maxAsyncRequests只允许拆分一个时,那么会拆出体积更大的那个模块。

** name**:主要用于分离chunks后的名字,可以是字符串或者函数,
相同name会合并成一个chunk

** splitChunks.cacheGroups**

            可以继承或者重写splitChunks对象下的属性,但是test、priority和reuseExistingChunk
             只能配置在cacheGroup对象中

            每个添加的对象都有默认配置,如果想禁用此配置,可以将其设置为false

CDN替换依赖包引入

项目打包时会根据依赖关系自动打包压缩依赖文件,当依赖文件过大,会导致首屏加载变慢

使用CDN: 简单来说就是可以使用CDN引入依赖库,减少依赖包体积,释放服务器压力, 它由距离最近的缓存服务器直接响应,提高加载速度

进一步考虑:当我们在开发环境下,使用CDN引入会比我们直接引入依赖要慢,所以配置CDN需要只在生产环境

配置步骤:

// 环境区分主要为开发环境与其他环境(其他:生产,uat,测试等等)
const isNotDevelopMentEnv = process.env.NODE_ENV !== 'development'
const cdnData = {
    css: [
        'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css'
    ],
    js: [
        'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
        'https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
        'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.0/vuex.min.js',
        'https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.6/vue-router.min.js',
        'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
        'https://cdn.bootcdn.net/ajax/libs/jquery/1.12.1/jquery.min.js',
        'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/vee-validate.min.js',
        'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/locale/zh_CN.js'
    ],
    /** 
    * 属性名称 vue, 表示遇到 import xxx from 'vue' 
    * 这类引入 'vue'的,不去 node_modules 中找,而是去找全局变量 Vue
    * 其他的为VueRouter、Vuex、axios、ELEMENT、echarts,注意全局变量是一个确定的值,不能修改为其他值,修改为其他大小写或者其他值会报错
    */
    externals: {
        'vue': 'Vue',
        'vuex': 'Vuex',
        'vue-router': 'VueRouter',
        'element-ui': 'ELEMENT',
        'vuex': 'Vuex',
        'axios': 'axios',
        'vee-validate': 'VeeValidate',
        'jQuery':"jquery",
        'jquery': 'window.$'
    }
}

// 在configureWebpack中添加externals
configureWebpack: {
    externals: isNotDevelopMentEnv ? cdnData.externals : {}
}

// 在chainWepack中添加如下
if (isNotDevelopMentEnv) {
    config.plugin('html')
    .tap(args => {
        args[0].cdn = cdnData
        return args
    })
}

修改 public/index.html(根据环境,开发环境不使用CDN,生产环境才开放)

<html>
    <head>
        <!-- 样式文件优先加载 -->
        <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
            <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
        <% } %>
    </head>
    <body>
        <div id="app"></div>
        <!-- js加载 -->
        <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
            <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
        <% } %>
    </body>
</html>

采用CDN引入后,不需要删除原有依赖引入,因为在本地还是使用这些依赖进行调试的,打包后因为有CDN所以不会把这些依赖引入所以不用担心,import引入的不需要变更。例如main.js中使用import ElementUI from 'element-ui', 以上的代码已经实现在开发环境会设置不适用CDN,会使用依赖包文件;当发布到生产环境,因为我们已经在vue.config.js的externals中指代了element-ui,所以这个语句也是有效的可以直接使用CDN elementUI

易出错点:
Router is not defined


解决方案: 将Router 改为 'VueRouter'

Uncaught TypeError: Illegal constructor

解决方案:修改externals 中‘'element-ui’的value为:ELEMENT

移除无用文件

安装:npm install useless-files-webpack-plugin --save-dev

// 导入插件
const UselessFile = require('useless-files-webpack-plugin')

 
chainWebpack: config => {
    config.plugin('uselessFile')
    .use(
      new UselessFile({
        root: './src', // 项目目录
        out: './fileList.json', // 输出文件列表
        clean: false, // 是否删除文件,
        exclude: [/node_modules/] // 排除文件列表
      })
    )
}

路由优化

动态路由懒加载,按需加载

单页面应用的首屏加载较慢主要是因为单页面应用在首屏时,无论是否需要都会加载所有的模块,可通过按需加载、路由懒加载来优化。

  • 动态加载,通过import来实现路由的动态加载,这个时候对于路由的加载是动态的,用到再加载。{、 path:"/home", name:"home", component: () => import(/* webpackChunkName: "home" */ "@/views/home.vue"))}
  • 按需加载,通过对路由文件的配置,来对相关模块划分区间,如登录界面可以和首页、主页面划分一块,在进入首屏时,只对首屏所在的区块进行加载。通过require.ensure()来将多个相同类的组件打包成一个文件。如示例代码,打包时,将两个组件打包成一个js文件,文件名为home{ path: '/login', //path路径 name: 'login', //组件名 component: r => require.ensure([], () => r(require('../components/login')), 'home') //good类型的组件},{ path: '/home', //path路径 name: 'home', //组件名 component: r => require.ensure([], () => r(require('../components/home')), 'home') //good类型的组件}

动态添加路由

通常路由权限实现可以通过路由守卫 router.beforeEach,配合使用 **router.addRoutes **动态添加路由

import router from './router'
import store from './store'

router.beforeEach(async(to, from, next) => {
    const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 判断用户是否通过 getInfo 获取了自己的角色权限
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // 获取用户角色
          const { roles } = await store.dispatch('user/getInfo')

          // 获取用户具有权限的路由
          const accessRoutes = await store.dispatch('permission/getUserMenus', roles)
          // 动态添加可访问的路由
          router.addRoutes(accessRoutes)

          // 确保addRoutes完整的hack方法
          // 设置 replace: true,因此导航不会留下历史记录
          next({ ...to, replace: true })
        } catch (error) {
          // 移除token,进入登录页面重新登录
          await store.dispatch('user/resetToken')
          console.error(error)
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  }
})

本文转载自: https://blog.csdn.net/qq_32998753/article/details/129484922
版权归原作者 菜下饭 所有, 如有侵权,请联系我们删除。

“Vue首屏加载优化”的评论:

还没有评论