当前位置:   article > 正文

Vue首屏加载优化_vite vue首屏优化

vite vue首屏优化

目录

打包优化

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

可视化资源分析工具

开启gzip压缩

公共代码抽离

代码抽离前

代码抽离后

执行抽离模块

配置解析

CDN替换依赖包引入

移除无用文件

路由优化

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

动态添加路由


打包优化

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

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

先简单了解一下 preload prefetch

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

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

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

preload 的优先级高于 prefetch

配置文件:vue.config.js

  1. chainWebpack: config => {
  2. // 移除 preload(预载) 插件
  3. config.plugins.delete('preload')
  4. // 移除 prefetch(预取) 插件
  5. config.plugins.delete('prefetch')
  6. }

可视化资源分析工具

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

  1. const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
  2. chainWebpack: config => {
  3. // 添加资源可视化工具
  4. config.plugins.push(
  5. new BundleAnalyzerPlugin()
  6. )
  7. }

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

开启gzip压缩

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

  1. // gzip压缩插件
  2. const CompressionWebpackPlugin = require('compression-webpack-plugin')
  3. chainWebpack: config => {
  4. // 添加gzip压缩插件
  5. config.plugins.push(
  6. new CompressionWebpackPlugin(
  7. {
  8. filename: info => {
  9. return `${info.path}.gz${info.query}`
  10. },
  11. algorithm: 'gzip',
  12. threshold: 5120, // 只有大小大于该值的资源会被处理 10240
  13. // test: new RegExp('\\.(' + ['js'].join('|') + ')$'),
  14. test: /\.js$|\.html$|\.json$|\.css/,
  15. minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
  16. deleteOriginalAssets: false // 删除原文件
  17. }
  18. )
  19. )
  20. }

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

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

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

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

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


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


 

公共代码抽离

  1. 代码抽离前

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

     
  2. 代码抽离后

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

     
  3. 执行抽离模块

    1. chainWebpack: config => {
    2. config
    3. .when(process.env.NODE_ENV !== 'development',
    4. config => {
    5. config
    6. .plugin('ScriptExtHtmlWebpackPlugin')
    7. .after('html')
    8. .use('script-ext-html-webpack-plugin', [{
    9. // `runtime` must same as runtimeChunk name. default is `runtime`
    10. inline: /runtime\..*\.js$/
    11. }])
    12. .end()
    13. config
    14. .optimization.splitChunks({
    15. chunks: 'all',
    16. cacheGroups: {
    17. libs: {
    18. name: 'chunk-libs',
    19. test: /[\\/]node_modules[\\/]/,
    20. priority: 10,
    21. chunks: 'initial' // 当为initial时,只能分离初始化的模块,异步的模块无法分离
    22. },
    23. elementUI: {
    24. name: 'chunk-elementUI', // 将 elementUI 拆分为单个包
    25. priority: 20, // 权重需要大于 libs 和 app 否则会被打包到 libs 或 app 中
    26. test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // 匹配文件
    27. },
    28. echarts: {
    29. name: 'chunk-echarts', // 将 echarts 拆分为单个包
    30. priority: 20,
    31. test: /[\\/]node_modules[\\/]_?echarts(.*)/
    32. },
    33. commons: {
    34. name: 'chunk-commons',
    35. test: resolve('src/components'), // 可以自定你的规则
    36. minChunks: 3, // 当某个模块满足minChunks引用次数时,才会被打包。
    37. priority: 5,
    38. reuseExistingChunk: true // 默认为false,关闭表示拆分出复用部分的模块,给双方引用
    39. }
    40. }
    41. });
    42. // 最小化代码
    43. config.optimization.minimize(true);
    44. // 如果我们要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: 'single',否则还会遇到这里所述的麻烦。
    45. // https://bundlers.tooling.report/code-splitting/multi-entry/
    46. // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
    47. config.optimization.runtimeChunk('single')
    48. }
    49. );
    50. }

  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需要只在生产环境





 

