Verbessern Sie in diesem Codelab die Leistung dieser einfachen Anwendung, mit der Nutzer zufällig Katzen bewerten können. Hier erfahren Sie, wie Sie das JavaScript-Bundle optimieren, indem Sie die Menge der zu transpilierenden Codes minimieren.
In der Beispiel-App können Sie ein Wort oder ein Emoji auswählen, um zu vermitteln, wie sehr Ihnen jede Katze gefällt. Wenn Sie auf eine Schaltfläche klicken, zeigt die App den Wert der Schaltfläche unter dem aktuellen Katzenbild an.
Messen
Es empfiehlt sich immer, zuerst eine Website zu prüfen, bevor Sie Optimierungen vornehmen:
- Wenn Sie eine Vorschau der Website aufrufen möchten, klicken Sie auf View App (App anzeigen) und dann auf Fullscreen (Vollbild)
.
- Drücken Sie „Strg + Umschalttaste + J“ (oder „Befehlstaste + Option + J“ auf dem Mac), um die Entwicklertools zu öffnen.
- Klicken Sie auf den Tab Netzwerk.
- Klicken Sie das Kästchen Cache deaktivieren an.
- Aktualisieren Sie die App.
Für diese Anwendung werden mehr als 80 KB verwendet. So finden Sie heraus, ob Teile des Bundles nicht verwendet werden:
Drücken Sie
Control+Shift+P
(oderCommand+Shift+P
auf einem Mac), um das Menü Befehl zu öffnen.Geben Sie
Show Coverage
ein und drücken SieEnter
, um den Tab Abdeckung aufzurufen.Klicken Sie auf dem Tab Abdeckung auf Aktualisieren, um die Anwendung während der Erfassung der Abdeckung neu zu laden.
Sehen Sie sich an, wie viel Code verwendet und wie viel Code für das Haupt-Bundle geladen wurde:
Mehr als die Hälfte des Pakets (44 KB) wird gar nicht genutzt. Das liegt daran, dass ein großer Teil des darin enthaltenen Codes aus Polyfills besteht, damit die Anwendung auch in älteren Browsern funktioniert.
@babel/preset-env verwenden
Die Syntax der JavaScript-Sprache entspricht dem Standard ECMAScript oder ECMA-262. Jedes Jahr werden neuere Versionen der Spezifikation veröffentlicht, die neue Funktionen enthalten, die den Angebotsprozess bestanden haben. Jeder Browser befindet sich in einer anderen Phase der Unterstützung dieser Funktionen.
Die folgenden ES2015-Funktionen werden in der Anwendung verwendet:
Die folgende ES2017-Funktion wird ebenfalls verwendet:
Sie können sich den Quellcode in src/index.js
ansehen, um zu sehen, wie er verwendet wird.
Alle diese Funktionen werden in der neuesten Version von Chrome unterstützt. Aber was ist mit anderen Browsern, die sie nicht unterstützen? Die in der Anwendung enthaltene Babel-Bibliothek ist die gängigste Bibliothek, die zum Kompilieren von Code mit neuerer Syntax in Code verwendet wird, den ältere Browser und Umgebungen verstehen können. Dies erfolgt auf zwei Arten:
- Polyfills werden verwendet, um neuere Funktionen von ES2015+ zu emulieren, sodass ihre APIs auch dann verwendet werden können, wenn sie vom Browser nicht unterstützt werden. Hier ist ein Beispiel für einen polyfill für die Methode
Array.includes
. - Plug-ins werden verwendet, um ES2015-Code (oder höher) in ältere ES5-Syntax umzuwandeln. Da es sich um Syntaxänderungen (wie Pfeilfunktionen) handelt, können sie nicht mit Polyfills emuliert werden.
In package.json
sehen Sie, welche Babel-Bibliotheken enthalten sind:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/core
ist der zentrale Babel-Compiler. Damit werden alle Babel-Konfigurationen in einem.babelrc
im Stammverzeichnis des Projekts definiert.babel-loader
enthält Babel in den Webpack-Build-Prozess.
Unter webpack.config.js
sehen Sie, wie babel-loader
als Regel verwendet wird:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
@babel/polyfill
stellt alle erforderlichen Polyfills für alle neueren ECMAScript-Funktionen bereit, damit sie in Umgebungen funktionieren, die sie nicht unterstützen. Es wurde bereits ganz oben insrc/index.js.
importiert
import "./style.css";
import "@babel/polyfill";
@babel/preset-env
gibt an, welche Transformationen und Polyfills für Browser oder Umgebungen erforderlich sind, die als Ziele ausgewählt werden.
Informationen zum Inhalt der Babel-Konfigurationsdatei .babelrc
:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Dies ist eine Babel- und Webpack-Einrichtung. Hier erfahren Sie, wie Sie Babel in Ihre Anwendung einbinden, falls Sie einen anderen Modul-Bundler als Webpack verwenden.
Das Attribut targets
in .babelrc
gibt an, welche Browser ausgewählt werden. @babel/preset-env
lässt sich in die Browserliste einbinden. Eine vollständige Liste kompatibler Abfragen, die in diesem Feld verwendet werden können, finden Sie in der Browserlist-Dokumentation.
Mit dem Wert "last 2 versions"
wird der Code in der Anwendung für die letzten beiden Versionen jedes Browsers transpiliert.
Debugging
Um einen vollständigen Überblick über alle Babel-Ziele des Browsers sowie alle enthaltenen Transformationen und Polyfills zu erhalten, fügen Sie .babelrc:
das Feld debug
hinzu.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- Klicken Sie auf Tools.
- Klicken Sie auf Logs.
Aktualisieren Sie die Anwendung und sehen Sie sich die Glitch-Statuslogs am unteren Rand des Editors an.
Zielbrowser
Babel protokolliert in der Konsole eine Reihe von Details zum Kompilierungsprozess, einschließlich aller Zielumgebungen, für die der Code kompiliert wurde.
Beachten Sie, dass nicht mehr unterstützte Browser wie Internet Explorer in dieser Liste enthalten sind. Dies ist ein Problem, da für nicht unterstützte Browser keine neueren Funktionen hinzugefügt werden und Babel weiterhin die spezifische Syntax für sie transpiliert. Dadurch wird Ihr Bundle unnötig groß, wenn Nutzer diesen Browser nicht für den Zugriff auf Ihre Website verwenden.
Babel protokolliert auch eine Liste der verwendeten Transformations-Plug-ins:
Das ist eine ziemlich lange Liste. Dies sind alle Plug-ins, die Babel verwenden muss, um jede Syntax aus ES2015+ für alle Zielbrowser in eine ältere Syntax umzuwandeln.
In Babel werden jedoch keine spezifischen Polyfills angezeigt, die verwendet werden:
Das liegt daran, dass der gesamte @babel/polyfill
direkt importiert wird.
Polyfills einzeln laden
Wenn @babel/polyfill
in eine Datei importiert wird, schließt Babel standardmäßig alle Polyfills ein, die für eine vollständige ES2015+-Umgebung erforderlich sind. Wenn Sie bestimmte Polyfills importieren möchten, die für die Zielbrowser erforderlich sind, fügen Sie der Konfiguration ein useBuiltIns: 'entry'
hinzu.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Aktualisieren Sie die Anwendung. Sie sehen jetzt alle enthaltenen Polyfills:
Jetzt sind zwar nur noch benötigte Polyfills für "last 2 versions"
enthalten, die Liste ist aber immer noch sehr lang. Das liegt daran, dass Polyfills, die für die Zielbrowser für alle neueren Funktionen benötigt werden, weiterhin enthalten sind. Ändern Sie den Wert des Attributs in usage
, um nur die Elemente einzuschließen, die für im Code verwendete Elemente erforderlich sind.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
Polyfills werden bei Bedarf automatisch eingefügt.
Das bedeutet, dass Sie den Import von @babel/polyfill
in src/index.js.
entfernen können
import "./style.css";
import "@babel/polyfill";
Jetzt sind nur noch die für die Anwendung erforderlichen Polyfills enthalten.
Die Größe des Anwendungs-Bundles wurde erheblich reduziert.
Liste der unterstützten Browser eingrenzen
Die Anzahl der enthaltenen Browserziele ist immer noch recht groß und nicht viele Nutzer verwenden nicht mehr unterstützte Browser wie Internet Explorer. Aktualisieren Sie die Konfigurationen so:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Sehen Sie sich die Details zum abgerufenen Bundle an.
Da die Anwendung so klein ist, gibt es wirklich keinen großen Unterschied bei diesen Änderungen. Es wird jedoch empfohlen, einen Prozentsatz für den Browser-Marktanteil (z. B. ">0.25%"
) zu verwenden und bestimmte Browser auszuschließen, bei denen Sie sicher sind, dass Ihre Nutzer sie nicht verwenden. Weitere Informationen dazu finden Sie im Artikel Letzte 2 Versionen als schädlich von James Kyle.
<script type="module"> verwenden
Es gibt noch mehr Raum für Verbesserungen. Obwohl einige nicht verwendete Polyfills entfernt wurden, werden derzeit viele davon ausgeliefert, die für manche Browser nicht benötigt werden. Durch die Verwendung von Modulen kann neue Syntax geschrieben und direkt an Browser gesendet werden, ohne dass unnötige Polyfills verwendet werden.
JavaScript-Module sind eine relativ neue Funktion, die in allen gängigen Browsern unterstützt wird.
Module können mit einem type="module"
-Attribut erstellt werden, um Skripts zu definieren, die aus anderen Modulen importiert und exportiert werden. Beispiel:
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
Viele neuere ECMAScript-Funktionen werden bereits in Umgebungen unterstützt, die JavaScript-Module unterstützen (also nicht auf Babel). Das bedeutet, dass die Babel-Konfiguration so geändert werden kann, dass zwei verschiedene Versionen Ihrer Anwendung an den Browser gesendet werden:
- Eine Version, die in neueren Browsern funktioniert, die Module unterstützen und ein Modul enthält, das größtenteils nicht transpiliert ist, aber eine kleinere Dateigröße hat
- Eine Version mit einem größeren, transpilierten Skript, das in jedem älteren Browser funktioniert
ES-Module mit Babel verwenden
Wenn Sie separate @babel/preset-env
-Einstellungen für die beiden Versionen der Anwendung haben möchten, entfernen Sie die Datei .babelrc
. Sie können der Webpack-Konfiguration Babel-Einstellungen hinzufügen, indem Sie für jede Version der Anwendung zwei verschiedene Kompilierungsformate angeben.
Fügen Sie zuerst eine Konfiguration für das alte Skript in webpack.config.js
hinzu:
const legacyConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: false
}
}]
]
}
},
cssRule
]
},
plugins
}
Beachten Sie, dass für "@babel/preset-env"
nicht der Wert targets
, sondern esmodules
mit dem Wert false
verwendet wird. Das bedeutet, dass Babel alle erforderlichen Transformationen und Polyfills enthält, die auf jeden Browser ausgerichtet sind, der ES-Module noch nicht unterstützt.
Fügen Sie am Anfang der Datei webpack.config.js
die Objekte entry
, cssRule
und corePlugins
hinzu. Diese werden sowohl vom Modul als auch von den Legacy-Skripts, die an den Browser gesendet werden, gemeinsam genutzt.
const entry = {
main: "./src"
};
const cssRule = {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
};
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"})
];
Erstellen Sie auf ähnliche Weise ein Konfigurationsobjekt für das folgende Modulskript, in dem legacyConfig
definiert ist:
const moduleConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].mjs"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: true
}
}]
]
}
},
cssRule
]
},
plugins
}
Der Hauptunterschied besteht darin, dass für den Namen der Ausgabedatei die Dateiendung .mjs
verwendet wird. Der Wert esmodules
ist hier auf "true" gesetzt. Das bedeutet, dass der in dieses Modul ausgegebene Code ein kleineres, weniger kompiliertes Skript ist, das in diesem Beispiel keine Transformation durchläuft, da alle verwendeten Funktionen bereits in Browsern unterstützt werden, die Module unterstützen.
Exportieren Sie am Ende der Datei beide Konfigurationen in ein einziges Array.
module.exports = [
legacyConfig, moduleConfig
];
Dadurch wird sowohl ein kleineres Modul für Browser erstellt, die es unterstützen, als auch ein größeres transpiliertes Skript für ältere Browser.
Browser, die Module unterstützen, ignorieren Skripts mit einem nomodule
-Attribut.
Umgekehrt ignorieren Browser, die keine Module unterstützen, Skriptelemente mit type="module"
. Sie können also sowohl ein Modul als auch ein kompiliertes Fallback einschließen. Idealerweise sollten sich die beiden Versionen der Anwendung in index.html
befinden:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Browser, die Module unterstützen, rufen main.mjs
ab und führen sie aus und ignorieren main.bundle.js.
. Browser, die keine Module unterstützen, tun das Gegenteil.
Beachten Sie, dass Modulskripts, anders als reguläre Skripts, standardmäßig verzögert werden.
Wenn das entsprechende nomodule
-Skript ebenfalls zurückgestellt und erst nach dem Parsen ausgeführt werden soll, müssen Sie das Attribut defer
hinzufügen:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
Als letzten Schritt müssen Sie dem Modul bzw. dem Legacy-Skript die Attribute module
und nomodule
hinzufügen. Importieren Sie das ScriptExtHtmlWebpackPlugin ganz oben in webpack.config.js
:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
Aktualisieren Sie jetzt das plugins
-Array in den Konfigurationen, um dieses Plug-in einzuschließen:
const plugins = [ new ExtractTextPlugin({filename: "[name].css", allChunks: true}), new HtmlWebpackPlugin({template: "./src/index.html"}), new ScriptExtHtmlWebpackPlugin({ module: /\.mjs$/, custom: [ { test: /\.js$/, attribute: 'nomodule', value: '' }, ] }) ];
Mit diesen Plug-in-Einstellungen wird ein type="module"
-Attribut für alle .mjs
-Skriptelemente sowie ein nomodule
-Attribut für alle .js
-Skriptmodule hinzugefügt.
Module im HTML-Dokument bereitstellen
Als Letztes müssen Sie sowohl die alten als auch die modernen Skriptelemente in die HTML-Datei ausgeben. Leider unterstützt das Plug-in, mit dem die endgültige HTML-Datei HTMLWebpackPlugin
erstellt wird, die Ausgabe sowohl des Moduls als auch der Skripts ohne Modul nicht. Obwohl es Problemumgehungen und separate Plug-ins zur Lösung dieses Problems gibt, z. B. BabelMultiTargetPlugin und HTMLWebpackMultiBuildPlugin, wird im Rahmen dieser Anleitung ein einfacherer Ansatz verwendet, um das Modul-Skriptelement manuell hinzuzufügen.
Fügen Sie am Ende der Datei in src/index.js
Folgendes hinzu:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Laden Sie nun die Anwendung in einem Browser, der Module unterstützt, z. B. die neueste Version von Chrome.
Nur das Modul wird mit einer viel kleineren Bundle-Größe abgerufen, da es größtenteils nicht transpiliert wird. Das andere Skriptelement wird vom Browser vollständig ignoriert.
Wenn Sie die Anwendung in einem älteren Browser laden, wird nur das größere, transpilierte Skript mit allen erforderlichen Polyfills und Transformationen abgerufen. Hier sehen Sie einen Screenshot aller Anfragen, die unter einer älteren Version von Chrome (Version 38) gestellt wurden.
Fazit
Jetzt wissen Sie, wie Sie mit @babel/preset-env
nur die erforderlichen Polyfills bereitstellen, die für Zielbrowser erforderlich sind. Außerdem wissen Sie, wie Sie mit JavaScript-Modulen die Leistung weiter verbessern können, indem Sie zwei verschiedene transpilierte Versionen einer Anwendung versenden. Wenn Sie genau wissen, wie diese beiden Techniken die Größe Ihres Pakets erheblich reduzieren können, können Sie weitere Optimierungen vornehmen.