Developed a customized version of Lighthouse for the front-end performance monitoring platform

I. Review above

In Lighthouse, we introduced the basic concepts and usages of the self-developed front-end performance monitoring platform. In this article, we continue to explore the path of customizing Lighthouse by using Node modules.

2. Details about Lighthouse Configuration

The basic uses of Lighthouse are as follows:

const lighthouse = require('lighthouse'); . lighthouse(url, options, config); .Copy the code

The lighthouse method accepts three parameters: URL, options, and config. The url is the url to be detected. The remaining two parameters are as follows:

2.1 the options

Options is the runtime configuration of Lighthouse. It is an object with the following main parameters:

port? : number;// Chrome portlogLevel? :'silent' | 'error' | 'info' | 'verbose'; // Log leveloutput? :'json' | 'html'| 'csv'; // Report output formatonlyAudits? : string[] |null; // Execution-only censoronlyCategories? : string[] |null; // Only review categories performedskipAudits? : string[] |null; // Skip the censorthrottlingMethod? :'devtools' | 'simulate' | 'provided'; // Network throttling
throttling? ThrottlingSettings; // Network analog controlemulatedFormFactor? :'mobile' | 'desktop' | 'none'; // Simulate the devicelocale? : Locale;/ / language
Copy the code

For example, if we want to check Baidu’s performance, we can configure it as follows:

const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

(async() = > {const fs = require('fs');
  const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless']});const options = {
    logLevel: 'info'.output: 'html'.onlyCategories: ['performance'].port: chrome.port,
  };

  const runnerResult = await lighthouse('https://www.baidu.com', options);
  const reportHtml = runnerResult.report;
  fs.writeFileSync('lhreport.html', reportHtml);

  awaitchrome.kill(); }) ();Copy the code

The results are as follows:

2.2 the config

For general scenarios, the detection capabilities provided by Lighthouse alone are sufficient. If we want to implement specific metrics on top of Lighthouse, we need to use the third parameter config, which gives us the ability to extend our detection. Config is also an object and contains the following parameters:

extends: 'lighthouse:default' | undefined; // Whether to inherit the default configuration
settings: Object | undefined; // Run time configuration
passes: Object[]; / / collector
audits: string[]; / / review
categories: Object | undefined; / / class
groups: Object | undefined; / / group
Copy the code

extends

This property determines whether the default configuration of Lighthouse is inherited. It has two values, ‘Lighthouse :default’ and’ undefined ‘. This property gives you all the built-in collector and censor capabilities of Lighthouse. Custom collectors and censors can only be executed without inheritance.

settings

This property controls the runtime configuration of Lighthouse. The configuration is basically the same as the options parameter above. It is used to simulate network/device parameters and determine which censors to run.

PS: Earlier we talked about how the second arguments to Lighthouse can also be set to onlyCategories, onlyAudits, etc. What happens when options and config are configured differently?

For example:

await lighthouse(
  url,
  {
    onlyCategories: ['performance'],}, {extends: 'lighthouse:default'.settings: {
      onlyCategories: ['seo'],}});Copy the code

Practice is the only criterion to test the truth. We have configured onlyCategories in options and config, but the two values are different, and the final execution result is performance. That is to say, when options and config conflict, Options prevail.

passes

This property controls how the requested URL is loaded and what information about the page is collected at load time, the collector we talked about earlier. In Settings onlyAudits, if you have custom audits, you need to write them in. Audits handle collector passes, passes accepts an array, and each of the passes accesses an object, and passes each item in the array causes the page to load once (for example, Four entries in passes will load the page four times), so be careful to add more than one item here so as not to prolong the run time.

Note: Each entry in passes needs to have corresponding audits or errors will be reported.

Example:

{
  passes: [{passName: 'fastPass'.gatherers: ['fast-gatherer'],}, {passName: 'slowPass'.recordTrace: true.useThrottling: true.networkQuietThresholdMs: 5000.gatherers: ['slow-gatherer'],},]; }Copy the code

audits

This property is used to control which custom reviewers are used to generate the report. This property accepts an array of strings that correspond to passes, which is that custom censors must have the corresponding collector.

Example:

{
  audits: ['first-contentful-paint'.'byte-efficiency/uses-optimized-images'];
}
Copy the code

categories

This attribute is used to classify the results reviewed by the censor. After setting a category item, you can see the information and detection results of the category item in the generated report, as well as the review items and corresponding detection results in the category. This property is an object where the auditRefs property accepts an array, each item of which is an object, containing the censors contained in the category, their weights, and grouping information. The final evaluation result for this category is also calculated based on each reviewer and its weight.

Example:

{
  categories: {
    performance: {
      title: 'Performance'.description: 'This category judges your performance'.auditRefs: [{id: 'first-meaningful-paint'.weight: 2.group: 'metrics'},
        {id: 'first-contentful-paint'.weight: 3.group: 'metrics'},
        {id: 'interactive'.weight: 5.group: 'other'},],}}}Copy the code

groups

This attribute is used to group items under a category. This attribute takes an object, including the group name and group description, corresponding to the group field in Categories.

The sample

