recherche
Maisoninterface Webtutoriel CSSCréez un outil Node.js pour enregistrer et comparer Google Lighthouse Rapports

Créez un outil Node.js pour enregistrer et comparer Google Lighthouse Rapports

Dans ce didacticiel, je vous montrerai étape par étape comment créer un outil simple dans Node.js pour exécuter Google Lighthouse Audits via la ligne de commande, enregistrer les rapports qu'ils génèrent au format JSON, puis les comparer afin que les performances Web puissent être surveillées à mesure que le site Web se développe et développe.

J'espère que cela pourra servir de bonne introduction à tout développeur intéressé à apprendre comment travailler avec Google Lighthouse par programme.

Mais d'abord, pour les non-initiés…

Qu'est-ce que Google Lighthouse?

Google Lighthouse est l'un des outils les mieux automatisés disponibles sur la courroie d'utilité d'un développeur Web. Il vous permet de auditer rapidement un site Web dans un certain nombre de domaines clés qui peuvent ensemble former une mesure de sa qualité globale. Ce sont:

  • Performance
  • Accessibilité
  • Meilleures pratiques
  • Référencement
  • Application Web progressive

Une fois l'audit terminé, un rapport est ensuite généré sur ce que votre site Web fait bien… et pas si bien, ce dernier ayant l'intention de servir d'indicateur pour vos prochaines étapes pour améliorer la page.

Voici à quoi ressemble un rapport complet.

En plus d'autres diagnostics généraux et des mesures de performances Web, une caractéristique vraiment utile du rapport est que chacun des domaines clés est agrégé en scores codés en couleur entre 0 et 100.

Non seulement cela permet aux développeurs d'évaluer rapidement la qualité d'un site Web sans analyse, mais cela permet également aux gens non techniques tels que les parties prenantes ou les clients de comprendre également.

Par exemple, cela signifie qu'il est beaucoup plus facile de partager la victoire avec Heather du marketing après avoir passé du temps à améliorer l'accessibilité du site Web car elle est plus en mesure d'apprécier l'effort après avoir vu le score d'accessibilité au phare augmenter à 50 points dans le vert.

Mais également, Simon, le chef de projet, peut ne pas comprendre ce que signifie l'index de vitesse ou la première peinture contente, mais quand il voit le rapport du phare montrant le score de performance du site Web au plus profond du rouge, il sait que vous avez toujours du travail à faire.

Si vous êtes dans Chrome ou dans la dernière version d'Edge, vous pouvez exécuter un audit de phare pour vous-même en utilisant Devtools. Voici comment:

Vous pouvez également exécuter un audit de phare en ligne via Pagespeed Insights ou via des outils de performance populaires, tels que WebPageTest.

Cependant, aujourd'hui, nous ne sommes intéressés que par le phare comme module de nœud, car cela nous permet d'utiliser l'outil par programme pour auditer, enregistrer et comparer les mesures de performances Web.

Découvrons comment.

Installation

Tout d'abord, si vous ne l'avez pas déjà, vous aurez besoin de node.js. Il existe un million de façons différentes de l'installer. J'utilise le Homebrew Package Manager, mais vous pouvez également télécharger un installateur directement sur le site Web de Node.js si vous préférez.Ce tutoriel a été écrit avec Node.js V10.17.0 à l'esprit, mais cela fonctionnera très probablement très bien sur la plus grande version publiée au cours des dernières années.

Vous aurez également besoin de Chrome installé, car c'est ainsi que nous exécuterons les audits du phare.

Ensuite, créez un nouveau répertoire pour le projet, puis CD dans la console. Ensuite, exécutez NPM init pour commencer à créer un fichier package.json. À ce stade, je recommanderais simplement de dénigrer la touche Entrée encore et encore pour en sauter autant que possible jusqu'à ce que le fichier soit créé.

Maintenant, créons un nouveau fichier dans le répertoire du projet. J'ai appelé le mien lh.js, mais n'hésitez pas à l'appeler comme vous voulez. Cela contiendra tout JavaScript pour l'outil. Ouvrez-le dans votre éditeur de texte de choix et pour l'instant, écrivez une instruction Console.log.

 Console.log («Hello World»);

Ensuite, dans la console, assurez-vous que votre CWD (répertoire de travail actuel) est votre répertoire de projet et exécutez le nœud lh.js, en remplaçant mon nom de fichier pour tout ce que vous avez utilisé.

Vous devriez voir:

 $ node lh.js
Bonjour le monde

Sinon, vérifiez que l'installation de votre nœud fonctionne et vous êtes certainement dans le bon répertoire de projet.

Maintenant, c'est à l'écart, nous pouvons passer au développement de l'outil lui-même.

Ouverture de chrome avec node.js

Installons la première dépendance de notre projet: Lighthouse lui-même.

 NPM Installer Lighthouse --Save-Dev

Cela crée un répertoire Node_Modules qui contient tous les fichiers du package. Si vous utilisez GIT, la seule chose que vous voudrez faire avec cela est de l'ajouter à votre fichier .gitignore.

Dans lh.js, vous voudrez ensuite supprimer la console de test.log () et importer le module de phare afin que vous puissiez l'utiliser dans votre code. Comme ainsi:

 const lighthouse = require ('phare');

En dessous, vous devrez également importer un module appelé Chrome-Launcher, qui est l'une des dépendances de Lighthouse et permet à Node de lancer Chrome par lui-même afin que l'audit puisse être exécuté.

 const lighthouse = require ('phare');
