Behalten Sie die Kontrolle: Ein Leitfaden für Webpack und React, Pt. 1

Veröffentlicht: 2022-03-11

Wenn Sie ein neues React-Projekt starten, haben Sie viele Vorlagen zur Auswahl: React App erstellen, React react-boilerplate und React Starter Kit, um nur einige zu nennen.

Diese Vorlagen, die von Tausenden von Entwicklern verwendet werden, können die Anwendungsentwicklung in sehr großem Umfang unterstützen. Aber sie verlassen die Entwicklererfahrung und bündeln die Ausgabe mit verschiedenen Standardwerten, die möglicherweise nicht ideal sind.

Wenn Sie ein größeres Maß an Kontrolle über Ihren Build-Prozess behalten möchten, können Sie in eine benutzerdefinierte Webpack-Konfiguration investieren. Wie Sie aus diesem Webpack-Lernprogramm erfahren werden, ist diese Aufgabe nicht sehr kompliziert, und das Wissen kann sogar bei der Fehlersuche in den Konfigurationen anderer Personen nützlich sein.

Webpack: Erste Schritte

Die Art und Weise, wie wir JavaScript heute schreiben, unterscheidet sich von dem Code, den der Browser ausführen kann. Wir verlassen uns häufig auf andere Arten von Ressourcen, transpilierte Sprachen und experimentelle Funktionen, die in modernen Browsern noch unterstützt werden müssen. Webpack ist ein Modul-Bundler für JavaScript, der diese Lücke schließen und browserübergreifend kompatiblen Code ohne Kosten für die Entwicklererfahrung erzeugen kann.

Bevor wir beginnen, sollten Sie bedenken, dass der gesamte in diesem Webpack-Tutorial vorgestellte Code auch in Form einer vollständigen Webpack/React-Beispielkonfigurationsdatei auf GitHub verfügbar ist. Bitte zögern Sie nicht, dort darauf zu verweisen, und kommen Sie auf diesen Artikel zurück, wenn Sie Fragen haben.

Basis-Konfig

Seit Legato (Version 4) erfordert Webpack keine Konfiguration, um ausgeführt zu werden. Durch die Auswahl eines Erstellungsmodus wird eine Reihe von Standardeinstellungen angewendet, die für die Zielumgebung besser geeignet sind. Im Sinne dieses Artikels werden wir diese Voreinstellungen beiseite schieben und selbst eine sinnvolle Konfiguration für jede Zielumgebung implementieren.

Zuerst müssen wir webpack und webpack-cli installieren:

 npm install -D webpack webpack-cli

