其他
代码覆盖率在性能优化上的一种可行应用
作 者|若冰
程序员进修班
You can't manage what you can't measure. 一件事如果你无法衡量它,你就无法管理它。——管理大师 彼得·德鲁克
const a = 1;
const b = 2; /* dead code */
export default a;
// index.js
import a from './a.js';
export default function() {
console.log(a);
}
2 冗余代码
3 代码覆盖率
1 Chrome 浏览器 Dev Tools
使用方式:Dev tools —— More tools —— Coverage
可度量代码类型:JS CSS
统计可视化形式:
使用率是以byte字节来计算的;
当我们选择一段脚本资源即可在 Source 栏可以看到加载页面时当前资源 run过得代码(蓝色)和没有run过得代码(红色);
缺点:显然,目前大部分网页上的JS脚本基本都是经过混淆压缩打包过后的产物,对于开发者而言,这种覆盖率可读性及参考价值不大。
2 Istanbul(NYC)
这个软件以土耳其最大城市伊斯坦布尔命名,因为土耳其地毯世界闻名,而地毯则是用来覆盖的。
function coverage(函数覆盖率-每个函数是否都调用了)
branch coverage(分支覆盖率-是否每个 if 代码块都执行了)
statement coverage(语句覆盖率-是否每个语句都执行了)
可以度量的代码类型:JS TS
统计可视化的形式:
HTML terminal
缺点:目前使用 istanbul 度量网页前端JS代码覆盖率没有非侵入的方案,采用的是在编译构建时修改构建结果的方式埋入统计代码,再在运行时进行统计展示。
3 插桩构建
function add(a, b) {
return a + b
}
module.exports = { add }
const c = (window.__coverage__ = {
// "f" 表示每一个 function 被执行的次数
// 当前代码只有一个 function 因此,f 数组只有一个 且记录值为 0
f: [0],
// "s" 表示每一个 statement 被执行的次数
// 3 个 statement 全部都以 0 赋值
s: [0, 0, 0],
})
// 函数定义是一个语句(statement),那么我们 +1
c.s[0]++
function add(a, b) {
// 如果 add 函数(function)被调用,f +1,且改调用语句 s +1
c.f[0]++
c.s[1]++
return a + b
}
// add 被调出语句 s +1
c.s[2]++
module.exports = { add }
"/Users/bairuobing/test/istanbul.js":{
"path":"/Users/bairuobing/test/istanbul.js",
"s":{
"1":1,
"2":0,
"3":1
},
"b":{
},
"f":{
"1":0
},
"fnMap":{ // function 的开始结束位置信息
"1":{
"name":"add",
"line":1,
"loc":{
"start":{
"line":1,
"column":0
},
"end":{
"line":1,
"column":19
}
}
}
},
"statementMap":{ // statement 的开始结束位置信息
"1":{
"start":{
"line":1,
"column":0
},
"end":{
"line":3,
"column":1
}
},
"2":{
"start":{
"line":2,
"column":4
},
"end":{
"line":2,
"column":16
}
},
"3":{
"start":{
"line":4,
"column":0
},
"end":{
"line":4,
"column":24
}
}
},
"branchMap":{ // branch 的开始结束位置信息
}
}
}
nyc report --reporter=text
# HTML 形式输出
nyc report --reporter=lcov --exclude-after-remap=false
terminal
HTML
1 代码分割
// ThisIsBigMod
import { createElement, useState, useEffect } from 'rax';
export default (props) => {
const [AsyncMod, setAsyncMod] = useState(null);
useEffect(() => {
const load = async () => {
const Module = await import('./ThisIsBigMod'); // 关键
try {
setAsyncMod(Module);
} catch (e) {
console.log(e);
}
};
load();
}, []);
if (!AsyncMod || !AsyncMod.default) {
return null;
}
return <AsyncMod.default {...props} />;
};
2 下一步
3 如何使用
@ali/build-plugin-coverage @ali/build-plugin-async-components
"plugins": [
......
"@ali/build-plugin-coverage",
[
"@ali/build-plugin-async-components",
{
"active": true
}
]
]
依赖 @ali/build-plugin-coverage
通过插桩将源码中插入统计代码
本地构建之后页面全局会注入__coverage__变量(可在页面控制台输出该变量检查插桩是否成功)
等待完成首屏渲染(或者完成自定义的一系列行为用例),此刻插桩代码已经完成了代码使用率的统计
打开 Tlog 小工具 点击代码优化->生成源代码优化配置,此刻 Tbox 本地服务已经接收到了发来的__coverage__并完成后续的代码覆盖率分析,通过分析使用率低于门槛值的组件文件,将这些组件的项目相对路径写入 app.json 的 modsPath 字段下
此刻 @ali/build-plugin-async-components 会根据 modsPath 配置自动将组件构建为动态引入的方式
如果您想通过自己的配置来完成组件异步化,请直接手动修改 app.json 里的 modsPath 字段,只需依赖 @ali/build-plugin-async-components 插件再次构件即可
此时我们条件加载被异步化的组件会发现,BigMod 组件已经被动态的拆包引入了,页面主 js 包也得到了瘦身,搞定!
写在最后