构建一个node.js工具来记录和比较Google灯塔报告
在本教程中,我将逐步向您展示如何在Node.js中创建一个简单的工具,以通过命令行运行Google Lighthouse审核,保存以JSON格式生成的报告,然后比较它们,以便随着网站的增长和开发,可以监视网络性能。
我希望这对任何有兴趣了解如何与Google Lighthouse合作的开发人员都可以作为一个很好的介绍。
但首先,对于初学者…
什么是Google灯塔?
Google Lighthouse是网络开发人员实用腰带上可用的最佳自动化工具之一。它使您可以在许多关键领域中快速审核网站,这可以构成其整体质量的衡量标准。这些都是:
- 表现
- 可访问性
- 最佳实践
- SEO
- 渐进的网络应用程序
审核完成后,就会在您的网站做得很好的情况下生成报告……但后者打算作为您下一步改进页面的指标。
这就是完整报告的样子。
除其他一般诊断和网络性能指标外,该报告的一个非常有用的功能是,每个关键领域都汇总为0-100之间的颜色编码得分。
这不仅允许开发人员在没有进一步分析的情况下快速评估网站的质量,而且还允许利益相关者或客户等非技术人员也可以理解。
例如,这意味着,在花费时间来改善网站可访问性之后,与Heather分享胜利要容易得多,因为看到灯塔可及性得分增加了50分,她更有能力欣赏这笔努力。
但是,同样的是,项目经理西蒙(Simon)可能不了解速度索引或第一个内容的涂料意味着什么,但是当他看到灯塔报告显示网站性能得分膝盖膝盖深处是红色的,他知道您仍然有工作要做。
如果您是Chrome或最新版本的Edge,则可以使用DevTools立即进行灯塔审核。以下是:
您还可以通过PagesPeed Insights或通过流行的性能工具(例如WebPagetest)在线运行灯塔审核。
但是,今天,我们只对灯塔作为节点模块感兴趣,因为这使我们能够以编程方式使用该工具来审核,记录和比较Web性能指标。
让我们找出如何。
设置
首先,如果您还没有,您将需要Node.js。有一百万种不同的方法可以安装它。我使用Homebrew Package Manager,但是如果您喜欢的话,您也可以直接从Node.js网站下载安装程序。本教程是用Node.JS v10.17.0编写的,但考虑到过去几年中发布的大多数版本,很可能会正常工作。
您还将需要安装Chrome,因为这就是我们进行灯塔审核的方式。
接下来,为项目创建一个新目录,然后在控制台中CD中的CD。然后运行npm init开始创建一个package.json文件。在这一点上,我建议您一遍又一遍地敲击Enter键,以尽可能多地跳过此内容,直到创建文件为止。
现在,让我们在项目目录中创建一个新文件。我叫我的lh.js,但随时随时称其为您想要的。这将包含该工具的所有JavaScript。在您选择的文本编辑器中打开它,现在,编写Console.Log语句。
console.log('Hello World');
然后,在控制台中,确保您的CWD(当前工作目录)是您的项目目录并运行节点LH.JS,将我的文件名替换为您使用的任何内容。
您应该看到:
$ node lh.js 你好世界
如果没有,请检查您的节点安装工作正在工作,并且您肯定是正确的项目目录。
现在,我们可以继续开发工具本身。
用node.js打开铬
让我们安装项目的第一个依赖性:灯塔本身。
NPM安装灯塔 - save-dev
这将创建一个包含所有包装文件的Node_modules目录。如果您使用的是git,那么您唯一想做的就是将其添加到.gitignore文件中。
在LH.JS中,您接下来要删除test Console.log()并导入灯塔模块,以便您可以在代码中使用它。像这样:
const Lighthouse = require('Lighthouse');
在其下方,您还需要导入一个称为Chrome-launcher的模块,该模块是Lighthouse的依赖项之一,并允许Node单独启动Chrome,以便可以进行审核。
const Lighthouse = require('Lighthouse'); const chromelauncher = require('Chrome-launcher');
现在,我们可以访问这两个模块,让我们创建一个简单的脚本,该脚本仅打开Chrome,运行灯塔审核,然后将报告打印到控制台。
创建一个接受URL作为参数的新功能。因为我们将使用node.js运行此操作,所以我们能够安全地使用ES6语法,因为我们不必担心那些讨厌的Internet Explorer用户。
const启动chrome =(url)=> { }
在功能中,我们需要做的第一件事是使用我们导入的Chrome-Launcher模块打开Chrome,并将其发送到通过URL参数传递的任何参数。
我们可以使用其启动()方法及其启动功能选项来执行此操作。
const启动chrome = url => { chromelauncher.launch({ starterurl:url }); };
调用下面的功能并传递您选择的URL会导致在运行节点脚本时在URL上打开Chrome。
启动Chrome('https://www.lukeharrison.dev');
启动功能实际上返回了一个承诺,这使我们能够访问包含一些有用方法和属性的对象。
例如,使用下面的代码,我们可以打开Chrome,将对象打印到控制台上,然后使用其Kill()方法在三秒钟后关闭Chrome。
const启动chrome = url => { Chromelauncher 。发射({ starterurl:url })) 。 Console.Log(Chrome); settimeout(()=> chrome.kill(),3000); }); }; 启动Chrome(“ https://www.lukeharrison.dev”);
现在,我们已经弄清楚了Chrome,让我们继续前进。
通过编程运行灯塔
首先,让我们重命名我们的启动Chrome()函数,以更反映其最终功能的内容:启动Chromeandrunlighthouse()。在艰难的部分方面,我们现在可以使用我们先前在教程中导入的灯塔模块。
在Chrome Launcher的函数中,仅在浏览器打开后才执行,我们将通过Lighthouse通过函数的URL参数并触发对本网站的审核。
const lastionchromeandrunlighthouse = url => { Chromelauncher 。发射({ starterurl:url })) 。 const opts = { 港口:chrome.port }; 灯塔(URL,opts); }); }; Launchromeandrunlighthouse(“ https://www.lukeharrison.dev”);
要将灯塔实例链接到我们的Chrome浏览器窗口,我们必须与URL一起通过其端口。
如果您现在要运行此脚本,您将在控制台中遇到一个错误:
(节点:47714)未经用力的撤销辩护措施:错误:您可能有多个针对相同原点打开的选项卡。
为了解决此问题,我们只需要从Chrome Launcher中删除启动功能选项,然后让Lighthouse从现在开始处理URL导航。
const lastionchromeandrunlighthouse = url => { chromelauncher.launch()。然后(chrome => { const opts = { 港口:chrome.port }; 灯塔(URL,opts); }); };
如果要执行此代码,您会注意到似乎正在发生某些事情。我们只是没有在控制台中获得任何反馈来确认灯塔审计肯定已经运行,也没有像以前那样关闭Chrome实例。
值得庆幸的是,Lighthouse()函数返回了一个承诺,使我们可以访问审核结果。
让我们杀死Chrome,然后通过结果对象的报告属性将这些结果以JSON格式打印到终端。
const lastionchromeandrunlighthouse = url => { chromelauncher.launch()。然后(chrome => { const opts = { 港口:chrome.port }; 灯塔(url,opts)。然后(结果=> { Chrome.kill(); console.log(Results.Report); }); }); };
虽然控制台并不是显示这些结果的最佳方法,但是如果您将它们复制到剪贴板并访问灯塔报告查看器,则在此处粘贴将以所有荣耀显示报告。
在这一点上,重要的是要稍微整理代码,以使启动chromeandrunlighthouse()函数在报告完成后返回该报告。这使我们可以稍后处理报告,而不会导致JavaScript的混乱金字塔。
const Lighthouse = require(“灯塔”); const chromelauncher = require(“ Chrome-launcher”); const lastionchromeandrunlighthouse = url => { 返回chromelauncher.launch()。然后(chrome => { const opts = { 港口:chrome.port }; 返回灯塔(url,opts)。然后(结果=> { 返回chrome.kill()。然后(()=> results.report); }); }); }; lasterchromeandrunlighthouse(“ https://www.lukeharrison.dev”)。然后(结果=> { console.log(结果); });
您可能注意到的一件事是,我们的工具目前只能审核一个网站。让我们更改此内容,以便您可以通过命令行作为参数传递URL。
为了消除使用命令行论证的痛苦,我们将使用称为Yargs的软件包来处理它们。
NPM安装 - save-dev Yargs
然后将其与Chrome Launcher和Lighthouse一起在脚本的顶部导入。我们在这里只需要它的argv函数。
const Lighthouse = require('Lighthouse'); const chromelauncher = require('Chrome-launcher'); const argv = require('yargs')。argv;
这意味着,如果您像这样在终端中通过命令行参数:
节点LH.JS - url https://www.google.co.uk
…您可以这样访问脚本中的参数:
const url = argv.url // https://www.google.co.uk
让我们编辑脚本以将命令行URL参数传递到函数的URL参数。重要的是要通过IF语句和错误消息添加小型安全网,以防万一没有参数。
如果(argv.url){ lastionchromeandrunlighthouse(argv.url)。然后(结果=> { console.log(结果); }); } 别的 { 投掷“您还没有将URL传递给灯塔”; }
塔达!我们有一个工具可以启动Chrome并以编程方式运行灯塔审核,然后以JSON格式将报告打印到终端。
保存灯塔报告
将报告打印到控制台并不是很有用,因为您无法轻易阅读其内容,也不能保存以将来使用。在教程的这一部分中,我们将更改此行为,以便将每个报告保存到自己的JSON文件中。
为了停止来自不同网站的报告,我们会这样组织:
- lukeharrison.dev
- 2020-01-31T18:18:12.648Z.JSON
- 2020-01-31T19:10:24.110Z.JSON
- cnn.com
- 2020-01-14T22:15:10.396Z.JSON
- LH.JS
我们将用时间戳命名报告,指示该报告的日期/时间何时生成。这意味着没有两个报告文件名称将永远相同,它将帮助我们轻松区分报告。
Windows有一个需要我们注意的问题:结肠(:)是文件名的非法字符。为了减轻此问题,我们将用下划线(_)替换所有结肠,因此典型的报告文件名看起来像:
- 2020-01-31T18_18_12.648Z.JSON
创建目录
首先,我们需要操纵命令行URL参数,以便我们可以将其用于目录名称。
这不仅涉及删除www,因为它需要考虑在不坐在根部的网页上运行的审核(例如:www.foo.com/bar),因为斜线是目录名称的无效字符。
对于这些URL,我们将再次用下划线替换无效的字符。这样,如果您在https://www.foo.com/bar上进行审核,则包含报告的结果名称为foo.com_bar。
为了使处理URL的处理更加容易,我们将使用称为URL的本机node.js模块。可以像其他任何软件包一样导入这,而无需将其添加到thepackage.json并通过NPM将其拉动。
const Lighthouse = require('Lighthouse'); const chromelauncher = require('Chrome-launcher'); const argv = require('yargs')。argv; const url = require('url');
接下来,让我们使用它来实例化新的URL对象。
如果(argv.url){ const urlobj = new url(argv.url); lastionchromeandrunlighthouse(argv.url)。然后(结果=> { console.log(结果); }); }
如果要将URLOBJ打印到控制台,您会看到我们可以使用的许多有用的URL数据。
$ node lh.js -url https://www.foo.com/bar url { href:'https://www.foo.com/bar', 来源:'https://www.foo.com', 协议:'https:',, 用户名:'', 密码: '', 主持人:'www.foo.com', 主机名:'www.foo.com', 港口: '', 路径名:'/bar', 搜索: '', searchParams:urlsearchparams {}, 哈希:'' }
创建一个称为DirName的新变量,并在我们URL的主机属性上使用String repent()方法除了HTTPS协议外,还可以摆脱www:
const urlobj = new url(argv.url); 令dirname = urlobj.host.replace('www。','');
我们已经使用了LET,这与const可以重新分配不同,因为如果URL具有路径名,我们需要更新参考,以用下划线替换斜线。这可以通过正则表达模式来完成,看起来这样:
const urlobj = new url(argv.url); 令dirname = urlobj.host.replace(“ www。”,“”); if(urlobj.pathname!==“/”){ dirName = dirname urlobj.pathname.replace(/\ // g,“ _”); }
现在我们可以创建目录本身。这可以通过使用另一个名为FS的本机node.js模块(“文件系统”简称)来完成。
const Lighthouse = require('Lighthouse'); const chromelauncher = require('Chrome-launcher'); const argv = require('yargs')。argv; const url = require('url'); const fs = require('fs');
我们可以使用其Mkdir()方法来创建目录,但是首先必须使用其已验证方法()方法检查目录是否已经存在,因为Node.js否则会引发错误:
const urlobj = new url(argv.url); 令dirname = urlobj.host.replace(“ www。”,“”); if(urlobj.pathname!==“/”){ dirName = dirname urlobj.pathname.replace(/\ // g,“ _”); } 如果(!fs.existsync(dirname)){ fs.mkdirsync(dirname); }
在此点测试脚本应导致创建一个新的目录。通过https://www.bbc.co.uk/news,作为URL参数将导致名为bbc.co.uk_news的目录。
保存报告
在启动Chromeandrunlighthouse()的当时功能中,我们希望用逻辑替换现有的console.log将报告写入磁盘。可以使用FS模块的WriteFile()方法完成。
lastionchromeandrunlighthouse(argv.url)。然后(结果=> { fs.writefile(“ report.json”,结果,err => { 如果(err)投掷err; }); });
第一个参数代表文件名,第二个参数是文件的内容,第三个是在写入过程中出现问题时包含错误对象的回调。这将创建一个名为report.json的新文件,其中包含返回的灯塔报告JSON对象。
我们仍然需要将其发送到正确的目录,并以时间戳为文件名。
lastionchromeandrunlighthouse(argv.url)。然后(结果=> { fs.writefile(`$ {dirname}/report.json`,结果,err => { 如果(err)投掷err; }); });
尽管后者要求我们以某种方式检索报告生成报告的时间戳。值得庆幸的是,该报告本身将其捕获为数据点,并将其存储为获取时间属性。
我们只需要记住要交换任何结肠(:)下划线(_),这样它就可以在Windows文件系统中播放。
lastionchromeandrunlighthouse(argv.url)。然后(结果=> { fs.writefile( `$ {dirName}/$ {resuces [“ fetchtime”]。替换(/:/g,“ _”)}。 结果, err => { 如果(err)投掷err; } ); });
如果您现在要运行此操作,而不是时间戳。JSON文件名,则可能会看到类似的错误:
未经手的征服者:typeError:无法读取未定义的属性“替换”
之所以发生这种情况,是因为灯塔目前正在以JSON格式返回报告,而不是JavaScript所消耗的对象。
值得庆幸的是,我们可以要求Lighthouse将报告作为常规JavaScript对象归还报告,而不是自己解析JSON。
这需要从:
返回chrome.kill()。然后(()=> results.report);
…到:
返回chrome.kill()。然后(()=> results.lhr);
现在,如果您重新运行脚本,则文件将正确命名。但是,打开时,不幸的是,只有内容是…
[对象对象]
这是因为我们现在像以前一样遇到了相反的问题。我们正在尝试将JavaScript对象渲染而不将其串起到JSON对象。
解决方案很简单。为了避免在解析或弦上浪费资源,我们可以从灯塔中返回两种类型:
返回灯塔(url,opts)。然后(结果=> { 返回chrome.kill()。然后(()=> { 返回 { JS:results.lhr, JSON:RESSERS.REPORT }; }); });
然后,我们可以将WriteFile实例修改为:
fs.writefile( `$ {dirName}/$ {results.js [“ fetchtime”]。替换(/:/g,“ _”)}。 结果。 err => { 如果(err)投掷err; } );
分类!灯塔审核完成后,我们的工具现在应将报告保存到以网站URL命名的目录中的独特时间戳文件名保存到文件中。
这意味着现在报告的报告效率更高,并且无论保存多少报告都不会相互覆盖。
比较灯塔报告
在日常开发期间,当我专注于提高性能时,能够直接在控制台中快速比较报告的能力,看看我是否朝正确的方向前进可能非常有用。考虑到这一点,此比较功能的要求应该是:
- 如果灯塔审核完成后已经存在了同一网站的上一个报告,请自动对其进行比较,并显示关键性能指标的任何更改。
- 我还应该能够比较来自任意两个网站的任何两个报告中的关键性能指标,而无需生成我可能不需要的新灯塔报告。
应该比较报告的哪些部分?这些是作为任何灯塔报告的一部分收集的数值密钥性能指标。它们提供了有关网站的客观和感知性能的见解。
此外,Lighthouse还收集了该报告此部分中未列出的其他指标,但仍以适当的格式进行比较。这些都是:
- 是时候首先字节了 -首先字节可以识别服务器发送响应的时间。
- 总阻塞时间 - FCP与交互式时间之间的所有时间段的总和,当任务长度超过50ms,以毫秒为单位表示。
- 估计的输入延迟 -估计的输入延迟是对您在最繁忙的页面加载最繁忙的5S窗口中对应用程序响应用户输入所需多长时间的估计。如果您的延迟高于50ms,则用户可能会将您的应用视为Laggy。
如何将指标比较输出到控制台?我们将使用新旧指标创建一个简单的基于百分比的比较,以查看它们如何从报告中更改为报告。
为了进行快速扫描,我们还会根据颜色代码的单个指标,具体取决于它们是否更快,较慢或不变。
我们的目标是该输出:
将新报告与先前报告进行比较
让我们开始创建一个名为ComparePorts()的新函数,就在我们的启动Cromeandrunlighthouse()函数下方,该功能将包含所有比较逻辑。我们将给它两个参数 - 从和到 - 接受用于比较的两个报告。
目前,作为占位符,我们将只将每个报告中的一些数据打印到控制台,以验证它正确接收它们。
const compareports =(从,到)=> { console.log(来自[“ farnurl”]“”“来自[fetchtime“”]); console.log(to [“ farnurl”]“”“ to [fetchtime']); };
由于此比较将在创建新报告后开始,因此执行此功能的逻辑应位于paintionChroMeandrunlighthouse()的the then函数中。
例如,如果您在目录中有30个报告,我们需要确定哪个报告是最新的,并将其设置为上一个报告,将其与新的报告进行比较。值得庆幸的是,我们已经决定将时间戳用作报告的文件名,因此这为我们提供了一些工作。
首先,我们需要收集任何现有报告。为了简化此过程,我们将安装一个名为Glob的新依赖项,该依赖项允许在搜索文件时进行模式匹配。这很关键,因为我们无法预测将存在多少报告或它们将被称为什么。
像其他任何依赖性一样安装它:
NPM安装glob- save-dev
然后以与往常相同的方式将其导入文件的顶部:
const Lighthouse = require('Lighthouse'); const chromelauncher = require('Chrome-launcher'); const argv = require('yargs')。argv; const url = require('url'); const fs = require('fs'); const glob = require('glob');
我们将使用Glob收集目录中的所有报告,我们已经知道通过DirName变量的名称。将其同步选项设置为True非常重要,因为我们不希望JavaScript执行继续执行,直到我们知道存在多少其他报告为止。
lastionchromeandrunlighthouse(argv.url)。然后(结果=> { const preveports = glog(`$ {dirname}/*。json`,{ 同步:是的 }); //等 });
此过程返回一系列路径。因此,如果报告目录看起来像这样:
- lukeharrison.dev
- 2020-01-31T10_18_12.648Z.JSON
- 2020-01-31T10_18_24.110Z.JSON
…然后由此产生的阵列看起来像这样:
[ 'lukeharrison.dev/2020-01-31T10_18_12.648Z.JSON', 'lukeharrison.dev/2020-01-31T10_18_24.110Z.JSON' 这是给出的
因为我们只能在以前的报告存在的情况下进行比较,所以让我们将此数组作为比较逻辑的条件:
const preveports = glog(`$ {dirname}/*。json`,{ 同步:是的 }); if(prevreports.length){ }
我们有报告文件路径的列表,我们需要比较他们的时间戳文件名,以确定哪个是最近的文件名。
这意味着我们首先需要收集所有文件名的列表,修剪所有无关的数据,例如目录名称,并谨慎地用colons(:)将其替换为下划线(_)(_ :),以将它们再次变成有效的日期。最简单的方法是使用路径,另一个node.js本机模块。
const路径= require('path');
将路径作为论点传递到其解析方法,这样的方法:
PATH.PARSE('lukeharrison.dev/2020-01-01-31T10_18_24.110Z.JSON');
返回此有用的对象:
{ 根: '', dir:“ lukeharrison.dev”, 基础:'2020-01-31T10_18_24.110Z.JSON', 分机:'.json', 名称:'2020-01-31T10_18_24.110Z' }
因此,要获取所有时间戳文件名的列表,我们可以做到这一点:
if(prevreports.length){ dates = []; (在preveports中报告){ dates.push(push( 新日期(path.parse(prevreports [report])。name.replace(/_/g,“:”)) ); } }
如果我们的目录看起来像:
- lukeharrison.dev
- 2020-01-31T10_18_12.648Z.JSON
- 2020-01-31T10_18_24.110Z.JSON
会导致:
[ '2020-01-31T10:18:12.648Z', '2020-01-31T10:18:24.110Z' 这是给出的
关于日期的一个有用的事情是,默认情况下它们可以固有可比性:
const alpha = new Date('2020-01-31'); const bravo = new Date('2020-02-15'); console.log(alpha> bravo); // 错误的 console.log(bravo> alpha); // 真的
因此,通过使用降低功能,我们可以减少日期数量,直到最新剩余的剩余时间为止:
dates = []; (在preveports中报告){ dates.push(new Date(path.parse(prevreports [report])。name.replace(/_/g,“:”))); } const max = dates.dates.duce(函数(a,b){ 返回Math.max(a,b); });
如果要将Max的内容打印到控制台,它将抛出UNIX时间戳,因此,现在,我们只需要添加另一行即可将最新日期转换回正确的ISO格式:
const max = dates.dates.duce(函数(a,b){ 返回Math.max(a,b); }); CONS CONST RESERPORT =新日期(max).toisostring();
假设这些是报告的清单:
- 2020-01-31T23_24_41.786Z.JSON
- 2020-01-31T23_25_36.827Z.JSON
- 2020-01-31T23_37_56.856Z.JSON
- 2020-01-31T23_39_20.459Z.JSON
- 2020-01-31T23_56_50.959Z.JSON
最近报告的价值将是2020-01-31T23:56:50.959Z。
现在我们知道了最新的报告,我们接下来需要提取其内容。在最近的Report变量下方创建一个称为最新报告的新变量,并为其分配一个空功能。
众所周知,此功能始终需要执行而不是手动调用它,因此将其变成IFFE(立即调用函数表达式)是有意义的,当JavaScript Parser达到它时,它将自行运行。这是由额外的括号表示的:
const最近的reportcontents =(()=> { })();
在此功能中,我们可以使用本机FS模块的ReadFileSync()方法返回最新报告的内容。因为这将采用JSON格式,因此将其解析为常规JavaScript对象很重要。
const最近的reportcontents =(()=> { const output = fs.ReadFilesync( dirname“/” erseReport.replace(/:/g,“ _”)“ .json”, “ utf8”, (err,结果)=> { 返回结果; } ); 返回JSON.PARSE(输出); })();
然后,这是将ComparePorts()函数调用并通过当前报告和最新报告作为参数的问题。
比较porterports(最近的报告,结果。
目前,只需将一些详细信息打印到控制台,以便我们可以测试报告数据通过确定:
https://www.lukeharrison.dev/ 2020-02-01T00:25:06.918Z https://www.lukeharrison.dev/ 2020-02-01T00:25:42.169z
如果您目前会遇到任何错误,请尝试删除任何报告或报告,而没有有效的内容。
比较任何两个报告
剩下的关键要求是能够比较任何两个网站的任何两个报告。实现此目的的最简单方法是允许用户将完整的报告文件路径作为命令行参数传递,然后将其发送到comparePorts()函数。
在命令行中,这看起来像:
Node lh.js -from lukeharrison.dev/2020-02-01T00:25:06.918Z--to cnn.com/2019-12-16t15:12:12:12:169z
实现此目的需要编辑条件if语句,该语句检查是否存在URL命令行参数。我们将添加额外的检查,以查看用户是否刚刚通过A到路径,否则检查URL是否像以前一样。这样,我们将防止新的灯塔审核。
if(argv.from && argv.to){ } else if(argv.url){ //等 }
让我们提取这些JSON文件的内容,将它们解析为JavaScript对象,然后将它们传递到comparePorts()函数。
在检索最新报告之前,我们已经对JSON进行了解析。我们可以将此功能推断到其自己的辅助功能中,并在两个位置使用它。
使用最新的reportcontents()函数作为基础,创建一个称为getContents()的新函数,该函数接受文件路径作为参数。确保这只是一个常规功能,而不是IFFE,因为我们不希望它在JavaScript Parser找到它后立即执行。
const getContents = pathstr => { const output = fs.ReadFileSync(pathstr,“ utf8”,(err,结果)=> { 返回结果; }); 返回JSON.PARSE(输出); }; const compareports =(从,到)=> { console.log(来自[“ farnurl”]“”“来自[“ fetchtime”)); console.log(to [“ farnurl”]“”“ to [“ fetchtime”]); };
然后更新最近的ReportContents()函数以使用此外推助手功能:
CONS CONST REASTREPORTCONTENTS = GETCONTENTS(DIRNAME'/'fasterReport.Replace(/:/G,'_'').json');
回到新的条件下,我们需要将比较报告的内容传递给比较porterports()函数。
if(argv.from && argv.to){ 比较港口( getContents(argv.from“ .json”), getContents(argv.to“ .json”) ); }
像以前一样,这应该打印出有关控制台中报告的一些基本信息,以便让我们知道一切正常。
Node lh.js -from lukeharrison.dev/2020-01-01-31T23_24_41.786Z-TO Lukeharrison.dev/2020-02-02-01T11_16_25.221Z
会导致:
https://www.lukeharrison.dev/ 2020-01-01-31T23_24_41.786Z https://www.lukeharrison.dev/ 2020-02-01T11_16_25.221Z
比较逻辑
开发的这一部分涉及构建比较逻辑,以比较比较()函数收到的两个报告。
在灯塔返回的对象中,有一个名为审核的属性,其中包含另一个对象列表性能指标,机会和信息。这里有很多信息,其中很多我们不感兴趣的目的。
这是首先内容涂料的条目,这是我们希望比较的九种性能指标之一:
“首先要使用的绘画”:{ “ id”:“先进paint”, “标题”:“第一个满足的油漆”, “描述”:“首先满足的油漆标志着第一个文本或图像的绘制时间。 “得分”:1, “ corkoredisplaymode”:“数字”, “ NemericValue”:1081.661, “ DisplayValue”:“ 1.1 S” }
创建一个列出了这9个性能指标的键。我们可以使用它来过滤审核对象:
const compareports =(从,到)=> { const metricfilter = [ “第一核漆”, “第一次涂鸦”, “速度指数”, “估计输入延迟”, “完全阻止时间”, “ max-potential-fid”, “第一字节”, “ first-cpu-idle”, “交互的” ]; };
然后,我们将循环浏览报告的一个审核对象之一,然后在我们的过滤器列表中交叉引用其名称。 (哪个审核对象都没有相同的内容结构,这没关系。)
如果它在那里,那就太好了,我们想使用它。
const metricfilter = [ “第一核漆”, “第一次涂鸦”, “速度指数”, “估计输入延迟”, “完全阻止时间”, “ max-potential-fid”, “第一字节”, “ first-cpu-idle”, “交互的” ]; for(让审核来自[“审核”]){ if(metricfilter.includes(auditobj)){ console.log(auditObj); } }
此Console.log()将在控制台上打印以下键:
首先绘制 首先是粉刷 速度指数 估计输入延迟 总障碍时间 最大势力 第一字节 第一cpu-idle 交互的
这意味着我们将使用['audits'] [auditObj]。numericValue和['aucatits'] [auditobj]。在此循环中分别numericValue来访问指标本身。
如果我们将它们用钥匙打印到控制台,则会导致这样的输出:
首先使用paint 1081.661 890.774 首先要绘制paint 1081.661 954.774 速度指数15576.70313351777 1098.622294504341 估计输入延迟12.8 12.8 总障碍时间59 31.5 最大势力153 102 第一字节的时间16.85999999999985 16.09600000000000004 First-Cpu-Idle 1704.8490000000002 1918.774 互动2266.2835 2374.3615
我们现在拥有所有需要的数据。我们只需要计算这两个值之间的百分比差,然后使用前面概述的颜色编码格式将其记录到控制台。
您知道如何计算两个值之间的百分比变化吗?我也不。值得庆幸的是,每个人最喜欢的Monolith搜索引擎都进行了营救。
公式是:
((从 - 到) /从)x 100
因此,假设我们的第一个报告(来自)的速度索引为5.7,然后是第二个报告的速度索引,第二个报告的速度指数为2.1。计算是:
5.7-2.1 = 3.6 3.6 / 5.7 = 0.63157895 0.63157895 * 100 = 63.157895
四舍五入到小数点的位置将使速度指数降低63.16%。
让我们将其放入MetricFilter数组下方的比较ports()函数中的辅助函数中。
const calcpercentagediff =(从,到)=> { const per =((从) / from) * 100; 返回Math.Round(Per * 100) / 100; };
Back in our auditObj conditional, we can begin to put together the final report comparison output.
First off, use the helper function to generate the percentage difference for each metric.
for (let auditObj in from["audits"]) { if (metricFilter.includes(auditObj)) { const percentageDiff = calcPercentageDiff( from["audits"][auditObj].numericValue, to["audits"][auditObj].numericValue ); } }
Next, we need to output values in this format to the console:
This requires adding color to the console output. In Node.js, this can be done by passing a color code as an argument to the console.log() function like so:
console.log('\x1b[36m', 'hello') // Would print 'hello' in cyan
You can get a full reference of color codes in this Stackoverflow question. We need green and red, so that's \x1b[32m and \x1b[31m respectively. For metrics where the value remains unchanged, we'll just use white. This would be \x1b[37m.
Depending on if the percentage increase is a positive or negative number, the following things need to happen:
- Log color needs to change (Green for negative, red for positive, white for unchanged)
- Log text contents change.
- '[Name] is X% slower for positive numbers
- '[Name] is X% faster' for negative numbers
- '[Name] is unchanged' for numbers with no percentage difference.
- If the number is negative, we want to remove the minus/negative symbol, as otherwise, you'd have a sentence like 'Speed Index is -92.95% faster' which doesn't make sense.
There are many ways this could be done. Here, we'll use theMath.sign() function, which returns 1 if its argument is positive, 0 if well… 0, and -1 if the number is negative. That'll do.
for (let auditObj in from["audits"]) { if (metricFilter.includes(auditObj)) { const percentageDiff = calcPercentageDiff( from["audits"][auditObj].numericValue, to["audits"][auditObj].numericValue ); let logColor = "\x1b[37m"; const log = (() => { if (Math.sign(percentageDiff) === 1) { logColor = "\x1b[31m"; return `${percentageDiff "%"} slower`; } else if (Math.sign(percentageDiff) === 0) { return "unchanged"; } 别的 { logColor = "\x1b[32m"; return `${percentageDiff "%"} faster`; } })(); console.log(logColor, `${from["audits"][auditObj].title} is ${log}`); } }
So, there we have it.
You can create new Lighthouse reports, and if a previous one exists, a comparison is made.
And you can also compare any two reports from any two sites.
Complete source code
Here's the completed source code for the tool, which you can also view in a Gist via the link below.
const lighthouse = require("lighthouse"); const chromeLauncher = require("chrome-launcher"); const argv = require("yargs").argv; const url = require("url"); const fs = require("fs"); const glob = require("glob"); const path = require("path"); const launchChromeAndRunLighthouse = url => { return chromeLauncher.launch().then(chrome => { const opts = { port: chrome.port }; return lighthouse(url, opts).then(results => { return chrome.kill().then(() => { 返回 { js: results.lhr, json: results.report }; }); }); }); }; const getContents = pathStr => { const output = fs.readFileSync(pathStr, "utf8", (err, results) => { return results; }); return JSON.parse(output); }; const compareReports = (from, to) => { const metricFilter = [ "first-contentful-paint", "first-meaningful-paint", "speed-index", "estimated-input-latency", "total-blocking-time", "max-potential-fid", "time-to-first-byte", "first-cpu-idle", “交互的” ]; const calcPercentageDiff = (from, to) => { const per = ((to - from) / from) * 100; return Math.round(per * 100) / 100; }; for (let auditObj in from["audits"]) { if (metricFilter.includes(auditObj)) { const percentageDiff = calcPercentageDiff( from["audits"][auditObj].numericValue, to["audits"][auditObj].numericValue ); let logColor = "\x1b[37m"; const log = (() => { if (Math.sign(percentageDiff) === 1) { logColor = "\x1b[31m"; return `${percentageDiff.toString().replace("-", "") "%"} slower`; } else if (Math.sign(percentageDiff) === 0) { return "unchanged"; } 别的 { logColor = "\x1b[32m"; return `${percentageDiff.toString().replace("-", "") "%"} faster`; } })(); console.log(logColor, `${from["audits"][auditObj].title} is ${log}`); } } }; if (argv.from && argv.to) { compareReports( getContents(argv.from ".json"), getContents(argv.to ".json") ); } else if (argv.url) { const urlObj = new URL(argv.url); let dirName = urlObj.host.replace("www.", ""); if (urlObj.pathname !== "/") { dirName = dirName urlObj.pathname.replace(/\//g, "_"); } if (!fs.existsSync(dirName)) { fs.mkdirSync(dirName); } launchChromeAndRunLighthouse(argv.url).then(results => { const prevReports = glob(`${dirName}/*.json`, { sync: true }); if (prevReports.length) { dates = []; for (report in prevReports) { dates.push( new Date(path.parse(prevReports[report]).name.replace(/_/g, ":")) ); } const max = dates.reduce(function(a, b) { return Math.max(a, b); }); const recentReport = new Date(max).toISOString(); const recentReportContents = getContents( dirName "/" recentReport.replace(/:/g, "_") ".json" ); compareReports(recentReportContents, results.js); } fs.writeFile( `${dirName}/${results.js["fetchTime"].replace(/:/g, "_")}.json`, results.json, err => { 如果(err)投掷err; } ); }); } 别的 { throw "You haven't passed a URL to Lighthouse"; }
View Gist
下一步
With the completion of this basic Google Lighthouse tool, there's plenty of ways to develop it further.例如:
- Some kind of simple online dashboard that allows non-technical users to run Lighthouse audits and view metrics develop over time. Getting stakeholders behind web performance can be challenging, so something tangible they can interest with themselves could pique their interest.
- Build support for performance budgets, so if a report is generated and performance metrics are slower than they should be, then the tool outputs useful advice on how to improve them (or calls you names).
祝你好运!
以上是构建一个node.js工具来记录和比较Google灯塔报告的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)