const chromelaUncher = require ('chrome-launcher');

Maintenant que nous avons accès à ces deux modules, créons un script simple qui ouvre simplement Chrome, exécute un audit de phare, puis imprime le rapport à la console.

Créez une nouvelle fonction qui accepte une URL en tant que paramètre. Parce que nous l'exécuterons en utilisant Node.js, nous pouvons utiliser en toute sécurité la syntaxe ES6 car nous n'avons pas à nous soucier de ces utilisateurs embêtants de l'explorateur Internet.

 const lankchrome = (url) => {

}

Dans la fonction, la première chose que nous devons faire est d'ouvrir Chrome en utilisant le module Chrome-Launcher que nous avons importé et l'envoyant à tout argument passé par le paramètre URL.

Nous pouvons le faire en utilisant sa méthode Launch () et son option de démarrage.

 const lankchrome = url => {
  chromelauncher.launch ({
    Starterl: URL
  });
};

Appeler la fonction ci-dessous et passer une URL de votre choix entraîne l'ouverture de Chrome à l'URL lorsque le script de nœud est exécuté.

 LaunchChrome ('https://www.lukeharrison.dev');

La fonction de lancement renvoie en fait une promesse, qui nous permet d'accéder à un objet contenant quelques méthodes et propriétés utiles.

Par exemple, en utilisant le code ci-dessous, nous pouvons ouvrir Chrome, imprimer l'objet à la console, puis fermer le chrome trois secondes plus tard en utilisant sa méthode kill ().

 const lankchrome = url => {
  chromelauncher
    .lancement({
      Starterl: URL
    })
    .Then (chrome => {
      console.log (chrome);
      setTimeout (() => chrome.kill (), 3000);
    });
};

LaunchChrome ("https://www.lukeharrison.dev");

Maintenant que nous avons compris Chrome, passons au phare.

Exécution du phare programmatique

Tout d'abord, renommeons notre fonction LaunchChrome () à quelque chose de plus reflétant sa fonctionnalité finale: LaunchComRomeandrunLightHouse (). Avec la partie difficile à l'écart, nous pouvons désormais utiliser le module de phare que nous avons importé plus tôt dans le tutoriel.

Dans la fonction du Chrome Launcher, qui ne s'exécute qu'une fois que le navigateur est ouvert, nous passerons le phare de l'argument URL de la fonction et déclencherons un audit de ce site Web.

 const lankchromeandrunlighthouse = url => {
  chromelauncher
    .lancement({
      Starterl: URL
    })
    .Then (chrome => {
      const opts = {
        Port: Chrome.port
      };
      Lighthouse (URL, opts);
    });
};

LaunchChromeandrunLighthouse ("https://www.lukeharrison.dev");

Pour lier l'instance de phare à notre fenêtre Chrome Browser, nous devons passer son port avec l'URL.

Si vous deviez exécuter ce script maintenant, vous appuyez sur une erreur dans la console:

 (Node: 47714) UNHANDLEDPROMISEREINGWARNING: ERREUR: Vous avez probablement plusieurs onglets ouverts à la même origine.

Pour résoudre ce problème, nous avons juste besoin de supprimer l'option Starterl du lanceur de Chrome et de laisser le phare gérer la navigation d'URL à partir d'ici.

 const lankchromeandrunlighthouse = url => {
  chromelauncher.launch (). puis (chrome => {
    const opts = {
      Port: Chrome.port
    };
    Lighthouse (URL, opts);
  });
};

Si vous deviez exécuter ce code, vous remarquerez que quelque chose semble définitivement se produire. Nous n'obtenons tout simplement aucun commentaire dans la console pour confirmer que l'audit du phare a définitivement fonctionné, pas plus que l'instance chromée ne se ferme pas par elle-même.

Heureusement, la fonction Lighthouse () renvoie une promesse qui nous permet d'accéder aux résultats d'audit.

Turons Chrome, puis imprimons ces résultats au terminal au format JSON via la propriété du rapport de l'objet de résultats.

 const lankchromeandrunlighthouse = url => {
  chromelauncher.launch (). puis (chrome => {
    const opts = {
      Port: Chrome.port
    };
    Lighthouse (URL, opts) .Then (résultats => {
      chrome.kill ();
      Console.log (résultats.Report);
    });
  });
};

Bien que la console ne soit pas le meilleur moyen d'afficher ces résultats, si vous deviez les copier dans votre presse-papiers et visiter la visionneuse du rapport du phare, le collage ici affichera le rapport dans toute sa gloire.

À ce stade, il est important de ranger un peu le code pour que la fonction LaunchCromeAndrunLighthouse () renvoie le rapport une fois qu'il a terminé l'exécution. Cela nous permet de traiter le rapport plus tard sans entraîner une pyramide désordonnée de JavaScript.

 const Lighthouse = require ("Lighthouse");
const chromelaUncher = requis ("chrome-launcher");

const lankchromeandrunlighthouse = url => {
  return chromelauncher.launch (). alors (chrome => {
    const opts = {
      Port: Chrome.port
    };
    return Lighthouse (URL, opts) .Then (résultats => {
      return chrome.kill (). alors (() => result.Report);
    });
  });
};