Dann müssen wir webpack.config.js mit einer Konfiguration mit den folgenden Optionen füllen:

  • devtool : Aktiviert die Source-Map-Generierung im Entwicklungsmodus.
  • entry : Die Hauptdatei unserer React-Anwendung.
  • output.path : Das Stammverzeichnis zum Speichern der Ausgabedateien.
  • output.filename : Das Dateinamenmuster, das für generierte Dateien verwendet werden soll.
  • output.publicPath : Der Pfad zum Stammverzeichnis, in dem die Dateien auf dem Webserver bereitgestellt werden.
 const path = require("path"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; const isDevelopment = !isProduction; return { devtool: isDevelopment && "cheap-module-source-map", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" } }; };

Die obige Konfiguration funktioniert gut für einfache JavaScript-Dateien. Aber wenn wir Webpack und React verwenden, müssen wir zusätzliche Transformationen durchführen, bevor wir Code an unsere Benutzer versenden. Im nächsten Abschnitt werden wir Babel verwenden, um die Art und Weise zu ändern, wie Webpack JavaScript-Dateien lädt.

JS-Loader

Babel ist ein JavaScript-Compiler mit vielen Plugins zur Codetransformation. In diesem Abschnitt werden wir es als Loader in unsere Webpack-Konfiguration einführen und so konfigurieren, dass es modernen JavaScript-Code in einen solchen umwandelt, der von gängigen Browsern verstanden wird.

Zuerst müssen wir babel-loader und @babel/core installieren:

 npm install -D @babel/core babel-loader

Dann fügen wir unserer Webpack-Konfiguration einen module hinzu, der babel-loader für das Laden von JavaScript-Dateien verantwortlich macht:

 @@ -11,6 +11,25 @@ module.exports = function(_env, argv) { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" + }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + cacheDirectory: true, + cacheCompression: false, + envName: isProduction ? "production" : "development" + } + } + } + ] + }, + resolve: { + extensions: [".js", ".jsx"] } }; };

Wir werden Babel mit einer separaten Konfigurationsdatei, babel.config.js , konfigurieren. Es wird die folgenden Funktionen verwenden:

  • @babel/preset-env : Wandelt moderne JavaScript-Funktionen in abwärtskompatiblen Code um.
  • @babel/preset-react : Wandelt die JSX-Syntax in Plain-Vanilla-JavaScript-Funktionsaufrufe um.
  • @babel/plugin-transform-runtime : Reduziert Code-Duplizierung durch Extrahieren von Babel-Helfern in gemeinsam genutzte Module.
  • @babel/plugin-syntax-dynamic-import : Aktiviert die dynamische import() Syntax in Browsern ohne native Promise -Unterstützung.
  • @babel/plugin-proposal-class-properties : Aktiviert die Unterstützung für den Syntaxvorschlag für öffentliche Instanzfelder zum Schreiben klassenbasierter React-Komponenten.

Wir werden auch einige React-spezifische Produktionsoptimierungen aktivieren:

  • babel-plugin-transform-react-remove-prop-types entfernt unnötige Prop-Typen aus dem Produktionscode.
  • @babel/plugin-transform-react-inline-elements wertet React.createElement während der Kompilierung aus und fügt das Ergebnis ein.
  • @babel/plugin-transform-react-constant-elements extrahiert statische React-Elemente als Konstanten.

Der folgende Befehl installiert alle erforderlichen Abhängigkeiten:

 npm install -D @babel/preset-env @babel/preset-react @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties babel-plugin-transform-react-remove-prop-types @babel/plugin-transform-react-inline-elements @babel/plugin-transform-react-constant-elements

Dann füllen wir babel.config.js mit diesen Einstellungen:

 module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties" ], env: { production: { only: ["src"], plugins: [ [ "transform-react-remove-prop-types", { removeImport: true } ], "@babel/plugin-transform-react-inline-elements", "@babel/plugin-transform-react-constant-elements" ] } } };

Diese Konfiguration ermöglicht es uns, modernes JavaScript so zu schreiben, dass es mit allen relevanten Browsern kompatibel ist. Es gibt andere Arten von Ressourcen, die wir möglicherweise in einer React-Anwendung benötigen, die wir in den folgenden Abschnitten behandeln werden.

CSS-Loader

Wenn es um das Styling von React-Anwendungen geht, müssen wir zumindest in der Lage sein, einfache CSS-Dateien einzubinden. Wir werden dies in Webpack mit den folgenden Loadern tun:

  • css-loader : Analysiert CSS-Dateien und löst externe Ressourcen wie Bilder, Schriftarten und zusätzliche Stilimporte auf.
  • style-loader : Fügt während der Entwicklung geladene Stile zur Laufzeit in das Dokument ein.
  • mini-css-extract-plugin : Extrahiert geladene Stile in separate Dateien für den Produktionseinsatz, um das Browser-Caching zu nutzen.

Lassen Sie uns die obigen CSS-Loader installieren:

 npm install -D css-loader style-loader mini-css-extract-plugin

Dann fügen wir dem Abschnitt module.rules unserer Webpack-Konfiguration eine neue Regel hinzu:

 @@ -1,4 +1,5 @@ const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -25,6 +26,13 @@ module.exports = function(_env, argv) { envName: isProduction ? "production" : "development" } } + }, + { + test: /\.css$/, + use: [ + isProduction ? MiniCssExtractPlugin.loader : "style-loader", + "css-loader" + ] } ] },