{
  groups: {
    metrics: {
      title: 'Metrics'.description: 'These metrics encapsulate your web app's performance across a number of dimensions.'}}}Copy the code

Complete configuration Example

{
  extends: 'lighthouse:default'.passes: [{
    passName: 'defaultPass'.gatherers: [
      'searchable-gatherer',]}],audits: [
    'searchable-audit',].categories: {
    mysite: {
      title: 'My site metrics'.description: 'Metrics for our super awesome site'.auditRefs: [{id: 'searchable-audit'.weight: 1.group: 'metrics'},],},groups: {
      'metrics': {
        title: 'Metrics'.description: 'These metrics encapsulate your web app's performance across a number of dimensions.'},}},}Copy the code

Customize the collector and censor

Finally, we enter the part of customization. Suppose a scenario, we want to get all loaded resources on the page, and calculate the score of resource transmission by analyzing the transmission time of resources. In the browser, you can open up the console and see the resource loading information in the Network, but what does that do in Lighthouse?

3.1 Customizing the Collector

First, to collect something, we define a collector. Lighthouse provides a standard log collector. You can write your own log collector based on the standard log collector. We use beforePass and afterPass in our log collector. AfterPass determines what to collect once the page is loaded. Lighthouse communicates with the browser through the driver, which provides the evaluateAsync method that executes methods within the page and passes the results to the reviewer.

// resource-gatherer.js
const Gatherer = require('lighthouse').Gatherer; // The standard collector of Lighthouse is introduced
class ResourceGatherer extends Gatherer {
  afterPass(options) {
    const driver = options.driver;
    return driver
      .evaluateAsync('JSON.stringify(window.performance.getEntries())')
      .then((loadMetrics) = > {
        if(! loadMetrics) {throw new Error('Resource unavailable');
        }
        returnloadMetrics; }); }}module.exports = ResourceGatherer;
Copy the code

3.2 Custom censors

Like the collector, Lighthouse provides a standard reviewer. We can write our own reviewer based on the standard reviewer. In our reviewer, we usually use two methods: Meta and audit. Of particular attention are the requiredArtifacts field and the ID field, whose values correspond to the appropriate collector and whose ids correspond to the contents of the corresponding audits arrays in the Config file. The Audit method returns an object containing the final result of the audit, including fields like Score and Details.

// resource-audit.js
const Audit = require('lighthouse').Audit; // The introduction of lighthouse's standard censor
class ResourceAudit extends Audit {
  static get meta() {
    return {
      id: 'resource-audit'.// Correspond to audits arrays
      title: 'Resource Information'.failureTitle: 'Resource load failed'.description: 'Show all resources'.requiredArtifacts: ['ResourceGatherer'].// The corresponding collector
    };
  }
  static audit(artifacts) {
    const loadMetrics = JSON.parse(artifacts.ResourceGatherer); // Get the collected content
    if(! loadMetrics.length) {return {
        numericValue: 0.score: 1.displayValue: 'No list found'}; }const score100Timing = 1000;
    const durations = loadMetrics.map((d) = > d.duration);
    const duration =
      durations.reduce((prev, next) = > prev + next, 0) / durations.length;
    const scores = durations.map((d) = > Math.min(score100Timing / d, 1)); // Calculate the score for each item
    const score = scores.reduce((prev, next) = > prev + next, 0) / scores.length; // Calculate the total score
    return {
      numericValue: duration, / / values
      score, / / score
      details: {
        items: loadMetrics, / / details
      },
      displayValue: `Query render avarage timing is The ${parseInt(
        duration,
        10
      )}ms`}; }}module.exports = ResourceAudit;
Copy the code

3.3 Configuring a Custom Collector and Censor

// config.js
module.exports = {
  extends: 'lighthouse:default'.// Inherit the default configuration
  settings: {
    onlyAudits: [
      'resource-audit'.// Show only our custom censors],},passes: [{passName: 'defaultPass'.gatherers: [
        'resource-gatherer'.// Resource-gatherer.js in the same directory],},],audits: [
    'resource-audit'.// resource-audit.js in the same directory].categories: {
    timing: {
      title: 'Resource Details'.description: 'Show all the resources loaded on the page'.auditRefs: [{'resource-audit'.weight: 1.group: 'metrics'},],}},groups: {
    metrics: {
      title: 'resources'.description: 'Resource taking too long to load',}};Copy the code

3.4 Using Lighthouse

const fs = require('fs');
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const config = require(`./config`);

(async() = > {const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless']});const options = {
    logLevel: 'info'.output: 'html'.port: chrome.port,
    formFactor: 'desktop'};const runnerResult = await lighthouse(
    'https://www.baidu.com/',
    options,
    config // Custom configuration
  );

  const reportHtml = runnerResult.report;
  fs.writeFileSync('lhreport.html', reportHtml);

  awaitchrome.kill(); }) ();Copy the code

3.5 Result Presentation

Show only our custom censors

Show the default and our custom censors

Fourth, the end

This article mainly introduces how to use Lighthouse’s custom collector and censor to detect special indicators of web pages. You can use it to your heart’s content to implement various detection capabilities. However, Lighthouse can only detect a specific website directly. This is where the headless browser power comes in. The next article will cover the basic uses of Puppeteer.

Reference links:

  • Github.com/GoogleChrom…
  • Github.com/GoogleChrom…

At present, we are responsible for the research and development of user behavior analysis system, which is the most used in the game industry. At the same time, we are also actively exploring new technology and new fields in the front end. If you are interested in games, big data, visualization, engineering, full stack and other aspects, welcome to join us and create a better future!

Email address:[email protected]