配置步骤: 

  1. // 环境区分主要为开发环境与其他环境(其他:生产,uat,测试等等)
  2. const isNotDevelopMentEnv = process.env.NODE_ENV !== 'development'
  3. const cdnData = {
  4. css: [
  5. 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css'
  6. ],
  7. js: [
  8. 'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
  9. 'https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
  10. 'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.0/vuex.min.js',
  11. 'https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.6/vue-router.min.js',
  12. 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
  13. 'https://cdn.bootcdn.net/ajax/libs/jquery/1.12.1/jquery.min.js',
  14. 'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/vee-validate.min.js',
  15. 'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/locale/zh_CN.js'
  16. ],
  17. /**
  18. * 属性名称 vue, 表示遇到 import xxx from 'vue'
  19. * 这类引入 'vue'的,不去 node_modules 中找,而是去找全局变量 Vue
  20. * 其他的为VueRouter、Vuex、axios、ELEMENT、echarts,注意全局变量是一个确定的值,不能修改为其他值,修改为其他大小写或者其他值会报错
  21. */
  22. externals: {
  23. 'vue': 'Vue',
  24. 'vuex': 'Vuex',
  25. 'vue-router': 'VueRouter',
  26. 'element-ui': 'ELEMENT',
  27. 'vuex': 'Vuex',
  28. 'axios': 'axios',
  29. 'vee-validate': 'VeeValidate',
  30. 'jQuery':"jquery",
  31. 'jquery': 'window.$'
  32. }
  33. }
  34. // 在configureWebpack中添加externals
  35. configureWebpack: {
  36. externals: isNotDevelopMentEnv ? cdnData.externals : {}
  37. }
  38. // 在chainWepack中添加如下
  39. if (isNotDevelopMentEnv) {
  40. config.plugin('html')
  41. .tap(args => {
  42. args[0].cdn = cdnData
  43. return args
  44. })
  45. }

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

  1. <html>
  2. <head>
  3. <!-- 样式文件优先加载 -->
  4. <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
  5. <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
  6. <% } %>
  7. </head>
  8. <body>
  9. <div id="app"></div>
  10. <!-- js加载 -->
  11. <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
  12. <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  13. <% } %>
  14. </body>
  15. </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

  1. // 导入插件
  2. const UselessFile = require('useless-files-webpack-plugin')
  3. chainWebpack: config => {
  4. config.plugin('uselessFile')
  5. .use(
  6. new UselessFile({
  7. root: './src', // 项目目录
  8. out: './fileList.json', // 输出文件列表
  9. clean: false, // 是否删除文件,
  10. exclude: [/node_modules/] // 排除文件列表
  11. })
  12. )
  13. }

路由优化

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

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

  • 动态加载,通过import来实现路由的动态加载,这个时候对于路由的加载是动态的,用到再加载。

    1. {、
    2. path:"/home",
    3. name:"home",
    4. component: () => import(/* webpackChunkName: "home" */ "@/views/home.vue"))
    5. }
  • 按需加载,通过对路由文件的配置,来对相关模块划分区间,如登录界面可以和首页、主页面划分一块,在进入首屏时,只对首屏所在的区块进行加载。通过require.ensure()来将多个相同类的组件打包成一个文件。如示例代码,打包时,将两个组件打包成一个js文件,文件名为home

    1. {
    2. path: '/login', //path路径
    3. name: 'login', //组件名
    4. component: r => require.ensure([], () => r(require('../components/login')), 'home') //good类型的组件
    5. },
    6. {
    7. path: '/home', //path路径
    8. name: 'home', //组件名
    9. component: r => require.ensure([], () => r(require('../components/home')), 'home') //good类型的组件
    10. }

动态添加路由

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

  1. import router from './router'
  2. import store from './store'
  3. router.beforeEach(async(to, from, next) => {
  4. const hasToken = getToken()
  5. if (hasToken) {
  6. if (to.path === '/login') {
  7. next({ path: '/' })
  8. } else {
  9. // 判断用户是否通过 getInfo 获取了自己的角色权限
  10. const hasRoles = store.getters.roles && store.getters.roles.length > 0
  11. if (hasRoles) {
  12. next()
  13. } else {
  14. try {
  15. // 获取用户角色
  16. const { roles } = await store.dispatch('user/getInfo')
  17. // 获取用户具有权限的路由
  18. const accessRoutes = await store.dispatch('permission/getUserMenus', roles)
  19. // 动态添加可访问的路由
  20. router.addRoutes(accessRoutes)
  21. // 确保addRoutes完整的hack方法
  22. // 设置 replace: true,因此导航不会留下历史记录
  23. next({ ...to, replace: true })
  24. } catch (error) {
  25. // 移除token,进入登录页面重新登录
  26. await store.dispatch('user/resetToken')
  27. console.error(error)
  28. next(`/login?redirect=${to.path}`)
  29. }
  30. }
  31. }
  32. }
  33. })

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/106229?site
推荐阅读
相关标签
  

闽ICP备14008679号