Wir werden auch MiniCssExtractPlugin zum plugins -Bereich hinzufügen, das wir nur im Produktionsmodus aktivieren werden:

 @@ -38,6 +38,13 @@ module.exports = function(_env, argv) { }, resolve: { extensions: [".js", ".jsx"] - } + }, + plugins: [ + isProduction && + new MiniCssExtractPlugin({ + filename: "assets/css/[name].[contenthash:8].css", + chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" + }) + ].filter(Boolean) }; };

Diese Konfiguration funktioniert für einfache CSS-Dateien und kann erweitert werden, um mit verschiedenen CSS-Prozessoren wie Sass und PostCSS zu arbeiten, die wir im nächsten Artikel besprechen werden.

Bildlader

Webpack kann auch verwendet werden, um statische Ressourcen wie Bilder, Videos und andere Binärdateien zu laden. Die allgemeinste Art, mit solchen Dateitypen umzugehen, ist die Verwendung von file-loader oder url-loader , die ihren Verbrauchern eine URL-Referenz für die erforderlichen Ressourcen bereitstellen.

In diesem Abschnitt werden wir einen url-loader hinzufügen, um gängige Bildformate zu verarbeiten. Was den url-loader vom file-loader unterscheidet, ist, dass, wenn die Größe der Originaldatei kleiner als ein bestimmter Schwellenwert ist, die gesamte Datei als base64-codierter Inhalt in die URL eingebettet wird, wodurch eine zusätzliche Anfrage entfällt.

Zuerst installieren wir url-loader :

 npm install -D url-loader

Dann fügen wir eine neue Regel zum Abschnitt module.rules unserer Webpack-Konfiguration hinzu:

 @@ -34,6 +34,16 @@ module.exports = function(_env, argv) { isProduction ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] + }, + { + test: /\.(png|jpg|gif)$/i, + use: { + loader: "url-loader", + options: { + limit: 8192, + name: "static/media/[name].[hash:8].[ext]" + } + } } ] },

SVG

Für SVG-Bilder verwenden wir den @svgr/webpack , der importierte Dateien in React-Komponenten umwandelt.

Wir installieren @svgr/webpack :

 npm install -D @svgr/webpack

Dann fügen wir eine neue Regel zum Abschnitt module.rules unserer Webpack-Konfiguration hinzu:

 @@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },

SVG-Bilder als React-Komponenten können praktisch sein, und @svgr/webpack führt eine Optimierung mit SVGO durch.

Hinweis: Für bestimmte Animationen oder sogar Mouseover-Effekte müssen Sie das SVG möglicherweise mit JavaScript manipulieren. Glücklicherweise bettet @svgr/webpack SVG-Inhalte standardmäßig in das JavaScript-Bundle ein, sodass Sie die dafür erforderlichen Sicherheitsbeschränkungen umgehen können.

Dateilader

Wenn wir auf andere Arten von Dateien verweisen müssen, erledigt der generische file-loader die Arbeit. Es funktioniert ähnlich wie url-loader , indem es eine Asset-URL für den Code bereitstellt, der es erfordert, aber es unternimmt keinen Versuch, es zu optimieren.

Wie immer installieren wir zuerst das Modul Node.js. In diesem Fall file-loader :

 npm install -D file-loader

Dann fügen wir eine neue Regel zum Abschnitt module.rules unserer Webpack-Konfiguration hinzu. Zum Beispiel:

 @@ -48,6 +48,13 @@ module.exports = function(_env, argv) { { test: /\.svg$/, use: ["@svgr/webpack"] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + loader: require.resolve("file-loader"), + options: { + name: "static/media/[name].[hash:8].[ext]" + } } ] },

Hier haben wir einen file-loader Dateien verweisen können. Sie können dieses Beispiel erweitern, um beliebige andere Arten von Dateien zu laden, die Sie benötigen.

Umgebungs-Plugin