LaunchChromeandrunLighthouse ("https://www.lukeharrison.dev") .Then (résultats => {{
  console.log (résultats);
});

Une chose que vous avez peut-être remarquée, c'est que notre outil ne peut auditer qu'un seul site Web pour le moment. Changeons cela afin que vous puissiez passer l'URL comme argument via la ligne de commande.

Pour éliminer la douleur de travailler avec des arguments en ligne de commande, nous les gérerons avec un package appelé Yargs.

 Installation de NPM - Save-Dev Yargs

Importez-le ensuite en haut de votre script avec Chrome Launcher et Lighthouse. Nous n'avons besoin que de sa fonction Argv ici.

 const lighthouse = require ('phare');
const chromelaUncher = require ('chrome-launcher');
const argv = require ('yargs'). argv;

Cela signifie que si vous deviez passer un argument de ligne de commande dans le terminal comme tel:

 Node lh.js --url https://www.google.co.uk

… Vous pouvez accéder à l'argument dans le script comme tel:

 const url = argv.url // https://www.google.co.uk

Montons notre script pour passer l'argument URL de ligne de commande au paramètre URL de la fonction. Il est important d'ajouter un petit filet de sécurité via l'instruction IF et le message d'erreur au cas où aucun argument n'est passé.

 if (argv.url) {
  LaunchChromeandrunLighthhouse (argv.url) .Then (résultats => {
    console.log (résultats);
  });
} autre {
  Jetez "vous n'avez pas passé d'URL au phare";
}

Tada! Nous avons un outil qui lance Chrome et exécute un audit de phare par programme avant d'imprimer le rapport au terminal au format JSON.

Sauver les rapports de phare

Le fait que le rapport est imprimé à la console n'est pas très utile car vous ne pouvez pas facilement lire son contenu, et ils ne sont pas enregistrés pour une utilisation future. Dans cette section du tutoriel, nous modifierons ce comportement afin que chaque rapport soit enregistré dans son propre fichier JSON.

Pour arrêter les rapports de différents sites Web se mélangeant, nous les organiserons comme:

  • 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

Nous nommerons les rapports avec un horodatage indiquant la date / heure du rapport. Cela ne signifiera pas que deux noms de fichiers de rapport ne seront jamais les mêmes, et cela nous aidera facilement à distinguer les rapports.

Il y a un problème avec Windows qui nécessite notre attention: le côlon (:) est un caractère illégal pour les noms de fichiers. Pour atténuer ce problème, nous remplacerons tous les Colons par des soulignements (_), donc un nom de fichier typique de rapport ressemblera:

  • 2020-01-31T18_18_12.648Z.json

Créer le répertoire

Tout d'abord, nous devons manipuler l'argument de l'URL de ligne de commande afin que nous puissions l'utiliser pour le nom du répertoire.

Cela implique plus que le simple de supprimer le www, car il doit tenir compte des audits exécutés sur des pages Web qui ne se trouvent pas à la racine (par exemple: www.foo.com/bar), car les barres obliques sont des caractères non valides pour les noms de répertoire.

Pour ces URL, nous remplacerons à nouveau les personnages invalides par des soulignements. De cette façon, si vous exécutez un audit sur https://www.foo.com/bar, le nom du répertoire résultant contenant le rapport serait foo.com_bar.

Pour faciliter la gestion des URL, nous utiliserons un module Node.js natif appelé URL. Cela peut être importé comme n'importe quel autre package et sans avoir à l'ajouter à thepackage.json et à le tirer via NPM.

 const lighthouse = require ('phare');
const chromelaUncher = require ('chrome-launcher');
const argv = require ('yargs'). argv;
const url = require ('url');

Ensuite, utilisons-le pour instancier un nouvel objet URL.

 if (argv.url) {
  const urlobj = new URL (argv.url);

  LaunchChromeandrunLighthhouse (argv.url) .Then (résultats => {
    console.log (résultats);
  });
}

Si vous deviez imprimer URLOBJ à la console, vous verriez beaucoup de données URL utiles que nous pouvons utiliser.

 $ node lh.js --url https://www.foo.com/bar
URL {
  href: 'https://www.foo.com/bar',
  Origine: «https://www.foo.com»,
  protocole: 'https:',
  nom d'utilisateur: '',
  mot de passe: '',
  Hôte: «www.foo.com»,
  nom d'hôte: «www.foo.com»,
  port: '',
  PathName: '/ Bar',
  recherche: '',
  SearchParams: UrlSearchParams {},
  Hash: ''
}

Créez une nouvelle variable appelée dirname et utilisez la méthode String Remplace () sur la propriété hôte de notre URL pour se débarrasser du www en plus du protocole HTTPS:

 const urlobj = new URL (argv.url);
Soit dirname = urlobj.host.replace ('www.', '');

Nous avons utilisé le LET ICI, qui, contrairement à const, peut être réaffecté, car nous devons mettre à jour la référence si l'URL a un chemin d'accès, pour remplacer les barres obliques par des traits de soulignement. Cela peut être fait avec un modèle d'expression régulière et ressemble à ceci:

 const urlobj = new URL (argv.url);
Soit dirname = urlobj.host.replace ("www.", "");
if (urlobj.pathname! == "/") {
  dirname = dirname urlobj.pathname.replace (/ \ // g, "_");
}

Maintenant, nous pouvons créer le répertoire lui-même. Cela peut être fait grâce à l'utilisation d'un autre module native Node.js appelé FS (abréviation de «Système de fichiers»).

 const lighthouse = require ('phare');
const chromelaUncher = require ('chrome-launcher');
const argv = require ('yargs'). argv;
const url = require ('url');
const fs = require ('fs');

Nous pouvons utiliser sa méthode mkdir () pour créer un répertoire, mais nous devons d'abord utiliser sa méthode existant () pour vérifier si le répertoire existe déjà, car Node.js allait autrement lancer une erreur:

 const urlobj = new URL (argv.url);
Soit dirname = urlobj.host.replace ("www.", "");
if (urlobj.pathname! == "/") {
  dirname = dirname urlobj.pathname.replace (/ \ // g, "_");
}
if (! fs.existSync (dirname)) {
  fs.mkDiRSync (dirname);
}

Le test du script au point devrait entraîner la création d'un nouveau répertoire. Passer https://www.bbc.co.uk/news en tant qu'argument URL aboutirait à un répertoire nommé bbc.co.uk_news.

Sauver le rapport

Dans la fonction d'alors pour LaunchChromeAndrunLightHouse (), nous voulons remplacer la console existante.log par la logique pour rédiger le rapport sur le disque. Cela peut être fait en utilisant la méthode WriteFile () du module FS.

 LaunchChromeandrunLighthhouse (argv.url) .Then (résultats => {
  fs.writeFile ("report.json", résultats, err => {
    si (err) jetez ERR;
  });
});

Le premier paramètre représente le nom du fichier, le second est le contenu du fichier et le troisième est un rappel contenant un objet d'erreur si quelque chose ne va pas pendant le processus d'écriture. Cela créerait un nouveau fichier appelé report.json contenant l'objet JSON RAPPORT du phare de retour.

Nous devons encore l'envoyer dans le bon répertoire, avec un horodatage comme nom de fichier.

 LaunchChromeandrunLighthhouse (argv.url) .Then (résultats => {
  fs.writeFile (`$ {dirname} / report.json`, résultats, err => {
    si (err) jetez ERR;
  });
});

Ce dernier nous oblige cependant à récupérer en quelque sorte un horodatage du moment où le rapport a été généré. Heureusement, le rapport lui-même capture cela comme point de données et est stocké comme la propriété FetchTime.

Nous avons juste besoin de nous rappeler d'échanger n'importe quel Colons (:) pour les traits de soulignement (_) afin qu'il joue bien avec le système de fichiers Windows.

 LaunchChromeandrunLighthhouse (argv.url) .Then (résultats => {
  fs.writefile (
    `$ {dirname} / $ {résultats [" fetchtime "]. remplace (/: / g," _ ")}. JSON`,
    résultats,
    err => {
      si (err) jetez ERR;
    }
  ));
});

Si vous deviez exécuter ceci maintenant, plutôt qu'un nom de fichier horodomoteur.json, vous verriez probablement une erreur similaire à:

 Non-standledpromisejectionWarning: typeError: Impossible de lire la propriété «remplacer» non défini

Cela se produit parce que Lighthouse renvoie actuellement le rapport au format JSON, plutôt qu'un objet consommable par JavaScript.

Heureusement, au lieu d'analyser le JSON nous-mêmes, nous pouvons simplement demander à Lighthouse de retourner le rapport comme un objet JavaScript ordinaire à la place.

Cela nécessite de modifier la ligne ci-dessous à partir de:

 return chrome.kill (). alors (() => result.Report);

…à:

 return chrome.kill (). alors (() => results.lhr);

Maintenant, si vous réinterrisez le script, le fichier sera nommé correctement. Cependant, lorsqu'il est ouvert, c'est le seul contenu qui sera malheureusement…

 [objet objet]

C'est parce que nous avons maintenant le problème opposé comme auparavant. Nous essayons de rendre un objet JavaScript sans le lancer d'abord dans un objet JSON.

La solution est simple. Pour éviter d'avoir à gaspiller des ressources en analyse ou en tronçant cet énorme objet, nous pouvons retourner les deux types du phare:

 return Lighthouse (URL, opts) .Then (résultats => {
  return chrome.kill (). puis (() => {
    retour {
      js: results.lhr,
      JSON: Résultats.Report
    };
  });
});

Ensuite, nous pouvons modifier l'instance WriteFile à ceci:

 fs.writefile (
  `$ {dirname} / $ {results.js [" fetchtime "]. Remplace (/: / g," _ ")}. JSON`,
  résultats.json,
  err => {
    si (err) jetez ERR;
  }
));

Traté! À la fin de l'audit de Lighthouse, notre outil devrait désormais enregistrer le rapport dans un fichier avec un nom de fichier timestampé unique dans un répertoire nommé d'après l'URL du site Web.

Cela signifie que les rapports sont désormais beaucoup plus efficaces et ne se remplaceront pas les uns les autres, peu importe le nombre de rapports enregistrés.

Comparaison des rapports de phare

Pendant le développement quotidien, lorsque je me concentre sur l'amélioration des performances, la capacité de comparer très rapidement les rapports directement dans la console et de voir si je me dirige dans la bonne direction pourrait être extrêmement utile. Dans cet esprit, les exigences de cette fonctionnalité de comparaison devraient être:

  1. Si un rapport précédent existe déjà pour le même site Web lorsqu'un audit de phare est terminé, effectuez automatiquement une comparaison avec elle et affichez les modifications des mesures de performance clés.
  2. Je devrais également être en mesure de comparer les mesures de performances clés à partir de deux rapports, à partir de deux sites Web, sans avoir à générer un nouveau rapport de phare dont je ne pourrais pas avoir besoin.

Quelles parties d'un rapport doivent être comparées? Ce sont les mesures de performance clés numériques collectées dans le cadre de tout rapport de phare. Ils donnent un aperçu de l'objectif et des performances perçues d'un site Web.

De plus, Lighthouse collecte également d'autres mesures qui ne sont pas répertoriées dans cette partie du rapport mais qui sont toujours dans un format approprié pour être inclus dans la comparaison. Ce sont:

  • Le temps de premier octet - le temps de premier octet identifie l'heure à laquelle votre serveur envoie une réponse.
  • Temps de blocage total - somme de toutes les périodes entre le FCP et le temps à interactif, lorsque la longueur de la tâche dépassait 50 ms, exprimé en millisecondes.
  • Latence d'entrée estimée - la latence d'entrée estimée est une estimation de la durée de votre application pour répondre à la saisie des utilisateurs, en millisecondes, pendant la fenêtre 5S la plus fréquentée de la charge de page. Si votre latence est supérieure à 50 ms, les utilisateurs peuvent percevoir votre application comme laggy.

Comment la comparaison métrique doit-elle être sortie de la console? Nous allons créer une comparaison simple basée sur le pourcentage en utilisant les anciennes et nouvelles métriques pour voir comment ils sont passés d'un rapport à l'autre.

Pour permettre un balayage rapide, nous allons également coder les métriques individuelles en fonction de leur plus rapide, plus lent ou inchangé.

Nous viserons cette sortie:

Comparez le nouveau rapport avec le rapport précédent

Commençons par la création d'une nouvelle fonction appelée comparaison () juste en dessous de notre fonction LaunchComEndrunLightHouse (), qui contiendra toute la logique de comparaison. Nous allons lui donner deux paramètres - d'après et à accepter les deux rapports utilisés pour la comparaison.

Pour l'instant, en tant qu'espace réservé, nous allons simplement imprimer des données de chaque rapport à la console pour valider qu'il les reçoit correctement.

 const ComparreEports = (de, à, à) => {
  console.log (de ["finurl"] "" de ["fetchtime"]);
  console.log (à ["finurl"] "" à ["fetchtime"]);
};

Comme cette comparaison commencerait après la création d'un nouveau rapport, la logique pour exécuter cette fonction devrait se situe dans la fonction alors pour LaunchCromeAndrunLightHouse ().

Si, par exemple, vous avez 30 rapports assis dans un répertoire, nous devons déterminer lequel est le plus récent et le définir comme le rapport précédent contre lequel le nouveau sera comparé. Heureusement, nous avons déjà décidé d'utiliser un horodatage comme nom de fichier pour un rapport, donc cela nous donne quelque chose avec lequel travailler.

Tout d'abord, nous devons collecter tous les rapports existants. Pour faciliter ce processus, nous allons installer une nouvelle dépendance appelée glob, ce qui permet la correspondance de motifs lors de la recherche de fichiers. Ceci est essentiel car nous ne pouvons pas prédire combien de rapports existeront ou comment ils seront appelés.

L'installez comme toute autre dépendance:

 NPM Install Glob --Save-Dev

Puis l'importez-le en haut du fichier de la même manière que d'habitude:

 const lighthouse = require ('phare');
const chromelaUncher = require ('chrome-launcher');
const argv = require ('yargs'). argv;
const url = require ('url');
const fs = require ('fs');
const glob = require ('glob');

Nous utiliserons Glob pour collecter tous les rapports dans le répertoire, dont nous connaissons déjà le nom de la variable Dirname. Il est important de définir son option de synchronisation sur true car nous ne voulons pas que l'exécution de JavaScript se poursuive jusqu'à ce que nous sachions combien d'autres rapports existent.

 LaunchChromeandrunLighthhouse (argv.url) .Then (résultats => {
  const prevReports = glob (`$ {dirname} / *. json`, {
    Sync: vrai
  });

  // et al

});

Ce processus renvoie un tableau de chemins. Donc, si le répertoire du rapport ressemblait à ceci:

  • lukeharrison.dev
    • 2020-01-31T10_18_12.648Z.json
    • 2020-01-31T10_18_24.110Z.json

… Alors le tableau qui en résulterait ressemblerait à ceci:

 [
 'lukeharrison.dev/2020-01-31t10_18_12.648z.json',
 'lukeharrison.dev/2020-01-31t10_18_24.110z.json'
]]

