1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Vue首屏加载优化

Vue首屏加载优化

时间:2020-12-08 13:33:04

相关推荐

Vue首屏加载优化

目录

打包优化

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

可视化资源分析工具

开启gzip压缩

公共代码抽离

代码抽离前

代码抽离后

执行抽离模块

配置解析

CDN替换依赖包引入

移除无用文件

路由优化

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

动态添加路由

打包优化

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

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

先简单了解一下preloadprefetch

preloadprefetch都是一种资源预加载机制;

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块里添加)

#开启gzipgzip 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压缩的资源

公共代码抽离

代码抽离前

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

代码抽离后

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

执行抽离模块

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:// /configuration/optimization/#optimizationruntimechunkconfig.optimization.runtimeChunk('single')});}

配置解析

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: ['/ajax/libs/element-ui/2.13.0/theme-chalk/index.css'],js: ['/ajax/libs/vue/2.6.10/vue.min.js','/ajax/libs/axios/0.19.2/axios.min.js','/ajax/libs/vuex/3.1.0/vuex.min.js','/ajax/libs/vue-router/3.0.6/vue-router.min.js','/ajax/libs/element-ui/2.13.0/index.js','/ajax/libs/jquery/1.12.1/jquery.min.js','/ajax/libs/vee-validate/2.0.0-rc.21/vee-validate.min.js','/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中添加externalsconfigureWebpack: {externals: isNotDevelopMentEnv ? cdnData.externals : {}}// 在chainWepack中添加如下if (isNotDevelopMentEnv) {config.plugin('html').tap(args => {args[0].cdn = cdnDatareturn 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 > 0if (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}`)}}}}})

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。