In diesem Codelab wird untersucht, wie sich die Seitenleistung durch die Minimierung und Komprimierung des JavaScript-Bundles für die folgende Anwendung verbessert, indem die Anforderungsgröße der App reduziert wird.
Messen
Bevor Sie mit der Optimierung beginnen, sollten Sie immer zuerst den aktuellen Zustand der Anwendung analysieren.
- Wenn Sie sich eine Vorschau der Website ansehen möchten, drücken Sie App ansehen und dann Vollbild
.
Mit dieser App, die auch im Codelab „Nicht verwendeten Code entfernen“ behandelt wird, können Sie für Ihr Lieblingskätzchen abstimmen. 🐈
Sehen Sie sich nun an, wie groß diese Anwendung ist:
- Drücken Sie „Strg + Umschalttaste + J“ (oder „Befehlstaste + Optionstaste + J“ auf einem Mac), um die Entwicklertools zu öffnen.
- Klicken Sie auf den Tab Netzwerk.
- Klicken Sie das Kästchen Cache deaktivieren an.
- Aktualisieren Sie die App.
Im Codelab Nicht verwendeten Code entfernen wurden zwar viele Fortschritte erzielt, um die Größe dieses Bundles zu verringern, aber 225 KB sind immer noch recht viel.
Reduzierung
Sehen Sie sich den folgenden Codeblock an.
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 Byte.
Wenn alle Leerzeichen entfernt wurden, sieht der Code so aus:
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
Die Dateigröße beträgt jetzt etwa 83 B. Wenn der Code durch Kürzen des Variablennamens und Ändern einiger Ausdrücke weiter verfälscht wird, sieht der endgültige Code möglicherweise so aus:
function soNice(){for(let i=0;i<100;)console.log("nice"),i++}
Die Dateigröße beträgt jetzt 62 B.
Mit jedem Schritt wird der Code schwerer lesbar. Die JavaScript-Engine des Browsers interpretiert sie jedoch alle auf genau dieselbe Weise. Das Verschleiern von Code auf diese Weise kann dazu beitragen, die Dateigröße zu verringern. 112 B waren nicht viel, aber es gab trotzdem eine Reduzierung der Größe um 50 %.
In dieser Anwendung wird webpack Version 4 als Modul-Bundler verwendet. Die genaue Version ist in package.json
zu sehen.
"devDependencies": {
//...
"webpack": "^4.16.4",
//...
}
In Version 4 wird das Bundle im Produktionsmodus standardmäßig minimiert. Dazu wird TerserWebpackPlugin
verwendet, ein Plug-in für Terser.
Terser ist ein beliebtes Tool zum Komprimieren von JavaScript-Code.
Wenn Sie sich ansehen möchten, wie der minimierte Code aussieht, klicken Sie im Netzwerk-Bereich der Entwicklertools auf main.bundle.js
. Klicken Sie jetzt auf den Tab Antwort.
Der Code in seiner endgültigen Form, minimiert und verschleiert, wird im Antworttext angezeigt.
Wenn Sie herausfinden möchten, wie groß das Bundle ohne Minimierung gewesen wäre, öffnen Sie webpack.config.js
und aktualisieren Sie die mode
-Konfiguration.
module.exports = {
mode: 'production',
mode: 'none',
//...
Laden Sie die Anwendung neu und sehen Sie sich die Bundle-Größe noch einmal im Netzwerkbereich der Entwicklertools an.
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 Minimieren von Code in Ihre Anwendung einbeziehen, hängt von den verwendeten Tools ab:
- Wenn webpack v4 oder höher verwendet wird, sind keine zusätzlichen Schritte erforderlich, da der Code im Produktionsmodus standardmäßig minimiert wird. 👍
- Wenn eine ältere Version von webpack verwendet wird, installieren Sie
TerserWebpackPlugin
und binden Sie es in den webpack-Buildprozess ein. Dies wird in der Dokumentation ausführlich erläutert. - Es gibt auch andere Plugins zur Komprimierung, die stattdessen verwendet werden können, z. B. BabelMinifyWebpackPlugin und ClosureCompilerPlugin.
- Wenn kein Modul-Bundler verwendet wird, verwenden Sie Terser als CLI-Tool oder fügen Sie es direkt als Abhängigkeit ein.
Komprimierung
Obwohl der Begriff „Komprimierung“ manchmal locker verwendet wird, um zu erklären, wie Code während der Minimierung reduziert wird, wird er im eigentlichen Sinne nicht komprimiert.
Komprimierung bezieht sich in der Regel auf Code, der mit einem Datenkomprimierungsalgorithmus geändert wurde. Im Gegensatz zur Minimierung, bei der am Ende ein perfekt gültiger Code entsteht, muss komprimierter Code vor der Verwendung dekomprimiert werden.
Bei jeder HTTP-Anfrage und ‑Antwort können Browser und Webserver Header hinzufügen, um zusätzliche Informationen zum abgerufenen oder empfangenen Asset anzugeben. Das ist in den Entwicklertools im Bereich „Netzwerk“ auf dem Tab Headers
zu sehen. Dort werden drei Typen angezeigt:
- Allgemein 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 vom Server spezifisch sind.
- Unter Request Headers (Anfrageheader) wird eine Liste der Header angezeigt, die vom Client an die Anfrage angehängt wurden.
Sehen Sie sich den accept-encoding
-Header in der Request Headers
an.
Mit accept-encoding
gibt der Browser an, welche Formate für die Inhaltsverschlüsselung oder Komprimierungsalgorithmen er unterstützt. Es gibt viele Textkomprimierungsalgorithmen, 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 basiert auf dem Deflate-Algorithmus und wird von allen aktuellen Browsern unterstützt. - Deflate (
deflate
): Wird nicht häufig verwendet. - Brotli (
br
): Ein neuerer Komprimierungsalgorithmus, der darauf abzielt, die Komprimierungsraten weiter zu verbessern, was zu noch schnelleren Seitenladezeiten führen kann. Es wird in den neuesten Versionen der meisten Browser unterstützt.
Die Beispielanwendung in diesem Codelab ist identisch mit der App, die im Codelab Unused Code entfernen erstellt wurde. Der einzige Unterschied ist, dass Express jetzt als Server-Framework verwendet wird. In den nächsten Abschnitten werden sowohl statische als auch dynamische Komprimierung behandelt.
Dynamische Komprimierung
Bei der dynamischen Komprimierung werden Assets spontan komprimiert, wenn sie vom Browser angefordert werden.
Vorteile
- Das Erstellen und Aktualisieren gespeicherter komprimierter Versionen von Assets ist nicht erforderlich.
- Die On-the-fly-Komprimierung eignet sich besonders gut für Webseiten, die dynamisch generiert werden.
Nachteile
- Das Komprimieren von Dateien auf höheren Ebenen, um bessere Komprimierungsverhältnisse zu erzielen, dauert länger. Das kann zu Leistungseinbußen führen, da der Nutzer warten muss, bis Assets komprimiert werden, bevor sie vom Server gesendet werden.
Dynamische Komprimierung mit Node/Express
Die Datei server.js
ist für die Einrichtung des Node-Servers 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);
});
Derzeit wird nur express
importiert und die Middleware express.static
verwendet, um alle statischen HTML-, JS- und CSS-Dateien im Verzeichnis public/
zu laden. Diese Dateien werden bei jedem Build von Webpack erstellt.
Damit alle Assets bei jeder Anfrage komprimiert werden, kann die Middleware-Bibliothek compression verwendet werden. Fügen Sie es zuerst als devDependency
in package.json
hinzu:
"devDependencies": {
//...
"compression": "^1.7.3"
},
Importieren Sie sie in die Serverdatei server.js
:
const express = require('express');
const compression = require('compression');
Fügen Sie sie als Middleware vor dem Mounten von express.static
hinzu:
//...
const app = express();
app.use(compression());
app.use(express.static('public'));
//...
Laden Sie die App nun neu und sehen Sie sich die Bundle-Größe im Bereich Network (Netzwerk) an.
Von 225 KB auf 61,6 KB! Im Response Headers
sehen Sie jetzt einen content-encoding
-Header, der angibt, dass der Server diese Datei mit gzip
codiert sendet.
Statische Komprimierung
Bei der statischen Komprimierung werden Assets im Voraus komprimiert und gespeichert.
Vorteile
- Latenz aufgrund hoher Komprimierungsstufen ist kein Problem mehr. Dateien müssen nicht mehr sofort komprimiert werden, da sie jetzt direkt abgerufen werden können.
Nachteile
- Assets müssen bei jedem Build komprimiert werden. Die Build-Zeiten können sich erheblich verlängern, wenn hohe Komprimierungsstufen verwendet werden.
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.
Hierfür kann CompressionPlugin
verwendet werden.
Fügen Sie es zuerst als devDependency
in package.json
hinzu:
"devDependencies": {
//...
"compression-webpack-plugin": "^1.1.11"
},
Wie jedes andere Webpack-Plug-in muss es in der Konfigurationsdatei webpack.config.js:
importiert werden.
const path = require("path");
//...
const CompressionPlugin = require("compression-webpack-plugin");
Fügen Sie sie in das Array plugins
ein:
module.exports = {
//...
plugins: [
//...
new CompressionPlugin()
]
}
Standardmäßig komprimiert das Plug-in die Build-Dateien 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. Öffnen Sie die Glitch Console, um sich anzusehen, was sich im endgültigen public/
-Verzeichnis befindet, das vom Node-Server bereitgestellt wird.
- Klicken Sie auf die Schaltfläche Tools.
- Klicken Sie auf die Schaltfläche Console.
- Führen Sie in der Konsole die folgenden Befehle aus, um in das Verzeichnis
public
zu wechseln und alle Dateien darin aufzurufen:
cd public
ls
Die gezippte Version des Bundles, main.bundle.js.gz
, wird jetzt auch hier gespeichert. CompressionPlugin
komprimiert standardmäßig auch index.html
.
Als Nächstes muss dem Server mitgeteilt werden, dass er diese gezippten Dateien immer dann senden soll, wenn die ursprünglichen JS-Versionen angefordert werden. Dazu können Sie in server.js
eine neue Route definieren, 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 reagieren soll. Anschließend wird eine Callback-Funktion verwendet, um festzulegen, wie diese Anfrage verarbeitet werden soll. So funktioniert die Route:
- Wenn Sie
'*.js'
als erstes Argument angeben, funktioniert dies für jeden Endpunkt, der zum Abrufen einer JS-Datei ausgelöst wird. - Im Callback wird
.gz
an die URL der Anfrage angehängt und der AntwortheaderContent-Encoding
wird aufgzip
gesetzt. - Schließlich sorgt
next()
dafür, dass die Sequenz mit dem nächsten Callback fortgesetzt wird.
Sehen Sie sich nach dem Neuladen der App noch einmal das Network
-Bedienfeld an.
Wie zuvor wurde die Bundle-Größe deutlich reduziert.
Fazit
In diesem Codelab haben Sie gelernt, wie Sie Quellcode komprimieren und minimieren. Beide Techniken werden in vielen der heute verfügbaren Tools standardmäßig verwendet. Es ist daher wichtig, herauszufinden, ob Ihre Toolchain sie bereits unterstützt oder ob Sie beide Prozesse selbst anwenden sollten.