Netzwerknutzlasten mit gzip reduzieren und komprimieren

In diesem Codelab wird untersucht, wie durch das Komprimieren und Komprimieren des JavaScript-Bundles für die folgende Anwendung die Seitenleistung verbessert wird, indem die Anfragegröße der Anwendung reduziert wird.

App – Screenshot

Messen

Bevor Sie Optimierungen hinzufügen, sollten Sie immer zuerst den aktuellen Status der Anwendung analysieren.

  • Wenn Sie sich eine Vorschau der Website ansehen möchten, klicken Sie auf App ansehen und dann auf Vollbild Vollbild.

Mit dieser App, die auch im Codelab "Remove unused code" beschrieben wurde, können Sie für Ihr Lieblingskätzchen stimmen. 🐈

Sehen wir uns nun an, wie groß diese Anwendung ist:

  1. Drücken Sie „Strg + Umschalttaste + J“ (oder „Befehlstaste + Wahltaste + J“ auf einem Mac), um die Entwicklertools zu öffnen.
  2. Klicken Sie auf den Tab Netzwerk.
  3. Klicken Sie das Kästchen Cache deaktivieren an.
  4. Aktualisieren Sie die App.

Ursprüngliche Bundle-Größe im Steuerfeld "Netzwerk"

Obwohl im Codelab "Nicht verwendeten Code entfernen" bereits große Fortschritte gemacht wurden, um die Bundle-Größe zu reduzieren, sind 225 KB immer noch recht groß.

Minimierung

Betrachten Sie den folgenden Codeblock.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Wenn diese Funktion in einer eigenen Datei gespeichert wird, beträgt die Dateigröße etwa 112 B (Byte).

Wenn alle Leerzeichen entfernt werden, sieht der resultierende Code so aus:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

Die Dateigröße würde nun etwa 83 B betragen. Wenn er durch Reduzieren der Länge des Variablennamens und Ändern einiger Ausdrücke weiter verzerrt wird, kann der endgültige Code so aussehen:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

Die Dateigröße erreicht jetzt 62 B.

Mit jedem Schritt wird der Code schwieriger zu lesen. Das JavaScript-Modul des Browsers interpretiert diese jedoch jeweils auf die gleiche Weise. Die Vorteile einer solchen Verschleierung von Code können dazu beitragen, kleinere Dateigrößen zu erreichen. 112 B war eigentlich nicht viel für den Anfang, aber die Größe verringerte sich dennoch um 50 %.

In dieser Anwendung wird webpack Version 4 als Modul-Bundler verwendet. Die jeweilige Version finden Sie unter package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

Version 4 reduziert das Bundle bereits standardmäßig im Produktionsmodus. Dabei wird das Plug-in TerserWebpackPlugin für Terser verwendet. Terser ist ein beliebtes Tool zur Komprimierung von JavaScript-Code.

Wenn Sie eine Vorstellung davon bekommen möchten, wie der komprimierte Code aussieht, klicken Sie im Bereich Netzwerk der Entwicklertools auf main.bundle.js. Klicken Sie jetzt auf den Tab Response (Antwort).

Antwort minimieren

Der Code wird in seiner endgültigen Form, reduziert und manipuliert, im Antworttext angezeigt. Um herauszufinden, wie groß das Bundle möglicherweise gewesen wäre, wenn es nicht minimiert wurde, öffnen Sie webpack.config.js und aktualisieren Sie die mode-Konfiguration.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Aktualisieren Sie die Anwendung und sehen Sie sich die Bundle-Größe noch einmal im Bereich Netzwerk der Entwicklertools an.

Paketgröße von 767 KB

Das ist ein ziemlich großer Unterschied! 😅

Machen Sie die Änderungen hier rückgängig, bevor Sie fortfahren.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Ob Sie einen Prozess zum Reduzieren des Codes in Ihrer Anwendung einbinden, hängt von den verwendeten Tools ab:

  • Bei Verwendung von Webpack v4 oder höher ist kein zusätzlicher Aufwand erforderlich, da Code im Produktionsmodus standardmäßig reduziert wird. 👍
  • Wenn eine ältere Version des Webpacks verwendet wird, installieren Sie TerserWebpackPlugin und fügen Sie sie in den Webpack-Build-Prozess ein. In der Dokumentation wird dies ausführlich erläutert.
  • Es gibt auch andere Reduzierungs-Plug-ins, die stattdessen verwendet werden können, z. B. BabelMinifyWebpackPlugin und ClosureCompilerPlugin.
  • Wenn ein Modul-Bundler überhaupt nicht verwendet wird, verwenden Sie Terser als CLI-Tool oder schließen Sie ihn direkt als Abhängigkeit ein.

