Die Performance einer Website zu verbessern ist ein stetiger Prozess. Neben der Optimierung der bestehenden Seite muss man auch im Blick behalten, dass eine Weiterentwicklung der Seite (z.B. durch neue Features) die Performance nicht wieder verschlechtert. Damit man so eine Verschlechterung nicht erst mitbekommt, wenn der Code bereits online ist, bietet sich eine Performance-Messung schon bei der Entwicklung an. Ich zeige heute am Beispiel von GitLab, wie man Lighthouse bei jedem Commit die Performance der Seite gegen ein hinterlegtes Performance-Budget testen lassen kann, sodass man zeitnah eine Regression bemerkt und einschreiten kann.

Was ist ein Performance-Budget?

Ein Performance-Budget ist eine Festlegung von Schwellenwerten diverser, unterschiedlicher Metriken. Für die Nutzung mit Lighthouse werden diese Werte in einer JSON-Datei gespeichert. Lighthouse misst dann die Metriken und vergleicht die gemessenen Werte mit den hinterlegten in der JSON-Datei. Werden Schwellenwerte über- bzw. unterschritten löst das einen Fehler aus.

In Verbindung mit GitLab CI kann dann definiert werden was bei einem solchen Fehler passiert. Entweder kann ein Fehler zum Abbruch der CI-Pipeline führen oder es kann definiert werden, dass der Performance-Test fehlschlagen darf und nur zu einer Warnung führt.

Fehlgeschlagener Lighthouse-Test, der in einer Pipeline nur eine Warnung hervorruft.

Eine kleine, beispielhafte budget.json-Datei kann z.B. so aussehen:

[
  {   
    "timings": [
      {
        "metric": "cumulative-layout-shift",
        "budget": 0.9
      },{
        "metric": "largest-contentful-paint",
        "budget": 500
      }
    ],
    "resourceSizes": [
      {
        "resourceType":"document",
        "budget":25      
      },{
        "resourceType":"script",
        "budget":165
      },{
        "resourceType":"image",
        "budget":630
      },{
        "resourceType":"stylesheet",
        "budget":32
      },{
        "resourceType":"media",
        "budget":0
      },{
        "resourceType":"font",
        "budget":37
      },{
        "resourceType":"total",
        "budget":889
      }
    ]
  }
]

Lighthouse in GitLab-CI integrieren

Um Lighthouse in GitLab-CI zu integrieren muss die .gitlab-ci.yml etwas erweitert werden:

stages:
  - build
  - deploy
  - performance

build:
  stage: build
  ...

deploy_staging:
  stage: deploy
  ...

lighthouse:
  stage: performance
  needs: ["deploy_staging"]
  allow_failure: true
  image: cypress/browsers:node14.15.0-chrome86-ff82
  script:
    - npm install -g @lhci/cli@0.8.x
    - mkdir lhci
    - lhci autorun
  artifacts:
    when: always
    expose_as: 'Lighthouse Reports'
    paths: ['lhci/']

Die Stages build und deploy sind natürlich nicht zwingend notwendig für Lighthouse, sie dienen hier aber der Verdeutlichung, dass Lighthouse URLs benötigt unter denen der geänderte Code dieses Commits abrufbar ist. Je nach verwendeter Sprache und Architektur der Seite können dafür build- und deploy-Schritte notwendig sein, aber bei einer statischen Seite eventuell auch nicht.

Wichtig ist die performance-Stage. Unter lighthouse finden sich die wichtigen Schritte. Die Angabe eine zwingend benötigten Stage (siehe needs) kann natürlich, wie eben beschrieben, auch unnötig sein. Die Angabe von allow_failure definiert das zuvor beschriebene Verhalten bei auftretenden Fehlern. Wo allow_failure: true nur eine Warnung in der CI-Pipeline ausgibt würde ein allow_failure: false zum Fehlschlagen der Pipeline führen und beispielsweise in einem Merge Request bei einem automatischen „Merge when pipeline succeeds“ das Mergen verhindern.