Parce que nous ne pouvons effectuer une comparaison que si un rapport précédent existe, utilisons ce tableau comme conditionnel pour la logique de comparaison:

 const prevReports = glob (`$ {dirname} / *. json`, {
  Sync: vrai
});

if (prevReports.length) {
}

Nous avons une liste de chemins de fichier de rapport et nous devons comparer leurs noms de fichiers horodgiens pour déterminer lequel est le plus récent.

Cela signifie que nous devons d'abord collecter une liste de tous les noms de fichiers, réduire toutes les données non pertinentes telles que les noms de répertoires et prendre soin de remplacer les traits de soulignement (_) avec des Colons (:) pour les transformer en dates valides. La façon la plus simple de le faire est d'utiliser Path, un autre module natif Node.js.

 const path = require ('path');

Passer le chemin comme un argument à sa méthode d'analyse, comme ainsi:

 path.parse ('lukeharrison.dev/2020-01-31t10_18_24.110z.json');

Renvoie cet objet utile:

 {
  racine: '',
  dir: 'lukeharrison.dev',
  base: '2020-01-31T10_18_24.110z.json',
  ext: '.json',
  Nom: '2020-01-31T10_18_24.110Z'
}

Par conséquent, pour obtenir une liste de tous les noms de fichiers d'horodatage, nous pouvons le faire:

 if (prevReports.length) {
  dates = [];
  pour (rapport dans les prevraports) {
    dates.push (
      Nouvelle date (path.parse (prevReports [rapport]). name.replace (/ _ / g, ":"))
    ));
  }
}