Wir können das DefinePlugin() von Webpack verwenden, um Umgebungsvariablen aus der Build-Umgebung für unseren Anwendungscode verfügbar zu machen. Zum Beispiel:

 @@ -1,5 +1,6 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -65,7 +66,12 @@ module.exports = function(_env, argv) { new MiniCssExtractPlugin({ filename: "assets/css/[name].[contenthash:8].css", chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" - }) + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify( + isProduction ? "production" : "development" + ) + }) ].filter(Boolean) }; };

Hier haben wir process.env.NODE_ENV durch eine Zeichenfolge ersetzt, die den Build-Modus darstellt: "development" oder "production" .

HTML-Plugin

In Ermangelung einer index.html -Datei ist unser JavaScript-Bundle nutzlos, es sitzt einfach da, ohne dass es jemand finden kann. In diesem Abschnitt stellen wir html-webpack-plugin vor, um eine HTML-Datei für uns zu generieren.

Wir installieren html-webpack-plugin :

 npm install -D html-webpack-plugin

Dann fügen wir html-webpack-plugin zum Abschnitt plugins unserer Webpack-Konfiguration hinzu:

 @@ -1,6 +1,7 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -71,6 +72,10 @@ module.exports = function(_env, argv) { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development" ) + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + inject: true }) ].filter(Boolean) };

Die generierte Datei public/index.html lädt unser Bundle und bootet unsere Anwendung.

Optimierung

Es gibt mehrere Optimierungstechniken, die wir in unserem Build-Prozess verwenden können. Wir beginnen mit der Code-Minifizierung, einem Prozess, mit dem wir die Größe unseres Pakets ohne Kosteneinbußen in Bezug auf die Funktionalität reduzieren können. Wir verwenden zwei Plugins zum Minimieren unseres Codes: terser-webpack-plugin für JavaScript-Code und optimize-css-assets-webpack-plugin für CSS.

Lassen Sie uns sie installieren:

 npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin

Dann fügen wir unserer Konfiguration einen optimization hinzu:

 @@ -2,6 +2,8 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TerserWebpackPlugin = require("terser-webpack-plugin"); +const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -75,6 +77,27 @@ module.exports = function(_env, argv) { isProduction ? "production" : "development" ) }) - ].filter(Boolean) + ].filter(Boolean), + optimization: { + minimize: isProduction, + minimizer: [ + new TerserWebpackPlugin({ + terserOptions: { + compress: { + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + comments: false, + ascii_only: true + }, + warnings: false + } + }), + new OptimizeCssAssetsPlugin() + ] + } }; };

Die obigen Einstellungen stellen die Code-Kompatibilität mit allen modernen Browsern sicher.

Code-Splitting

Code-Splitting ist eine weitere Technik, mit der wir die Leistung unserer Anwendung verbessern können. Code-Splitting kann sich auf zwei unterschiedliche Ansätze beziehen:

  1. Mit einer dynamischen import() -Anweisung können wir Teile der Anwendung extrahieren, die einen erheblichen Teil unserer Bundle-Größe ausmachen, und sie bei Bedarf laden.
  2. Wir können Code extrahieren, der sich weniger häufig ändert, um das Browser-Caching zu nutzen und die Leistung für wiederkehrende Besucher zu verbessern.

Wir füllen den Abschnitt optimization.splitChunks unserer Webpack-Konfiguration mit Einstellungen zum Extrahieren von Abhängigkeiten von Drittanbietern und gemeinsamen Chunks in separate Dateien:

 @@ -99,7 +99,29 @@ module.exports = function(_env, argv) { sourceMap: true }), new OptimizeCssAssetsPlugin() - ] + ], + splitChunks: { + chunks: "all", + minSize: 0, + maxInitialRequests: 20, + maxAsyncRequests: 20, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name(module, chunks, cacheGroupKey) { + const packageName = module.context.match( + /[\\/]node_modules[\\/](.*?)([\\/]|$)/ + )[1]; + return `${cacheGroupKey}.${packageName.replace("@", "")}`; + } + }, + common: { + minChunks: 2, + priority: -10 + } + } + }, + runtimeChunk: "single" } }; };

