页面加载性能是一个老生常谈的问题,但是却异常重要,尤其在访问量大的商业软件中。但是有很多开发者在开发过程中压根就没有考虑过这个问题。大家在开发业务代码的过程中,也就忽略了这个增加工作量,也不会带来什么直观的工作内容。


写在前面,这里以vue框架为例,基于vue-cli3的开发方式

首先,使用webpack分析工具,查看当前项目的依赖,分析依赖及打包情况,对症下药

安装插件

npm i webpack-bundle-analyzer -D

在vue.config.js中,添加如下配置:

chainWebpack: (config)=>{
    /* 添加分析工具*/
    if (process.env.NODE_ENV === 'production') {
      if (process.env.npm_config_report) {
        config
          .plugin('webpack-bundle-analyzer')
          .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
          .end()
        config.plugins.delete('prefetch')
      }
    }

    ...
  }

执行命令

npm run build --report

执行成功后,浏览器会打开一个窗口,显示当前依赖的大小及各打包文件情况

结合自己的项目情况,分析依赖的引入及打包情况,有以下几点优化方式

第一,路由懒加载。

查看打包目录中,js文件夹下的chunk-哈希值的文件为采用懒加载形式时生成的文件,一个路由会生成一个文件。

const home= () => import('@/pages/home/index.vue')

第二,使用CDN引入第三方依赖。

比如,直接引入ehcarts会发现占打包文件较大的空间,如果项目没有特殊要求,可以采用CDN的方式引入;其他诸如axios、vue、lodash等都可以采用这种方式。

  • 在index.html中引入CDN资源
...
  <body>
    <div id="app">
    </div>
    <!-- built files will be auto injected -->
    <script src="//cdn.bootcss.com/echarts/4.2.1/echarts.simple.min.js"></script>
  </body>
  ...
  • 修改vue.config.js配置文件
module.exports = {
    configureWebpack: {
      externals: {
        'echarts': 'echarts' // 配置使用CDN
      }
    }
   }

externals中的key是用于import,value表示的在全局中访问到该对象,就是window.echarts

在vue中使用echarts的时候无需 import echarts,可直接使用

第三,按需加载第三方类库

比如,项目中使用了 lodash 库,如果不是大量使用里面的方法的话,可以这样引入

import _cloneDeep from 'lodash/difference' // 或者 const _cloneDeep = require('lodash/difference')
const o = _cloneDeep ({a: 1, b: 2}) 

也可以借助第三方插件的形式,lodash-webpack-plugin和babel-plugin-lodash。

在使用中还是采用原有的 import _ from 'lodash'方式,只是借助插件,在打包时webpack会根据使用的方法按需打包

先安装依赖

npm install lodash-webpack-plugin babel-plugin-lodash -D

上述插件可能部分已经存在于项目中,可以根据实际删除

接着修改 vue.config.js


const LodashModuleReplacementPlugin = require("lodash-webpack-plugin");

module.exports = {
  configureWebpack: config => {
     if (process.env.NODE_ENV === 'production') {
        return{
           plugins: [
              new LodashModuleReplacementPlugin(), //优化lodash
           ]
        }
     }
  }
};

附:使用 IgnorePlugin 插件优化 moment.js


const webpack = require('webpack');

module.exports = {
  configureWebpack: config => {
     if (process.env.NODE_ENV === 'production') {
        return{
           plugins: [
              new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // 忽略/moment/locale下的所有文件
           ]
        }
     }
  }
};

按需引入 element-ui,参见官方文档即可,其他组件库类似

注:如果是按需一次性在main.js中引入,虽然比全部引入要小一些,但是也会一定程度上影响首次加载,这个看项目而行吧。

按需引入后element-ui小了很多,不过看到文章开头的图上显眼的 table.js后想到, table组件只有后台管理页面用到了,不需要全局注册,所以我们删除 main.js中 Table和 TablColumn的引用,并在后台组件中局部注册。

这里处理的思路就是,将按需引入处理到极致。如果是对首屏要求很高,可以采用这种方式,哪里用到哪里才引用,这其实也是平时开发中的一种良好习惯。

chunk.venders.js 。如果是文件为第三方依赖的打包后文件,在做完这些优化之后,会发现这个文件有显现的减小。

