性能优化之script标签的async属性到底能不能在项目中用?有什么收益?
背景
在性能优化中,我们都知道,async属性可以让script标签变得不阻塞HTML解析,defer属性也有类似的功能,但实际defer是会阻塞script解析的(用defer的话,多个script会按顺序执行,而async执行是无序的,谁下载的快执行谁),使用async属性理论上讲,是要比defer更快的。
- 不熟悉async、defer作用的可以看我这篇:juejin.cn/post/694391…
用async属性有什么问题?
感觉不太安全,因为async会让多个script标签执行是无序的
实际测试async,看看是否是危险的
随便用一个脚手架起项目(vue或react都行),build之后,进入dist目录下,用http-server起服务,访问html如下(手动改成async)
<!doctype html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="/favicon.ico">
<title>hello-world</title>
<script async src="/js/chunk-vendors.f5ba3c0d.js"></script>
<script async src="/js/app.382b3f67.js"></script>
<link href="/css/app.2cf79ad6.css" rel="stylesheet">
</head>
<body>
<div id="app"></div>
</body>
</html>
用弱网访问,看下的图,很明显 app.382b3f67.js 要先下载完(因为size小)
确认是否是 app.382b3f67.js 先执行, 手动往2个js里面埋入log代码,结果和预期一样,app.382b3f67.js先执行
确认 chunk-vendors.f5ba3c0d.js 里面的代码是关键依赖,比如是vue或react,理论上讲,应该是要报错的,因为依赖后执行了,但结果并没有报错
结论
在webpack项目内,可以放心对多个script标签使用async属性,webpack已经对产物做好了兼容
webpack是如何实现兼容async的?
分析打包出来的产物,可以探知到webpack是如何实现的
产物1:app.382b3f67.js (简化后的,方便理解)
(function () {
"use strict";
var __webpack_modules__ = ({
4629:
(function (__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) {}),
6949:
(function (module) {})
});
!function () {
// ...
var chunkLoadingGlobal = self["webpackChunkhello_world"] = self["webpackChunkhello_world"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
// .push 被改写
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
}();
var __webpack_exports__ = __webpack_require__.O(undefined, [998], function () {
return __webpack_require__(4629);
})
__webpack_exports__ = __webpack_require__.O(__webpack_exports__);
})();
console.log('app.382b3f67.js')
产物2:chunk-vendors.f5ba3c0d.js(简化后的,方便理解)
(self["webpackChunkhello_world"] = self["webpackChunkhello_world"] || []).push([[998], {
// ...
}]);
console.log('chunk-vendors.f5ba3c0d.js')
解释:
- app.382b3f67.js里面的关键代码(webpack打包时的入口代码index.js),比如new Vue()或root.render(),被存放到了__webpack_require__(4629)里面(每次打包数字会变,不一定是4629)
- 4629 没有立即被执行,而是先存放好,等依赖[998]准备好,才执行
那app.382b3f67.js怎么知道依赖998已经准备好了呢?
- 依赖[998]就是chunk-vendors.f5ba3c0d.js,998被 .push 到了 self["webpackChunkhello_world"] 里面,这个push不是Array.push,而是被改写过的(我在上面写了注释,具体是哪一行被改写了)
- 改写的作用是:可以通知到 app.382b3f67.js,998依赖已经准备好了
由此可以得到结论:
- 依赖未准备好时,webpack打包时的入口代码index.js不会执行
到这里还没完,由上面的结论可以发现:如果不做特殊处理,我们几乎无法监控到webpack应用的白屏问题
- 因为 如果某个js资源网络问题丢失了,index.js根本就不会执行,意味着上报代码也不会执行。如果js执行报错,那直接进程退出了,上报代码也不会执行(除非try catch了)
由此还可以得到一个结论:
- 如果要监控webpack应用的白屏问题,那么要用额外的script标签加载js来监控,这部分js不走webpack打包的逻辑
另外:附上webpack的版本:webpack@5.76.1
码字不易,点赞鼓励!!
转载自:https://juejin.cn/post/7212263304395685944