Ausgabe eines GitLab-CI-Jobs bei dem Lighthouse einen Fehler ausgibt wegen des Überschreitens des LCP-Werts im Performance-Budget
Das Ergebnis eines Lighthouse-Tests bei dem der LCP-Wert aus dem Performance-Budget überschritten wurde.

Das Image (image: cypress/browsers:node14.15.0-chrome86-ff82) mag kurz verwundern, ich habe mich hier an das Beispiel aus der Lighthouse-CI Getting Started-Doku gehalten. Cypress.io ist ein End-To-End-Testing Tool, das neben ihrem eigentlichen Tool auch Docker-Images mit Browsern zur Verfügung stellt, die sich anscheinend auch gut für den Einsatz mit Lighthouse eignen.

Das verwendete Script ist überschaubar. Es wird im Beispiel Lighthouse-CI per npm installiert, dann wird ein Verzeichnis namens „lhci“ angelegt und anschließend wird auch schon Lighthouse-CI mittels lhci autorun gestartet. Die eigentliche Konfiguration von Lighthouse findet in einer extra Datei statt – dazu kommen wir gleich.

Zum Schluss ist noch der Bereich artifacts interessant. Dieser sorgt dafür, dass die Ergebnisse des Lighthouse-Tests nach dem Ablauf des Tasks zur Verfügung stehen und eingesehen werden können. Habt ihr GitLab Pages eingerichtet könnt ihr den Lighthouse Report direkt im Browser ansehen, ansonsten könnt ihr ihn aber auch herunterladen.

Screenshot aus GitLab der die Möglichkeit zum Download und Browsen von Job-Artifacts anzeigt.
Artifacts eines Jobs können in GitLab heruntergeladen oder direkt angesehen werden.

Der Bezeichner unter expose_as taucht dann in einem Merge Request in der Liste der Artifacts auf. Von dort aus kann auch gleich zu den Artifacts gesprungen werden und der jeweilige Lighthouse-Report pro URL und Testlauf angesehen werden.

Ausgabe der Artifacts in einem Merge Request.

Lighthouse-CI konfigurieren

Oben hatte ich es bereits angedeutet, der Befehl lhci autorun wirkt nicht gerade sehr spezifisch. Man kann beim Aufruf von lhci die gesamte Konfiguration als CLI-Parameter übergeben, aber das ist nicht sonderlich übersichtlich. Standardmäßig sucht lhci nach einer Datei namens .lighthouserc.js und liest von dort die Konfiguration ein. Werfen wir einen Blick in die Datei:

module.exports = {
  ci: {
    collect: {
      url: [
        'http://project.staging.mydomain.com',
        'http://project.staging.mydomain.com/hello/foobar'
      ],
      settings: {chromeFlags: '--no-sandbox'},
    },
    assert: {
      budgetsFile: 'budget.json',
    },
    upload: {
      target: 'filesystem',
      outputDir: './lhci'
    },
  },
};

In diesem Minimalbeispiel werden zwei URLs zum Testen übergeben, die Option chromeFlags wird auf --no-sandbox gesetzt, unser zuvor geschriebenes Performance-Budget wird als budget.json übergeben und es wird definiert, dass die Lighthouse-Reports für die Domains bitte im Dateisystem in dem Verzeichnis lhci abgelegt werden sollen. Prinzipiell lässt sich in dieser Datei alles mögliche konfigurieren, was man sonst in Lighthouse auch einstellen kann.

Zusammenfassung

Das ist auch schon alles, was nötig ist. Der Anpassung der .gitlab-ci.yml führt generell die Performance-Stage mit dem Lighthouse-Job ein. Die .lighthouserc.js konfiguriert Lighthouse und übergibt dabei die Schwellenwerte für das Performance-Budget als budget.json nach denen sich entscheidet, ob der Test in der CI-Pipeline erfolgreich war oder nicht, also ob der Commit die Performance verschlechtern würde.

So einfach kann eine dauerhafte, automatische Überwachung der Performance bereits in der Entwicklung sein, um Regression im produktiven Code zu vermeiden und Probleme schnell zu erkennen und anzugehen.