Komprimierung

Auch wenn der Begriff "Komprimierung" manchmal locker verwendet wird, um zu erklären, wie Code während der Reduzierung reduziert wird, wird er nicht im wörtlichen Sinne komprimiert.

Komprimierung bezieht sich in der Regel auf Code, der mit einem Datenkomprimierungsalgorithmus geändert wurde. Im Gegensatz zur Komprimierung, die schließlich perfekt gültigen Code liefert, muss komprimierter Code vor der Verwendung dekomprimiert werden.

Bei jeder HTTP-Anfrage und -Antwort können Browser und Webserver headers hinzufügen, um zusätzliche Informationen zu dem abzurufenden oder empfangenen Asset anzugeben. Sie sehen dies auf dem Tab Headers im Bereich „Netzwerk“ der Entwicklertools. Dort werden drei Typen angezeigt:

  • General steht für allgemeine Header, die für die gesamte Anfrage-Antwort-Interaktion relevant sind.
  • Unter Antwortheader wird eine Liste von Headern angezeigt, die für die tatsächliche Antwort des Servers spezifisch sind.
  • Anfrageheader zeigt eine Liste der Header an, die der Anfrage vom Client angehängt wurden.

Sehen Sie sich die accept-encoding-Kopfzeile in Request Headers an.

Codierungsheader akzeptieren

Mit accept-encoding wird vom Browser angegeben, welche Inhaltscodierungsformate oder Komprimierungsalgorithmen unterstützt werden. Es gibt viele Algorithmen zur Textkomprimierung, aber nur drei werden hier für die Komprimierung (und Dekomprimierung) von HTTP-Netzwerkanfragen unterstützt:

  • Gzip (gzip): Das am häufigsten verwendete Komprimierungsformat für Server- und Clientinteraktionen. Es baut auf dem Deflate-Algorithmus auf und wird von allen aktuellen Browsern unterstützt.
  • Deflate (deflate): Nicht häufig verwendet.
  • Brotli (br): Ein neuerer Komprimierungsalgorithmus, mit dem die Komprimierungsverhältnisse weiter verbessert werden sollen, was zu einem noch kürzeren Laden der Seiten führen kann. Sie wird von den neuesten Versionen der meisten Browser unterstützt.

Die Beispielanwendung in dieser Anleitung ist mit der App aus dem Codelab "Remove unused code" (Nicht verwendeten Code entfernen) identisch. Der einzige Unterschied besteht darin, dass jetzt Express als Server-Framework verwendet wird. In den nächsten Abschnitten geht es um die statische und die dynamische Komprimierung.

Dynamische Komprimierung

Bei der dynamischen Komprimierung werden Assets sofort dann komprimiert, wenn sie vom Browser angefordert werden.

Vorteile

  • Das Erstellen und Aktualisieren von gespeicherten komprimierten Versionen von Assets ist nicht erforderlich.
  • Die Komprimierung im laufenden Betrieb funktioniert besonders gut bei dynamisch generierten Webseiten.

Nachteile

  • Es dauert länger, Dateien auf höheren Ebenen zu komprimieren, um bessere Komprimierungsverhältnisse zu erzielen. Dies kann zu Leistungseinbußen führen, da der Nutzer auf die Komprimierung der Assets wartet, bevor sie vom Server gesendet werden.

Dynamische Komprimierung mit Node/Express

Die Datei server.js ist für die Einrichtung des Knotenservers verantwortlich, auf dem die Anwendung gehostet wird.

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

Aktuell importieren Sie lediglich express und verwenden die Middleware express.static, um alle statischen HTML-, JS- und CSS-Dateien im Verzeichnis public/ zu laden. Diese Dateien werden mit jedem Build von Webpack erstellt.

Damit alle Assets bei jeder Anfrage komprimiert werden, kann die Middleware-Bibliothek für Komprimierung verwendet werden. Fügen Sie es zuerst als devDependency in package.json hinzu:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