Ce qui encore une fois si notre répertoire ressemblait:

  • lukeharrison.dev
    • 2020-01-31T10_18_12.648Z.json
    • 2020-01-31T10_18_24.110Z.json

Entraînerait:

 [
 «2020-01-31T10: 18: 12.648Z»,
 '2020-01-31T10: 18: 24.110Z'
]]

Une chose utile à propos des dates est qu'ils sont intrinsèquement comparables par défaut:

 const alpha = new Date ('2020-01-31');
const bravo = new Date ('2020-02-15');

console.log (alpha> bravo); // FAUX
console.log (bravo> alpha); // vrai

Ainsi, en utilisant une fonction de réduction, nous pouvons réduire notre éventail de dates jusqu'à ce que les restes les plus récents:

 dates = [];
pour (rapport dans les prevraports) {
  dates.push (new Date (path.parse (prevReports [rapport]). name.replace (/ _ / g, ":")));
}
const max = dates.reduce (fonction (a, b) {
  return math.max (a, b);
});

Si vous deviez imprimer le contenu de Max à la console, cela jetterait un horodat Unix, alors maintenant, nous devons simplement ajouter une autre ligne pour convertir notre date la plus récente au format ISO correct:

 const max = dates.reduce (fonction (a, b) {
 return math.max (a, b);
});
const reterReport = new Date (max) .toiSoString ();

