搜尋
首頁web前端css教學構建一個node.js工具來記錄和比較Google燈塔報告

構建一個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命名的目錄中的獨特時間戳文件名保存到文件中。

這意味著現在報告的報告效率更高,並且無論保存多少報告都不會相互覆蓋。

比較燈塔報告

在日常開發期間,當我專注於提高性能時,能夠直接在控制台中快速比較報告的能力,看看我是否朝正確的方向前進可能非常有用。考慮到這一點,此比較功能的要求應該是:

  1. 如果燈塔審核完成後已經存在了同一網站的上一個報告,請自動對其進行比較,並顯示關鍵性能指標的任何更改。
  2. 我還應該能夠比較來自任意兩個網站的任何兩個報告中的關鍵性能指標,而無需生成我可能不需要的新燈塔報告。

應該比較報告的哪些部分?這些是作為任何燈塔報告的一部分收集的數值密鑰性能指標。它們提供了有關網站的客觀和感知性能的見解。

此外,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 = ((to - from) / from) * 100;
  return 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 => {
        if (err) throw 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中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
模擬鼠標運動模擬鼠標運動Apr 22, 2025 am 11:45 AM

如果您曾經在現場演講或課程中必須顯示一個互動動畫,那麼您可能知道它並不總是那麼容易與您的幻燈片進行互動

通過Astro Action和Fuse.js為搜索提供動力通過Astro Action和Fuse.js為搜索提供動力Apr 22, 2025 am 11:41 AM

對於Astro,我們可以在構建過程中生成大部分網站,但是有一小部分服務器端代碼可以使用Fuse.js之類的搜索功能來處理搜索功能。在此演示中,我們將使用保險絲搜索一組個人“書籤”

未定義:第三個布爾值未定義:第三個布爾值Apr 22, 2025 am 11:38 AM

我想在我的一個項目中實現一條通知消息,類似於您在保存文檔時在Google文檔中看到的信息。換句話說,一個

捍衛三元聲明捍衛三元聲明Apr 22, 2025 am 11:25 AM

幾個月前,我正在使用黑客新聞(就像一個人一樣),並且遇到了一篇(現已刪除的)文章,內容涉及不使用if語句。如果您是這個想法的新手(就像我

使用網絡語音API進行多語言翻譯使用網絡語音API進行多語言翻譯Apr 22, 2025 am 11:23 AM

自科幻小說以來,我們就幻想著與我們交談的機器。今天這很普遍。即便如此,製造的技術

JetPack Gutenberg塊JetPack Gutenberg塊Apr 22, 2025 am 11:20 AM

我記得當古騰堡被釋放到核心時,因為那天我在WordCamp我們。現在已經過去了幾個月,所以我想我們越來越多的人

在VUE中創建可重複使用的分頁組件在VUE中創建可重複使用的分頁組件Apr 22, 2025 am 11:17 AM

大多數Web應用程序背後的想法是從數據庫中獲取數據,並以最佳方式將其呈現給用戶。當我們處理數據時

使用'盒子陰影”和剪輯路徑一起使用'盒子陰影”和剪輯路徑一起Apr 22, 2025 am 11:13 AM

讓我們在一個情況下做一些似乎有意義的事情的情況下逐步進行一些逐步,但是您仍然可以用CSS欺騙來完成它。在這個

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。