使用Webpack构建生产资源时Deferred脚本仍被WebPageTest标记为渲染阻塞的原因排查
为什么带defer的Webpack分割脚本仍被WebPageTest标记为渲染阻塞?
嘿,这个问题我碰到过好几次了,咱们一步步拆解清楚:
先明确defer和async的核心差异
首先得把这两个属性的执行逻辑掰明白:
- defer:脚本会并行下载,但会严格按照在HTML中的顺序执行,而且执行时机是DOM解析完成后、
DOMContentLoaded事件触发之前。它不会阻塞DOM解析,但执行阶段会占用主线程。 - async:脚本同样并行下载,但下载完成后会立即抢占主线程执行,不管DOM状态,也不管其他async脚本的顺序。
WebPageTest判定"渲染阻塞"的逻辑
WebPageTest标记渲染阻塞资源,看的不是脚本带不带defer/async,而是这个资源的处理(下载+执行)是否推迟了浏览器的首次绘制(First Paint)或首次内容绘制(First Contentful Paint)。它是基于实际主线程的阻塞情况来判定,而非单纯看脚本属性。
你的场景里defer出问题的原因
结合Webpack代码分割的情况,问题出在这几点:
- 执行时机刚好撞上首次渲染:Webpack分割后的chunk(比如vendor、common这类公共脚本)通常体积不小,下载完成的时间可能刚好在浏览器准备首次渲染的节点上。defer脚本会在DOM解析完立刻执行,这时候主线程被脚本执行占用,浏览器没法进行渲染操作,自然就被判定为阻塞了。
- 顺序执行放大了阻塞时间:defer是按顺序执行的,如果有多个分割后的chunk,前面的脚本执行耗时久,会直接阻塞后面所有defer脚本的执行,这段时间里主线程完全被占用,渲染只能一直等。
为什么换成async就解决了?
async的"下载完就执行"特性,反而帮你避开了首次渲染的关键节点:
- 如果某个chunk下载完成时,浏览器已经完成了首次渲染,那它的执行就不会影响页面的初始绘制;
- 就算下载完成得早,async脚本抢占主线程的时机也不一定卡在渲染的关键点上,而且各个async脚本独立执行,不会互相阻塞,总阻塞时间会更分散,不容易被WebPageTest判定为"渲染阻塞"。
额外提醒
不是说defer就一定会导致渲染阻塞,关键看脚本的执行时机和时长。如果你的分割chunk体积很小,或者下载完成时间晚于首次渲染,defer也不会被标记为阻塞。你可以在WebPageTest的瀑布流和主线程分析里,看看这些脚本的执行时间点和首次绘制的时间点是否重叠,就能更直观地确认问题啦。
内容的提问来源于stack exchange,提问作者Rabin Poudyal
相关产品推荐
相关产品推荐