Importieren Sie es in die Serverdatei server.js:

const express = require('express');
const compression = require('compression');

Fügen Sie sie als Middleware hinzu, bevor express.static bereitgestellt wird:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Aktualisieren Sie nun die App und sehen Sie sich im Bereich Netzwerk die Bundle-Größe an.

Paketgröße mit dynamischer Komprimierung

Von 225 KB auf 61,6 KB! Im Response Headers zeigt ein content-encoding-Header an, dass der Server diese mit gzip codierte Datei heruntersendet.

Header für die Inhaltscodierung

Statische Komprimierung

Die Idee hinter der statischen Komprimierung besteht darin, die Assets vorab zu komprimieren und zu speichern.

Vorteile

  • Die Latenz aufgrund der hohen Komprimierungsstufen spielt keine Rolle mehr. Die Komprimierung von Dateien muss nicht spontan erfolgen, da sie jetzt direkt abgerufen werden können.

Nachteile

  • Assets müssen bei jedem Build komprimiert werden. Die Build-Dauer kann sich bei einer hohen Komprimierung erheblich verlängern.

Statische Komprimierung mit Node/Express und Webpack

Da bei der statischen Komprimierung Dateien im Voraus komprimiert werden, können die Webpack-Einstellungen so geändert werden, dass Assets im Rahmen des Build-Schritts komprimiert werden. CompressionPlugin kann dazu verwendet werden.

Fügen Sie es zuerst als devDependency in package.json hinzu:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Importieren Sie es wie jedes andere Webpack-Plug-in in die Konfigurationsdatei webpack.config.js:.

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

Fügen Sie es außerdem in das Array plugins ein:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

Das Plug-in komprimiert die Build-Dateien standardmäßig mit gzip. In der Dokumentation erfahren Sie, wie Sie Optionen hinzufügen, um einen anderen Algorithmus zu verwenden oder bestimmte Dateien ein- oder auszuschließen.

Wenn die App neu geladen und neu erstellt wird, wird jetzt eine komprimierte Version des Haupt-Bundles erstellt. Öffne die Glitch Console, um dir den Inhalt des endgültigen public/-Verzeichnisses anzusehen, das vom Knotenserver bereitgestellt wird.

  • Klicken Sie auf die Schaltfläche Tools.
  • Klicken Sie auf die Schaltfläche Console.
  • Führen Sie in der Console die folgenden Befehle aus, um zum Verzeichnis public zu wechseln und alle zugehörigen Dateien anzusehen:
cd public
ls

Endgültig ausgegebene Dateien im öffentlichen Verzeichnis

Die mit gzip komprimierte Version des Bundles, main.bundle.js.gz, wird jetzt hier gespeichert. CompressionPlugin komprimiert index.html außerdem standardmäßig.

Als Nächstes muss der Server angewiesen werden, diese mit gzip komprimierten Dateien zu senden, wenn ihre ursprünglichen JS-Versionen angefordert werden. Dazu kann in server.js eine neue Route definiert werden, bevor die Dateien mit express.static bereitgestellt werden.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

Mit app.get wird dem Server mitgeteilt, wie er auf eine GET-Anfrage für einen bestimmten Endpunkt antworten soll. Mit einer Callback-Funktion wird dann definiert, wie diese Anfrage verarbeitet werden soll. Die Route funktioniert so:

  • Wenn Sie '*.js' als erstes Argument angeben, funktioniert dies für jeden Endpunkt, der ausgelöst wird, um eine JS-Datei abzurufen.
  • Im Callback wird .gz an die URL der Anfrage angehängt und der Antwortheader Content-Encoding ist auf gzip gesetzt.
  • Schließlich sorgt next() dafür, dass die Sequenz zu jedem nachfolgenden Callback fortgeführt wird.

Sehen Sie sich nach dem Aktualisieren der App noch einmal den Bereich Network an.

Reduzierung der Bundle-Größe durch statische Komprimierung

Wie zuvor wurde die Paketgröße deutlich reduziert.

Fazit

In diesem Codelab wurde das Komprimieren und Komprimieren von Quellcode behandelt. Beide Verfahren werden in vielen der heute verfügbaren Tools zum Standard. Daher ist es wichtig herauszufinden, ob Ihre Toolchain sie bereits unterstützt oder ob Sie beide Prozesse selbst anwenden sollten.