En supposant que ce sont la liste des rapports:

  • 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

La valeur de la réduction serait 2020-01-31T23: 56: 50.959Z.

Maintenant que nous connaissons le rapport le plus récent, nous devons ensuite extraire son contenu. Créez une nouvelle variable appelée récentReportContents sous la variable Récente et attribuez-le une fonction vide.

Comme nous le savons, cette fonction devra toujours s'exécuter, plutôt que de l'appeler manuellement, il est logique de le transformer en une IFFE (expression de fonction immédiatement invoquée), qui s'exécutera par elle-même lorsque l'analyseur JavaScript l'atteint. Ceci est signifié par la parenthèse supplémentaire:

 const reterReportContents = (() => {

}) ();

Dans cette fonction, nous pouvons retourner le contenu du rapport le plus récent en utilisant la méthode readFilesYC () du module FS natif. Parce que ce sera au format JSON, il est important de l'analyser dans un objet JavaScript ordinaire.

 const reterReportContents = (() => {
  const Output = fs.readfilesync (
    dirname "/" recentReport.replace (/: / g, "_") ".json",
    "UTF8",
    (err, résultats) => {
      Résultats de retour;
    }
  ));
  return JSON.Parse (sortie);
}) ();

Et puis, il s'agit d'appeler la fonction CompareReports () et de passer à la fois le rapport actuel et le rapport le plus récent comme arguments.

 ComparreReports (RécentReportContents, results.js);

À l'heure actuelle, cela imprime quelques détails sur la console afin que nous puissions tester les données du rapport sur OK:

 https://www.lukeharrison.dev/ 2020-02-01T00: 25: 06.918Z
https://www.lukeharrison.dev/ 2020-02-01T00: 25: 42.169z

Si vous obtenez des erreurs à ce stade, essayez de supprimer des fichiers ou des rapports Report.json sans contenu valide de plus tôt dans le tutoriel.

Comparez deux rapports

L'exigence clé restante était la possibilité de comparer deux rapports de deux sites Web. La façon la plus simple de mettre en œuvre cela serait de permettre à l'utilisateur de passer les chemins de fichier de rapport complet en tant que arguments de ligne de commande que nous enverrons ensuite à la fonction comparableports ().

Dans la ligne de commande, cela ressemblerait à:

 Node LH.JS --From Lukeharrison.dev/2020-02-01T00:25:06.918Z --pas CNN.com/2019-16T15:12:07.169Z

La réalisation de ceci nécessite la modification de l'instruction conditionnelle IF qui vérifie la présence d'un argument de ligne de commande d'URL. Nous ajouterons un chèque supplémentaire pour voir si l'utilisateur vient de passer un chemin de et sur le chemin, sinon vérifiez l'URL comme auparavant. De cette façon, nous empêcherons un nouvel audit de phare.

 if (argv.from && argv.to) {

} else if (argv.url) {
 // et al
}

Extraitons le contenu de ces fichiers JSON, analysons-les dans des objets JavaScript, puis les transmettons à la fonction comparableports ().

Nous avons déjà analysé JSON auparavant lors de la récupération du rapport le plus récent. Nous pouvons simplement extrapoler cette fonctionnalité dans sa propre fonction d'assistance et l'utiliser dans les deux endroits.

En utilisant la fonction récentReportContents () comme une base, créez une nouvelle fonction appelée getContents () qui accepte un chemin de fichier comme argument. Assurez-vous que ce n'est qu'une fonction régulière, plutôt qu'une IFFE, car nous ne voulons pas qu'elle s'exécute dès que l'analyseur JavaScript le trouve.

 const getContents = pathstr => {
  const Output = fs.readfilesync (pathstr, "utf8", (err, résultats) => {
    Résultats de retour;
  });
  return JSON.Parse (sortie);
};

