当前位置: 首页 > Web前端 > HTML

代码覆盖率在性能优化中的一种可行应用

时间:2023-03-28 13:23:49 HTML

介绍:JavaScript是前端应用的主要语言。与其他平台编程语言相比,JS资源大部分情况下是通过网络加载的,因此代码的大小直接影响到页面加载执行时间。“无效代码”的多??少直接影响到我们的代码质量,因此衡量代码执行覆盖率是一项重要的预优化工作。你无法管理你无法衡量的东西。如果你不能衡量一件事,你就无法管理它。——管理大师彼得德鲁克前言JavaScript是前端应用的主要语言。与其他平台编程语言相比,JS资源大多通过网络加载,因此代码的大小直接影响页面加载执行时间。“无效代码”的多??少直接影响到我们的代码质量,因此衡量代码执行覆盖率是一项重要的预优化工作。什么是代码覆盖率1、死代码死代码也叫无用代码。这个概念应该是编译时静态分析的代码,对执行没有影响。例如://a.jsconsta=1;常量b=2;/*死代码*/exportdefaulta;//index.jssimportafrom'./a.js';exportdefaultfunction(){console.log(a);}通常我们在编译时使用TreeShaking去除这些死代码用于减少代码大小。2.冗余代码代码覆盖率中提到的冗余代码与DeadCode略有不同。简单来说,Deadcode适用于compilation,Codecoverage适用于runtime。死代码是在任何情况下都不会执行的代码,因此可以在编译期间将其删除。冗余代码是指某些特定的业务逻辑不会执行这些代码逻辑(例如:加载首屏时,根本不会加载某个前端组件,所以对于“首屏”的业务逻辑用例,前端代码冗余)3.代码覆盖率代码覆盖率(Codecoverage)是软件测试中的一个指标。也就是说,它描述了测试期间(运行时)执行的源代码占源代码总数的比例。如何衡量代码覆盖率1、Chrome浏览器的DevToolsDevToolschrome浏览器为我们提供了一个工具Coverage来衡量页面代码(JS、CSS)的覆盖率。使用方法:Devtools——Moretools——Coverage可测代码类型:JSCSS统计可视化形式:使用率以字节计算;当我们选择一个脚本资源时,我们可以在Source栏看到加载页面,当当前资源已经运行代码(蓝色)和还没有运行代码(红色)时;缺点:很明显,网页上的大部分JS脚本,基本上都是经过混淆、压缩、打包后的产物。对于开发者来说,这种覆盖可读性和参考价值不大。TIPS:当然,如果你有源图的话,也可以用浏览器查看源码的覆盖率:在source选项卡中找到当前页面的js资源文件(当然已经混淆得面目全非了)并输入sourcemapURL(以def的形式,以发布平台为例,可以在构建结果中找到)在webpack://目录下,可以查看对应源码的大概覆盖率(不过有没有消费价值)。那么问题来了,有没有办法让开发者了解源代码的代码覆盖值呢?2.Istanbul(NYC)这个软件是以土耳其最大的城市伊斯坦布尔命名的,因为土耳其的地毯是世界闻名的,地毯都是用来盖的。Istanbul或NYC(纽约市,基于istanbul实现)是一个衡量JavaScript程序代码覆盖率的工具。目前,大多数节点代码测试框架都使用该工具来获取测试报告。它有四个衡量维度:linecoverage(线路覆盖率)Coverage——每一行是否执行)【一般我们关注的是这些信息】functioncoverage(函数覆盖率——每个函数是否被调用)branchcoverage(分支覆盖率——是否每个if代码blockisexecuted)语句覆盖率(statementcoverage-每条语句是否执行)可测代码类型:JSTS统计可视化形式:HTMLterminal缺点:目前还没有非侵入式的方案可以使用istanbul,就是在编译构建时修改构建结果的方法,嵌入到统计代码中,然后在运行时显示统计信息。我们可以使用babel-plugin-istanbul插件在AST级别重写源代码。这种编译方式也称为代码检测/检测。3.Instrumentation如果我们要测量这段代码执行了哪些代码,哪些代码没有执行,我们会怎么做?//add.jsfunctionadd(a,b){returna+b}module.exports={add}我们很容易想到在我们的源代码中添加一些“装饰”代码,那么当代码是一行一行的时候执行到某处,那么我们记录在全局环境变量中://全局对象记录__coverage__,记录上面代码中语句和函数的执行次数constc=(window.__coverage__={//"f"表示每个函数被执行的次数//当前代码只有一个函数,所以只有一个f数组,记录值为0f:[0],//"s"表示每个语句被执行的次数executed//3allstatements都赋0s:[0,0,0],})//函数定义是一个语句(statement),那么我们+1c.s[0]++functionadd(a,b){//如果add函数(function)被调用,f+1,调用语句s+1c.f[0]++c.s[1]++returna+b}//add被调用语句s+1c.s[2]++module.exports={add}andistabul同理,babel-plugin-istanbul在构建过程中解析AST,并添加相应的统计单元(语句、函数、分支等)作为装饰代码,最后代码运行后,输出一个json格式的数据:{"/Users/白若冰/test/istanbul.js":{"路径":"/Users/白若冰/test/istanbul.js","s":{"1":1,"2":0,"3":1},"b":{},"f":{"1":0},"fnMap":{//的开始和结束位置函数消息“1”:{“名称”:“添加”,“行”:1,“loc”:{“开始”:{“行”:1,“列”:0},“结束”:{“line":1,"column":19}}}},"statementMap":{//statement"1":{"start":{"line":1,"column":0的起止位置信息},“结尾”:{“行”:3,“列”:1}},“2”:{“开始”:{“行”:2,“列”:4},“结束”:{“行”:2,“列":16}},"3":{"start":{"line":4,"column":0},"end":{"line":4,"column":24}}},"branchMap":{//分支的起止位置信息}}}我们运行代码后,得到上面的json就可以消费了#以终端格式输出nyc报告--reporter=text#以HTML格式输出nyc报告--reporter=lcov--exclude-after-remap=falseterminalHTMLcodecoverageapplicationtipsiniHomeRaxdevelopmentkitTbox:tbox每平方米每屋ConsumerNativeDevelopmentKit既然我们知道了源代码的代码覆盖率,那么我们可以用它来为性能优化做些什么呢?当主工程bundle较大时,将大的/无用的前端组件解包以瘦身首屏主JS包是一个可行的选择。这时候可以根据代码覆盖率来决定优化哪些代码。1.代码拆分React.lazy给我们提供了一个很好的思路,就是利用动态加载模块规范import()的能力(webpack将import()解析为代码拆分)实现前端的懒加载/动态加载组件代码。以此为灵感,为什么不通过动态导入加载一些组件来换取首页bundle的瘦身呢?//动态导入组件//ThisIsBigModimport{createElement,useState,useEffect}from'rax';exportdefault(props)=>{const[AsyncMod,setAsyncMod]=useState(null);useEffect(()=>{constload=async()=>{constModule=awaitimport('./ThisIsBigMod');//keytry{setAsyncMod(Module);}catch(e){console.log(e);}};加载();},[]);如果(!AsyncMod||!AsyncMod.default){返回null;}return;};组件代码的首屏使用率为0(或低于30%的阈值),项目工程中自动生成一个持久化文件配置(在app.json中),然后将这些低使用率的组件代码放置在产品代码被重写为在生产构建期间动态导入。于是就有了如下解决方案:3.如何使用该功能需要在项目下安装如下构建插件:@ali/build-plugin-coverage@ali/build-plugin-async-componentstnpminstall--save-dev@ali/build-plugin-coverage@ali/build-plugin-async-componentsbuild.json//build.json"plugins":[......"@ali/build-plugin-coverage",["@ali/build-plugin-async-components",{"active":true}]]RunTbox:instrumentationbuilddependson@ali/build-plugin-coverage通过instrumentation将源代码插入到统计代码中本地构建完成后页面会全局注入__coverage__变量(可以在页面console输出这个变量,检查插桩是否成功)分析自动生成配置,等待首屏渲染完成(或者完成一系列自定义行为用例),此时插桩代码已经完成了代码使用情况的统计打开TlogsmallTool点击CodeOptimization->GenerateSourceCodeOptimizationConfiguration。此时Tbox本地服务已经收到了__coverage__,完成了后续的代码覆盖率分析。通过分析使用率低于阈值的组件文件,这些组件的组件将项目的相对路径写入app.json的modsPath字段。此时@ali/build-plugin-async-components会根据modsPath配置自动构建组件为动态导入方式。如果想通过自己配置完成异步组件,请直接手动修改app.json中的modsPath字段,只需要依赖@ali/build-plugin-async-components插件重新构建即可。这时候我们会发现,通过条件加载异步加载的组件,BigMod组件已经被动态解包引入,页面的主要js包也被瘦身了,大功告成!写在最后istanbul在node环境下运行测试用例代码可以衡量覆盖率,因为它拦截了运行时模块加载器的源代码,但不幸的是,本文介绍的代码插入分析覆盖会引入一些冗余的堆代码。或许是puppeteerheadlessbrowser提供的api+sourceMap反编译覆盖率更完美的衡量方式。期待与您一起探索,一起努力!原文链接本文为阿里云原创内容,未经允许不得转载