第四,打包时去掉sourceMap文件

修改 vue.config.js 配置

module.exports = {
  productionSourceMap: false
}

第五,将静态资源使用cdn加载

将项目中的静态资源js css等放在oss服务器或者其他地方,减小服务器压力

第六,开启 gzip压缩

我在项目中启用压缩后,文件大小减少了70%以上,优化效果十分明显。

下图是在简单做了部分优化之后的加载过程(优化开始时忘了截图),耗时8s以上。服务器端配置以 nginx 为例

如果 Nginx 服务器开启 gzip,会将静态资源在服务端进行压缩,压缩包传输给浏览器后,浏览器再进行解压使用,这大大提高了网络传输的效率,尤其对 js,css 这类文本的压缩,效果很明显。

客户端

安装依赖

npm i -D compression-webpack-plugin

修改 vue.config.js 配置

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

module.exports = {
 ...
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new CompressionPlugin({
            test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png/, // 需要压缩的文件类型
            threshold: 10240, // 归档需要进行压缩的文件大小最小值,这里是10K以上的进行压缩
            deleteOriginalAssets: false // 是否删除原文件
          })
        ]
      }
    }
  }
}

打包后,查看js文件

可以看到所有文件都被压缩了三分之二以上

在服务器我们也要做相应的配置

# 开启|关闭 gzip。
gzip on|off;
# 文件大于指定 size 才压缩,以 kb 为单位。
gzip_min_length 10;
# 压缩级别,1-9,值越大压缩比越大,但更加占用 CPU,且压缩效率越来越低。
gzip_comp_level 2;
# 压缩的文件类型。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;
# 开启后如果能找到 .gz 文件,直接返回该文件,不会启用服务端压缩。
gzip_static on|off
# 是否添加响应头 Vary: Accept-Encoding 建议开启。
gzip_vary on;
# 请求压缩的缓冲区数量和大小,以 4k 为单位,32 为倍数。
gzip_buffers 32 4K;

注:遇到服务端开启gzip后,并没有生效的问题,发现是nginx配置压缩文件类型时 application/x-javascript,如果是这样的写法则并不会生效。

JavaScript的MIME类型通常为“application/x-javascript”, 非IE的浏览器认“application/javascript”,

所以在上述配置中 application/javascript 和 application/x-javascript 并用,可以解决该问题。

然后重启nginx服务

systemctl restart nginx.service

当在请求中出现如下标识,即开启成功

再对比一下资源加载时间

前者为启用压缩前,后者为压缩后,时间从8.33s减少到了2.44s,效率提高了70%以上

第七,冗余代码

打包文件 app.哈希.js 中为所有vue文件打包的集合。

基于此,把项目中的冗余代码,注释的多余代码删除一通后,你会发现文件会变小。

可能人就是这样,在项目中觉得多几行css 多几个标签觉得不会对页面产生什么影响,但是如果做一通优化之后看到了‘数字性’的减少,才会思考编写高性能代码对加载性能的影响。

第八,浏览器缓存

浏览器缓存可以分为强缓存和协商缓存,根据实际应用场景来选择缓存方式或者结合使用。一般来讲一些基本不会变化的静态资源文件可以设置强缓存,更新频繁的文件不要设置缓存。而启用缓存的好处在于,在某个时间段内可以减少发送请求的数量,从而使页面响应更快,也就有更好的页面体验。

基本原理:浏览器缓存分强缓存和协商缓存,他们是将网络资源存储在本地,等待下次请求该资源时,如果命中就不需要到服务器重新请求该资源,直接在本地读取该资源。

  • 强缓存:在web服务器返回的响应中添加Expires和Cache-Control Header
  • 协商缓存:通过【Last-Modified, If-Modified-Since】和【ETag, If-None-Match】两对Header分别管理

关于缓存的详细介绍,推荐一篇文章,时空隧道

第九,图片压缩

图片压缩是常用的手法,因为设备像素点的关系,UI给予的图片一般都是 x2,x4的,所以压缩就非常有必要。

第十,spilt chunks

在没配置任何东西的情况下,webpack 4 就智能的帮你做了代码分包。入口文件依赖的文件都被打包进了main.js,那些大于 30kb 的第三方包,如:echarts、xlsx等都被单独打包成了一个个独立 bundle。