const ComparreEports = (de, à, à) => {
  console.log (de ["finurl"] "" de ["fetchtime"]);
  console.log (à ["finurl"] "" à ["fetchtime"]);
};

Mettez ensuite à jour la fonction récentReportContents () pour utiliser cette fonction d'assistance extrapolée à la place:

 const reterReportContents = getContents (dirname '/' recentReport.replace (/: / g, '_') '.json');

De retour dans notre nouveau conditionnel, nous devons transmettre le contenu des rapports de comparaison à la fonction comparableports ().

 if (argv.from && argv.to) {
  comparaison (
    getContents (argv.from ".json"),
    getContents (argv.to ".json")
  ));
}

Comme avant, cela devrait imprimer quelques informations de base sur les rapports de la console pour nous faire savoir que tout fonctionne bien.

 Node LH.JS --From lukeharrison.dev/2020-01-31t23_24_41.786z --to lukeharrison.dev/2020-02-01t11_16_25.221z

Conduirait à:

 https://www.lukeharrison.dev/ 2020-01-31T23_24_41.786Z
https://www.lukeharrison.dev/ 2020-02-01T11_16_25.221Z

Logique de comparaison

Cette partie du développement implique la logique de comparaison de la construction pour comparer les deux rapports reçus par la fonction CompareReports ().

Dans l'objet que le phare renvoie, il y a une propriété appelée audits qui contient un autre objet répertoriant les mesures de performances, les opportunités et les informations. Il y a beaucoup d'informations ici, dont une grande partie ne nous intéresse pas aux fins de cet outil.

Voici l'entrée pour la première peinture contente, l'une des neuf mesures de performance que nous souhaitons comparer:

 "Presque de premier contenu": {
  "id": "Presque premièrement",
  "Titre": "First Contentful Paint",
  "Description": "La première peinture contente marque le temps auquel le premier texte ou l'image est peint. [En savoir plus] (https://web.dev/first-contentful-paint).",
  "Score": 1,
  "scoredisplaymode": "numérique",
  "NumericValue": 1081.661,
  "DisplayValue": "1.1 S"
}

Créez un tableau répertoriant les clés de ces neuf mesures de performance. Nous pouvons l'utiliser pour filtrer l'objet d'audit:

 const ComparreEports = (de, à, à) => {
  const MetricFilter = [
    "Presque de premier contenu",
    "Presque de premier plan",
    "index de vitesse",
    "Estimation-Input-latence",
    "temps de blocage total",
    "Max-potentiel-fid",
    "temps pour premier octet",
    "First-Cpu-Idle",
    "interactif"
  ]]
};

Ensuite, nous allons parcourir l'un des objets Audits du rapport, puis référer son nom par rapport à notre liste de filtres. (Peu importe quel objet d'audit, car ils ont tous les deux la même structure de contenu.)

Si c'est là, alors brillant, nous voulons l'utiliser.

 const MetricFilter = [
  "Presque de premier contenu",
  "Presque de premier plan",
  "index de vitesse",
  "Estimation-Input-latence",
  "temps de blocage total",
  "Max-potentiel-fid",
  "temps pour premier octet",
  "First-Cpu-Idle",
  "interactif"
]]

for (let auditobj dans ["Audits"]) {
  if (MetricFilter. y compris (AuditObj)) {
    Console.log (AuditOBJ);
  }
}

Cette console.log () imprimerait les touches ci-dessous à la console:

 peinture de premier ordre
peinture de premier plan
index de vitesse
à la latence à l'entrée estimée
temps de blocage total
max-potentiel-fid
temps de premier octet
premier processeur
interactif

Ce qui signifie que nous utiliserions à partir de [«audits»] [auditobj] .numericValue et à [«audits»] [AuditObj] .numericValue respectivement dans cette boucle pour accéder aux mesures elles-mêmes.

Si nous devions les imprimer sur la console avec la clé, cela entraînerait une sortie comme ceci:

 Pressage de premier contenu 1081.661 890.774
Pressage de premier ordre 1081.661 954.774
Speed-Index 15576.70313351777 1098.622294504341
Estimé-INFUT-LATENCE 12.8 12.8
Temps de blocage total 59 31,5
max-potentiel-fid 153 102
Temps-temps 16.859999999999985 16.096000000000004
Premier-CPU-IDLE 1704.8490000000002 1918.774
Interactif 2266.2835 2374.3615

Nous avons toutes les données dont nous avons besoin maintenant. Nous devons simplement calculer la différence en pourcentage entre ces deux valeurs, puis la connecter à la console à l'aide du format codé couleur décrit précédemment.

Savez-vous comment calculer la variation en pourcentage entre deux valeurs? Moi non plus. Heureusement, le moteur de recherche monolithe préféré de tout le monde est venu à la rescousse.

La formule est:

 ((De - à) / de) x 100

Disons donc que nous avons un indice de vitesse de 5,7 s pour le premier rapport (de), puis une valeur de 2,1 s pour le second (à). Le calcul serait:

 5,7 - 2,1 = 3,6
3,6 / 5,7 = 0,63157895
0,63157895 * 100 = 63.157895

Arronner à deux décimales entraînerait une diminution de l'indice de vitesse de 63,16%.

