本文主要讲述,关于 Chrome Content Download 时间过长问题调查经过,及相关优化方案
chunk-136cc8c0.js
是上图地图 geojson,587 kB 用了 1.01s,作为系统首页,会导致中间地图(视觉中心)出现长时间的空白,对于用户体验来说,极其不友好。
怀疑一:是不是网络环境不好导致?
在开发环境下运行(本地)也会出现类似情况,且更重要的是 chunk-7182b1fa.js
933kB 才用了 35ms。因此可以排除网络坏境导致。
怀疑二:缓存导致?
某些 chunk 请求用时短,某些请求用时长;是不是缓存起的作用。
- 缓存(304)的确可以加快资源的请求
- 但对于
chunk-136cc8c0.js
和其他请求依然不是一个量级的
怀疑三:浏览器并行加载个数有限,导致排队时间过长?
Chrome 浏览器默认并行加载个数为 6 个,过多的请求的确会引起资源请求排队。
通过 Chrome => Network => Timing 查看:
- Queueing(排队): 浏览器会在以下情况下对请求进行队列处理:
- 有更高优先级的请求。(浏览器 Network 中可以通过 Priority 查看)
- 已经建立了 6 个 TCP 连接(HTTP/1.0和HTTP/1.1协议下的限制)
- The browser is briefly allocating space in the disk cache
- Stalled(阻塞): 请求可能会因为 Queueing 中描述的任何原因而停止
- Request sent(发送请求): 发送HTTP请求的时间(从第一个bit到最后一个bit)
- Waiting (TTFB)(等待响应): 浏览器正在等待响应的第一个字节。TTFB表示时间到第一个字节。这个时间包括1个延迟往返和服务器准备响应所花费的时间
- Content Download(下载): 下载HTTP响应的时间(包含头部和响应体)
整体 Queueing(排队)并没有消耗多少时间(1.28ms),而是 Content Download 消耗了大量时间(1.01s)。所以断定并不是排队时间过长导致该问题。
更详细的,可以查看 Chrome 开发文档:这里
怀疑四:Cpu 被耗尽,浏览器”停顿“?
查了相关的帖子,这篇 最后的回答「Frontend app is doing too much work」给了另一个方向。
是不是初始阶段进行大量的 JS 工作,导致 cpu 被耗尽了,使得浏览器处于了”停顿“状态。
通过 Chrome => Performance => Start recording:
chunk-136cc8c0.js
加载前后 cpu 使用率为 100%,导致资源请求和后续等待执行的时间都很长。初步断定,和 cpu 占用率用很大关系。
问题有了方向,后续就是针对问题出各种优化方案
优化方式
通用:开启缓存
Nginx 开启静态资源(css、js)缓存配置:
在 server 中的 location / 的配置中增加如下配置
代码语言:javascript复制location / {
if ($request_filename ~* ^.*?.(js|css)$){
# 缓存时效性可以根据项目诉求,自行设定
expires 30d;
}
root xxx/dist;
index index.html;
....
}
通用:开启 Gzip
对静态资源进行 gzip,能起到巨大的效果(实测:40M的内容,gzip完不足10M)。
第1步:按照依赖
代码语言:javascript复制$ npm install compression-webpack-plugin --save-dev
第2步:vue.config.js
开启 gzip 压缩
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [new CompressionPlugin()],
};
详细配置可查看 compression-webpack-plugin
第3步:在 Nginx 配置文件中的 http 或者 server 中增加配置
代码语言:javascript复制server {
gzip on;
gzip_vary on;
gzip_types application/javascript text/css;
location / {
...
}
}
由于是 geojson,压缩空间有限但也减少了一半,对于 http 请求速度会有很大的提高(对于纯 css、js代码,效果会更加明显)。
可选:动态延迟加载
页面中存在好多 Dialog 等下钻需要的组件,可以通过 webpack import()
动态加载,避免进入页面全部发起请求。
import()
可以动态的加载模块。调用 import
的之处,被视为分割点,被请求的模块和它引用的所有子模块,会分割到一个单独的 chunk 中。
原形式:
代码语言:javascript复制import detailEventDialog from './components/detail-event.vue'
import detailVulnDialog from './components/detail-vuln.vue'
import detailCompDialog from './components/detail-comp.vue'
import detailWeakDialog from './components/detail-weak.vue'
import detailWeakTabDialog from './components/detail-weak-tab.vue'
import detailAssetDialog from './components/detail-asset.vue'
import detailUserDialog from './components/detail-user.vue'
export default {
components: {
detailEventDialog,
detailVulnDialog,
detailCompDialog,
detailWeakDialog,
detailWeakTabDialog,
detailAssetDialog,
detailUserDialog
}
}
修改后:
代码语言:javascript复制export default {
components: {
detailEventDialog: () => import('./components/detail-event.vue'),
detailVulnDialog: () => import('./components/detail-vuln.vue'),
detailCompDialog: () => import('./components/detail-comp.vue'),
detailWeakDialog: () => import('./components/detail-weak.vue'),
detailWeakTabDialog: () => import('./components/detail-weak-tab.vue'),
detailAssetDialog: () => import('./components/detail-asset.vue'),
detailUserDialog: () => import('./components/detail-user.vue')
}
}
import()
必须至少包含一些关于模块的路径信息。打包可以限定于一个特定的目录或文件集,以便于在使用动态表达式时 - 包括可能在 import()
调用中请求的每个模块。例如, import(./locale/${language}.json)
会把 .locale
目录中的每个 .json
文件打包到新的 chunk 中。在运行时,计算完变量 language
后,就可以使用像 english.json
或 german.json
的任何文件。 – webpack import
可选:DOM逐针渲染
vue mixins
代码语言:javascript复制export default function (maxCount = 0) {
return {
data () {
return {
// 展示权重,默认为0
displayPriority: 0,
running: true
}
},
mounted () {
this._startLoop()
},
methods: {
_startLoop () {
// 最大渲染数为0时,停止助阵逐帧渲染
if (maxCount === 0) return
this.running = true
const step = () => {
this.displayPriority
if (this.running && this.displayPriority <= maxCount) {
requestAnimationFrame(step)
}
}
requestAnimationFrame(step)
},
_stopLoop () {
this.running = false
},
delayRender (priority) {
return this.displayPriority >= priority
}
},
activated () {
// 激活状态开始渲染
!this.running && this._startLoop()
},
deactivated () {
// 停止渲染监听
this._stopLoop()
},
beforeDestroy () {
// 停止渲染监听
this._stopLoop()
}
}
}
使用
代码语言:javascript复制 export default {
mixins: [delayRenderService(9)]
}
针对业务代码瘦身
针对特定业务需求,对静态资源进行了瘦身(删除了一些无用的第三方、自研组件、及静态资源的引用);然后将 chunk-136cc8c0.js
(geojson) 加载时机提前,获取到资源后,先行渲染底图。
最小化主线程工作
浏览器的渲染器进程将代码转换为用户可以与之交互的网页。默认情况下,渲染器进程的主线程通常处理大部分代码:它解析 HTML 并构建 DOM,解析 CSS 并应用指定的样式,以及解析、评估和执行 JavaScript。主线程还处理用户事件。因此,每当主线程忙于做其他事情时,网页可能无法响应用户交互,从而导致糟糕的体验。
方式 | |
---|---|
Script evaluation | 优化第三方 JavaScript 去抖动输入处理程序 使用网络工作者 |
Style and layout | 减少样式计算的范围和复杂性避免大型、复杂的布局和布局颠簸 |
Rendering | 坚持只使用合成器的属性并管理层数简化油漆复杂性并减少油漆区域 |
Parsing HTML and CSS | 提取关键 CSS缩小 CSS推迟非关键 CSS |
Script parsing and compilation | 通过代码拆分减少 JavaScript 负载删除未使用的代码 |
Garbage collection | 使用以下命令监控网页的总内存使用情况 measureMemory() |