其它被我们设置了异步加载的页面或者组件变成了一个个chunk,也就是被打包成独立的bundle。

它实际分包的策略是这样的:

  • 新的 chunk 是否被共享或者是来自 node_modules 的模块
  • 新的 chunk 体积在压缩之前是否大于 30kb
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始加载时的并发请求数量小于等于 3 个

在实际项目中可以自行配置分包策略,示例如下:

splitChunks({
  cacheGroups: {
    vendors: {
      name: `chunk-vendors`,
      test: /[\\/]node_modules[\\/]/,
      priority: -10,
      chunks: 'initial',
    },
    dll: {
      name: `chunk-dll`,
      test: /[\\/]bizcharts|[\\/]\@antv[\\/]data-set/,
      priority: 15,
      chunks: 'all',
      reuseExistingChunk: true
    },
    common: {
      name: `chunk-common`,
      minChunks: 2,
      priority: -20,
      chunks: 'all',
      reuseExistingChunk: true
    },
    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
    },
  }
})

也可以通过按需加载的形式进行分包,使得我们的包分散开,提升加载性能。

推荐一篇文章:webpack按需加载

第十,webpack配置

另外webpack也有很多策略可以减少包的大小或者分包缓存的方式,结合浏览器缓存策略从而在更新包或用户访问时减少服务请求,体验优化

    // 指定环境变量
    // 当NODE_ENV未设置production为时,Vue.js会进行其他检查并显示警告,一些库基于此变量的行为会有所不同。
    // 这种检查和警告通常在生产中是不必要的,但是它们保留在代码中并增加了库的大小
    // 在webpack 4中,通过添加以下optimization.nodeEnv: 'production' 选项将其删除
    config.optimization.nodeEnv('production')
    // 压缩代码
    config.optimization.minimize(true)

    // it can improve the speed of the first screen, it is recommended to turn on preload
    // it can improve the speed of the first screen, it is recommended to turn on preload
    config.plugin('preload').tap(() => [{
      rel: 'preload',
      // to ignore runtime.js
      // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
      fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
      include: 'initial'
    }])
    // when there are many pages, it will cause too many meaningless requests
    config.plugins.delete('prefetch')
    // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
    config.optimization.runtimeChunk('single')

webpack 的optimization.runtimeChunk这个配置项的具体作用是优化持久化缓存的。 runtime 指的是 webpack 的运行环境(具体作用就是模块解析, 加载) 和 模块信息清单, 模块信息清单在每次有模块变更(hash 变更)时都会变更, 所以我们想把这部分代码单独打包出来, 配合后端缓存策略, 这样就不会因为某个模块的变更导致包含模块信息的模块(通常会被包含在最后一个 bundle 中)缓存失效. optimization.runtimeChunk 就是告诉 webpack 是否要把这部分单独打包出来

假设一个使用动态导入的情况,在`app.js`动态导入`component.js`

const app = () =>import('./component').then();

build之后,产生3个包。

  • `0.01e47fe5.js`
  • `main.xxx.js`
  • `runtime.xxx.js`

其中`runtime`,用于管理被分出来的包。下面就是一个`runtimeChunk`的截图,可以看到`chunkId`这些东西。

...
function jsonpScriptSrc(chunkId) {
/******/         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"01e47fe5"}[chunkId] + ".bundle.js"
/******/     }
...

采用这种分包策略,当内容改变时可以将文件变化降到一个小文件的粒度。从而不必要更新整个文件,对于访问页面和更新文件都比较友好。更多官方策略讲述,点击这里

推荐一篇webpack性能优化的文章,查看

webpack压缩代码的文章,查看

好啦,文章提到的优化方式基本就是这些,当然优化也不至于此,还有网络加载优化、页面渲染优化(动画、重排、重绘等)、浏览器文件缓存等等。如果有更好的优化方式欢迎评论。

写在最后,页面优化本身是一件很抽象的工作,但是我们却可以通过平时的编码规范来促成更可靠的页面。优化的过程也是一个见仁见智的过程,要结合实际项目实际分析。优化的过程也会引发我们对于编码时的一些思考,原来这样写对页面加载会更友好,不知不觉中也能促进编写高可用的能力。

 

Logo

前往低代码交流专区

更多推荐