Mettons cela dans une fonction d'assistance à l'intérieur de la fonction CompareReports (), en dessous du tableau MetricFilter.

 const calcpercentagediff = (de, à) => {
  const per = ((à - de) / de) * 100;
  retour math.round (par * 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. Ça va faire.

 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";
      } autre {
        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(() => {
        retour {
          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",
    "interactif"
  ]]

  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";
        } autre {
          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 => {
        si (err) jetez ERR;
      }
    ));
  });
} autre {
  throw "You haven't passed a URL to Lighthouse";
}

View Gist

Étapes suivantes

With the completion of this basic Google Lighthouse tool, there's plenty of ways to develop it further. Par exemple:

  • 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).

Bonne chance!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Style CSS de niveau supérieur pour les curseursStyle CSS de niveau supérieur pour les curseursApr 23, 2025 am 11:04 AM

Les curseurs personnalisés avec CSS sont excellents, mais nous pouvons faire passer les choses au niveau supérieur avec JavaScript. À l'aide de JavaScript, nous pouvons passer entre les états du curseur, placer du texte dynamique dans le curseur, appliquer des animations complexes et appliquer des filtres.

Worlds Collide: Détection de collision de l'ordre clé à l'aide de requêtes de styleWorlds Collide: Détection de collision de l'ordre clé à l'aide de requêtes de styleApr 23, 2025 am 10:42 AM

Les animations CSS interactives avec des éléments se ricochent semblent plus plausibles en 2025. Bien qu'il soit inutile de mettre en œuvre Pong dans CSS, la flexibilité et la puissance croissantes de CSS renforcent le soupçon de Lee selon lequel un jour, ce sera un jour

Utilisation du filtre de fond CSS pour les effets d'interface utilisateurUtilisation du filtre de fond CSS pour les effets d'interface utilisateurApr 23, 2025 am 10:20 AM

Conseils et astuces sur l'utilisation de la propriété CSS Back-Filter pour styliser les interfaces utilisateur. Vous apprendrez à superposer des filtres en toile de fond entre plusieurs éléments et à les intégrer à d'autres effets graphiques CSS pour créer des conceptions élaborées.

Smil sur?Smil sur?Apr 23, 2025 am 09:57 AM

Eh bien, il s'avère que les fonctionnalités d'animation intégrées de SVG n'ont jamais été obsolètes comme prévu. Bien sûr, CSS et JavaScript sont plus que capables de porter la charge, mais il est bon de savoir que Smil n'est pas mort dans l'eau comme précédemment

'Pretty' est dans l'œil du spectateur'Pretty' est dans l'œil du spectateurApr 23, 2025 am 09:40 AM

Yay, laissez-le saut pour le wat-wrap: joli atterrissage dans la technologie safari aperçu! Mais méfiez-vous qu'il est différent de la façon dont il fonctionne dans les navigateurs de chrome.

CSS-Tricks raconte xliiiCSS-Tricks raconte xliiiApr 23, 2025 am 09:35 AM

Cette mise à jour de CSS-Tricks met en évidence des progrès significatifs dans l'Almanac, des apparitions de podcast récentes, un nouveau guide CSS Counter et l'ajout de plusieurs nouveaux auteurs contribuant un contenu précieux.

La fonctionnalité @Apply de Tailwind est meilleure qu'il n'y paraîtLa fonctionnalité @Apply de Tailwind est meilleure qu'il n'y paraîtApr 23, 2025 am 09:23 AM

La plupart du temps, les gens présentent la fonctionnalité de Tailwind avec l'un des utilitaires à propriété unique de Tailwind (qui modifie une seule déclaration CSS). Lorsqu'il est présenté de cette façon, @Apply ne semble pas du tout prometteur. So évidence

Se sentir comme je n'ai pas de libération: un voyage vers les déploiements sainsSe sentir comme je n'ai pas de libération: un voyage vers les déploiements sainsApr 23, 2025 am 09:19 AM

Le déploiement comme un idiot se résume à un décalage entre les outils que vous utilisez pour déployer et la récompense en complexité réduite par rapport à la complexité ajoutée.

See all articles

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Outils chauds

Listes Sec

Listes Sec

SecLists est le compagnon ultime du testeur de sécurité. Il s'agit d'une collection de différents types de listes fréquemment utilisées lors des évaluations de sécurité, le tout en un seul endroit. SecLists contribue à rendre les tests de sécurité plus efficaces et productifs en fournissant facilement toutes les listes dont un testeur de sécurité pourrait avoir besoin. Les types de listes incluent les noms d'utilisateur, les mots de passe, les URL, les charges utiles floues, les modèles de données sensibles, les shells Web, etc. Le testeur peut simplement extraire ce référentiel sur une nouvelle machine de test et il aura accès à tous les types de listes dont il a besoin.

PhpStorm version Mac

PhpStorm version Mac

Le dernier (2018.2.1) outil de développement intégré PHP professionnel

Version Mac de WebStorm

Version Mac de WebStorm

Outils de développement JavaScript utiles

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

DVWA

DVWA

Damn Vulnerable Web App (DVWA) est une application Web PHP/MySQL très vulnérable. Ses principaux objectifs sont d'aider les professionnels de la sécurité à tester leurs compétences et leurs outils dans un environnement juridique, d'aider les développeurs Web à mieux comprendre le processus de sécurisation des applications Web et d'aider les enseignants/étudiants à enseigner/apprendre dans un environnement de classe. Application Web sécurité. L'objectif de DVWA est de mettre en pratique certaines des vulnérabilités Web les plus courantes via une interface simple et directe, avec différents degrés de difficulté. Veuillez noter que ce logiciel