1. 背景
个人站点访问性能优化,当前核心问题是js脚本体积过大,耗时占比很高。
因此,想通过代码覆盖率工具,抽取出使用到的代码,丢弃未使用代码,减少文件体积,提升访问速度。
从评估的代码覆盖率来看,着实有极大的优化动力。
2. 实践过程
2.1 使用Coverage工具
2.1.1 获得代码覆盖率并导出
打开chrome开发者工具后,按下 ctrl + shift + p,会弹出一个输入框,
输入coverage,点击后在下方可以看到coverage面板:
点击刷新按钮,开始捕获界面操作。
此时就可以开始界面操作,注意要保证每个界面,每个事件都能覆盖到。
如果可能,最好是做到界面操作自动化,因为每次代码编译后会生成新的文件,每次都要操作一次,人工操作总有可能会漏掉一些界面和操作,以致代码覆盖率不准,如果获取到的used代码有遗漏,则访问界面时会出问题。
注意:一定是要用准备发布的文件来操作,否则操作完之后,重新编译,生成新的文件,那就白操作了,当然如果能保证每次编译只是文件名的hash值不同,而文件内容完全一样(代码没有改动过,只是重新编译)也行。
操作完之后,可以查看代码覆盖率:
其中红色是unused,绿色是used,看起来unused的代码还不少,操作之后可以导出文件,文件是json格式,样例如下:
java
[
{
{
"url": "https://www.kengcoder.com/assets/index-T1fy7zxz.js",
"ranges": [
{
"start": 0,
"end": 208
},
{
"start": 331,
"end": 377
},
]
"text": "(function(){const t=document.createElement(\"link\").relList;...."
},
]
这个导出文件可以用于抽取used的代码,替换体积较大的原始文件。
2.1.2 json文件格式说明
json格式内容是一个数组,每个数组包含一个文件的代码覆盖率信息,分为如下几部分:
序号 | 类型 |
---|---|
url | 文件的url路径 |
ranges | used代码的位置数组,每一个元素包含[start, end],start是起始位置,end是结束位置,所有元素加起来就是所有used的代码 |
text | 每个url指定文件的全量原始代码,可以用来生成新的used代码。 |
2.2 used代码抽取
初看到这里,可能会觉得so easy,只要遍历ranges数组,按照[start, end]截取代码就行了,如果是我们自己的代码按照统一格式写的可能问题不大,但是三方软件各种写法都有,我们看下实际的used代码块长啥样:
2.2.1 代码覆盖率场景复杂
● 场景1 纯粹的used代码
● 场景2 去掉unused仍然语法正确
● 场景3 去掉unused后语法错误
对于前面两个场景直接根据[start, end]截取没有问题,但是对于场景3就会出现语法错误。
这个时候,我们可能想挑战一下,通过增加特殊的截取逻辑来保证语法正确,不过建议不要有此想法,因为还会出现什么新的语法错误场景,以及是否有规律,无法预测,解决起来会耗费大量时间,比如再看看下面代码:
2.2.2 解决思路
● 给coverage工具开发者提issue
如果真要解决,最正确的办法是给coverage工具开发者提issue,让他们来保证,不会出现一个有效代码块被拆成used和unused两部分的情况。
● 临时方案
如果按照上面所说,问题没解决前是否没办法缩小代码体积了,也不一定,既然used部分搞不定,就看看unused部分是否可以,去掉unused的代码,剩下的当作used代码。
对于上面的场景3,和场景4也包含了unused代码,这部分说了解决不了,所以这些都全部当作used代码。
再分析分析看看哪些是可以安全的当作unused代码的,如下:
这些代码看起来都有共同规律,就是从start位置开始,并且以function开头定义的函数,似乎都能安全删除,类似的还有以setup()开头的。
发现规律后,剩下的就是验证是否可行,以及这种场景能减少的文件体积是否划算,如果因为体积减小能提速25%以上,并且整个过程不是很复杂,还是值得的。
2.2.3 开始验证临时方案
按照以上思路开始验证后,又发生了新的问题,那就是生成的coverage.json文件的起始位置不准确,按照该文件截取代码后无法得到正确代码。
json文件的标注位置:
coverage界面查看到的实际位置:
对于这个bug实在有点难理解,按照界面展现结果导出数据不就可以了么,为啥导出的数据和界面不一致,即便是两个团队开发的,一个负责界面展现,一个负责数据导出,也应该对齐处理逻辑。
对于这个bug,已有人给chromium提过issue,也修改过代码,但实际情况是,最终并未覆盖到所有场景,还是有问题(或许简单的js脚本没有问题,例如都是统一的函数定义,没有嵌套,但那就不得而知了)。
bugs.chromium.org/p/chromium/...
到了这一步,看起来似乎无解了 !!!
2.2.3 新的解决思路
既然界面的位置是正确的,那就从界面获取代码覆盖率,思路如下:
将光标不断的向下移动,到了红绿或绿红交界的位置时,获取下面的Line和Column信息。
红-绿交界可获得used代码开始位置,绿-红交界可获得unused代码开始位置,最终就可以计算出unused代码段信息。
思路有了,开始操作,手工肯定不行,搞个机器人自动抓取并记录数据,经过一顿操作之后,机器人跑起来.......
原始文件1百万个字符,跑了一晚上大概10个小时,大约完成1/5,这主要是因为获取下面的数据要通过OCR图片识别,比较耗时,快不了。
本来想趁热打铁一晚上跑出结果,立马验证下,现在还要再跑4个晚上,感觉这种方式着实太折腾,对于商业产品绝对不可行。
实在不想再等几个晚上出结果,于是就开始尝试别的办法减小代码体积,最后也达到了目的。
至此,通过代码覆盖率减小JS代码体积的验证就暂时搁置了。
3. 总结
● 本次实践没有死磕得到最终结果也是出于性价比考虑,首先已有其他方法可以达到相同目的,其次当前实践的方式确实折腾了点,且以后每次更新代码还要再折腾,代价着实太大,因此暂时先放弃,万不得已需要时回头再来做验证。
● 非正常手段或许可以解决问题,但很可能费时费力,还极易出错,如果不是万不得已,还是尽量通过正常手段解决。
● 大公司的代码也会有bug,是否解决一样会看性价比,以及是否普遍场景下会出现,上面描述的问题没有得到解决,估计是错误场景没法100%完全覆盖到,因此只能不了了之。
其他阅读:
掘金都使用webp图片提速降本,必须安排!
Spring AOP-AspectJ注解实现拦截
Spring AOP-编码实现拦截
Spring Cache架构、机制及使用
布隆过滤器适配Spring Cache及问题与解决策略
如何编写软件设计文档
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