Werfen wir einen genaueren Blick auf die Optionen, die wir hier verwendet haben:

  • chunks: "all" : Standardmäßig wirkt sich die allgemeine Chunk-Extraktion nur auf Module aus, die mit einem dynamischen import() geladen wurden. Diese Einstellung ermöglicht auch die Optimierung für das Laden am Einstiegspunkt.
  • minSize: 0 : Standardmäßig kommen nur Chunks über einem bestimmten Größenschwellenwert für die Extraktion in Frage. Diese Einstellung ermöglicht die Optimierung für allen gängigen Code, unabhängig von seiner Größe.
  • maxInitialRequests: 20 und maxAsyncChunks: 20 : Diese Einstellungen erhöhen die maximale Anzahl von Quelldateien, die parallel für Einstiegspunktimporte bzw. Splitpunktimporte geladen werden können.

Zusätzlich geben wir die folgende cacheGroups Konfiguration an:

  • vendors : Konfiguriert die Extraktion für Module von Drittanbietern.
    • test: /[\\/]node_modules[\\/]/ : Dateinamensmuster für übereinstimmende Abhängigkeiten von Drittanbietern.
    • name(module, chunks, cacheGroupKey) : Gruppiert separate Chunks aus demselben Modul, indem ihnen ein gemeinsamer Name gegeben wird.
  • common : Konfiguriert die Extraktion gemeinsamer Chunks aus dem Anwendungscode.
    • minChunks: 2 : Ein Chunk wird als gemeinsam betrachtet, wenn er von mindestens zwei Modulen referenziert wird.
    • priority: -10 : Weist der common Cache-Gruppe eine negative Priorität zu, sodass Chunks für die vendors -Cache-Gruppe zuerst berücksichtigt werden.

Wir extrahieren auch Webpack-Laufzeitcode in einem einzigen Chunk, der von mehreren Einstiegspunkten gemeinsam genutzt werden kann, indem runtimeChunk: "single" .

Dev-Server

Bisher haben wir uns darauf konzentriert, den Produktions-Build unserer Anwendung zu erstellen und zu optimieren, aber Webpack hat auch einen eigenen Webserver mit Live-Nachladen und Fehlerberichten, die uns im Entwicklungsprozess helfen werden. Es heißt webpack-dev-server und wir müssen es separat installieren:

 npm install -D webpack-dev-server

In diesem Snippet führen wir einen devServer Abschnitt in unsere Webpack-Konfiguration ein:

 @@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };

Hier haben wir die folgenden Optionen verwendet:

  • compress: true : Aktiviert die Asset-Komprimierung für schnelleres Neuladen.
  • historyApiFallback: true : Aktiviert einen Fallback auf index.html für verlaufsbasiertes Routing.
  • open: true : Öffnet den Browser nach dem Start des Dev-Servers.
  • overlay overlay: true : Zeigt Webpack-Fehler im Browserfenster an.

Möglicherweise müssen Sie auch Proxyeinstellungen konfigurieren, um API-Anforderungen an den Back-End-Server weiterzuleiten.

Webpack und React: Performance-optimiert und Ready!

Wir haben gerade gelernt, wie man verschiedene Ressourcentypen mit Webpack lädt, wie man Webpack in einer Entwicklungsumgebung verwendet und wie man verschiedene Techniken zur Optimierung eines Produktions-Builds verwendet. Bei Bedarf können Sie jederzeit die vollständige Konfigurationsdatei als Inspiration für Ihr eigenes React/Webpack-Setup überprüfen. Das Schärfen solcher Fähigkeiten ist Standard für jeden, der React-Entwicklungsdienste anbietet.

Im nächsten Teil dieser Reihe erweitern wir diese Konfiguration um Anweisungen für spezifischere Anwendungsfälle, einschließlich der Verwendung von TypeScript, CSS-Präprozessoren und erweiterten Optimierungstechniken mit serverseitigem Rendering und ServiceWorkers. Bleiben Sie dran, um alles zu erfahren, was Sie über Webpack wissen müssen, um Ihre React-Anwendung in die Produktion zu bringen.