Utrzymuj kontrolę: przewodnik po pakietach internetowych i reagowaniu, cz. 1

Opublikowany: 2022-03-11

Rozpoczynając nowy projekt React, masz do wyboru wiele szablonów: Stwórz aplikację React, React react-boilerplate i React Starter Kit, żeby wymienić tylko kilka.

Szablony te, przyjęte przez tysiące programistów, są w stanie wspierać tworzenie aplikacji na bardzo dużą skalę. Ale pozostawiają doświadczenie programisty i pakują dane wyjściowe obarczone różnymi wartościami domyślnymi, co może nie być idealne.

Jeśli chcesz zachować większą kontrolę nad procesem kompilacji, możesz zainwestować w niestandardową konfigurację WebPack. Jak dowiesz się z tego samouczka Webpack, to zadanie nie jest zbyt skomplikowane, a wiedza może być nawet przydatna podczas rozwiązywania problemów z konfiguracjami innych osób.

Pakiet internetowy: Pierwsze kroki

Sposób, w jaki dzisiaj piszemy JavaScript, różni się od kodu, który może wykonać przeglądarka. Często polegamy na innych typach zasobów, transpilowanych językach i eksperymentalnych funkcjach, które nie są jeszcze obsługiwane w nowoczesnych przeglądarkach. Webpack to pakiet modułów dla JavaScript, który może wypełnić tę lukę i bez żadnych kosztów, jeśli chodzi o doświadczenie programisty, stworzyć kod kompatybilny z różnymi przeglądarkami.

Zanim zaczniemy, należy pamiętać, że cały kod przedstawiony w tym samouczku Webpack jest również dostępny w postaci kompletnego przykładowego pliku konfiguracyjnego Webpack/React na GitHub. Zachęcamy do odwoływania się do tego tam i wróć do tego artykułu, jeśli masz jakiekolwiek pytania.

Konfiguracja podstawowa

Od Legato (wersja 4), Webpack nie wymaga żadnej konfiguracji do uruchomienia. Wybranie trybu kompilacji spowoduje zastosowanie zestawu wartości domyślnych bardziej odpowiedniego dla środowiska docelowego. W duchu tego artykułu zamierzamy odłożyć te wartości domyślne na bok i samodzielnie zaimplementować rozsądną konfigurację dla każdego środowiska docelowego.

Najpierw musimy zainstalować webpack i webpack-cli :

 npm install -D webpack webpack-cli

Następnie musimy wypełnić webpack.config.js konfiguracją zawierającą następujące opcje:

  • devtool : Włącza generowanie mapy źródłowej w trybie programistycznym.
  • entry : Główny plik naszej aplikacji React.
  • output.path : katalog główny do przechowywania plików wyjściowych.
  • output.filename : Wzorzec nazwy pliku do użycia dla generowanych plików.
  • output.publicPath : ścieżka do katalogu głównego, w którym pliki zostaną wdrożone na serwerze WWW.
 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: "/" } }; };

Powyższa konfiguracja działa dobrze dla zwykłych plików JavaScript. Ale korzystając z Webpack i React, będziemy musieli wykonać dodatkowe przekształcenia przed wysłaniem kodu do naszych użytkowników. W następnej sekcji użyjemy Babel, aby zmienić sposób, w jaki Webpack ładuje pliki JavaScript.

Program ładujący JS

Babel to kompilator JavaScript z wieloma wtyczkami do transformacji kodu. W tej sekcji wprowadzimy go jako loadera do naszej konfiguracji Webpacka i skonfigurujemy do przekształcania współczesnego kodu JavaScript na taki, jaki jest zrozumiały dla popularnych przeglądarek.

Najpierw musimy zainstalować babel-loader i @babel/core :

 npm install -D @babel/core babel-loader

Następnie dodamy sekcję module do naszej konfiguracji Webpack, dzięki czemu babel-loader będzie odpowiedzialny za ładowanie plików JavaScript:

 @@ -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"] } }; };

Skonfigurujemy Babel za pomocą oddzielnego pliku konfiguracyjnego, babel.config.js . Będzie korzystać z następujących funkcji:

  • @babel/preset-env : Przekształca nowoczesne funkcje JavaScript w kod zgodny wstecz.
  • @babel/preset-react : Przekształca składnię JSX na zwykłe wywołania funkcji JavaScript.
  • @babel/plugin-transform-runtime : Zmniejsza duplikację kodu poprzez wyodrębnienie pomocników Babel do współdzielonych modułów.
  • @babel/plugin-syntax-dynamic-import : Włącza dynamiczną składnię import() w przeglądarkach, które nie obsługują natywnej obsługi Promise .
  • @babel/plugin-proposal-class-properties : Włącza obsługę propozycji składni pola instancji publicznej, do pisania komponentów React opartych na klasach.

Włączymy również kilka optymalizacji produkcji specyficznych dla Reacta:

  • babel-plugin-transform-react-remove-prop-types usuwa niepotrzebne typy prop-types z kodu produkcyjnego.
  • @babel/plugin-transform-react-inline-elements ocenia React.createElement podczas kompilacji i umieszcza wynik.
  • @babel/plugin-transform-react-constant-elements wyodrębnia statyczne elementy React jako stałe.

Poniższe polecenie zainstaluje wszystkie niezbędne zależności:

 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

Następnie babel.config.js następującymi ustawieniami:

 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" ] } } };

Taka konfiguracja pozwala nam pisać nowoczesny JavaScript w sposób zgodny ze wszystkimi odpowiednimi przeglądarkami. Istnieją inne rodzaje zasobów, których możemy potrzebować w aplikacji React, które omówimy w kolejnych sekcjach.

Moduł ładujący CSS

Jeśli chodzi o stylizowanie aplikacji React, musimy mieć możliwość dołączenia do minimum zwykłych plików CSS. Zrobimy to w Webpack za pomocą następujących programów ładujących:

  • css-loader : analizuje pliki CSS, rozpatrując zasoby zewnętrzne, takie jak obrazy, czcionki i dodatkowe importy stylów.
  • style-loader : podczas programowania wstrzykuje załadowane style do dokumentu w czasie wykonywania.
  • mini-css-extract-plugin : Wyodrębnia załadowane style do oddzielnych plików do użytku produkcyjnego, aby wykorzystać buforowanie przeglądarki.

Zainstalujmy powyższe programy ładujące CSS:

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

Następnie dodamy nową regułę do sekcji module.rules naszej konfiguracji Webpack:

 @@ -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" + ] } ] },

Dodamy również MiniCssExtractPlugin do sekcji plugins , które włączymy tylko w trybie produkcyjnym:

 @@ -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) }; };

Ta konfiguracja działa dla zwykłych plików CSS i może być rozszerzona do pracy z różnymi procesorami CSS, takimi jak Sass i PostCSS, które omówimy w następnym artykule.

Ładowarka obrazów

Webpack może być również używany do ładowania zasobów statycznych, takich jak obrazy, filmy i inne pliki binarne. Najbardziej ogólnym sposobem obsługi takich typów plików jest użycie file-loader lub programu ładującego url-loader , które zapewnią odniesienie URL do wymaganych zasobów użytkownikom.

W tej sekcji dodamy url-loader do obsługi popularnych formatów graficznych. To, co odróżnia url-loader od file-loader polega na tym, że jeśli rozmiar oryginalnego pliku jest mniejszy niż określony próg, cały plik zostanie osadzony w adresie URL jako zawartość zakodowana w base64, eliminując w ten sposób potrzebę dodatkowego żądania.

Najpierw instalujemy url-loader :

 npm install -D url-loader

Następnie dodajemy nową regułę do sekcji module.rules naszej konfiguracji Webpacka:

 @@ -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

W przypadku obrazów SVG użyjemy programu @svgr/webpack , który przekształca importowane pliki w komponenty React.

Instalujemy @svgr/webpack :

 npm install -D @svgr/webpack

Następnie dodajemy nową regułę do sekcji module.rules naszej konfiguracji Webpacka:

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

Obrazy SVG jako komponenty React mogą być wygodne, a @svgr/webpack przeprowadza optymalizację przy użyciu SVO.

Uwaga: w przypadku niektórych animacji, a nawet efektów najechania kursorem myszy, może być konieczne manipulowanie SVG za pomocą JavaScript. Na szczęście @svgr/webpack osadza zawartość SVG w pakiecie JavaScript, co pozwala ominąć wymagane do tego ograniczenia bezpieczeństwa.

Program ładujący pliki

Kiedy potrzebujemy odwołać się do innych rodzajów plików, ogólny program ładujący file-loader wykona to zadanie. Działa podobnie do programu url-loader , dostarczając adres URL zasobu do kodu, który tego wymaga, ale nie próbuje go optymalizować.

Jak zawsze, najpierw instalujemy moduł Node.js. W tym przypadku file-loader :

 npm install -D file-loader

Następnie dodajemy nową regułę do sekcji module.rules naszej konfiguracji Webpacka. Na przykład:

 @@ -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]" + } } ] },

Tutaj dodaliśmy file-loader do ładowania czcionek, do którego można się odwoływać w plikach CSS. Możesz rozszerzyć ten przykład, aby załadować dowolne inne rodzaje plików, których potrzebujesz.

Wtyczka środowiska

Możemy użyć funkcji DefinePlugin() z pakietu Webpack, aby udostępnić zmienne środowiskowe ze środowiska kompilacji w naszym kodzie aplikacji. Na przykład:

 @@ -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) }; };

Tutaj process.env.NODE_ENV napisem reprezentującym tryb budowania: "development" lub "production" .

Wtyczka HTML

W przypadku braku pliku index.html nasz pakiet JavaScript jest bezużyteczny, po prostu siedzi tam i nikt nie jest w stanie go znaleźć. W tej sekcji przedstawimy html-webpack-plugin do generowania dla nas pliku HTML.

Instalujemy html-webpack-plugin :

 npm install -D html-webpack-plugin

Następnie dodajemy html-webpack-plugin do sekcji plugins w naszej konfiguracji Webpack:

 @@ -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) };

Wygenerowany plik public/index.html załaduje nasz pakiet i załaduje naszą aplikację.

Optymalizacja

Istnieje kilka technik optymalizacji, których możemy użyć w naszym procesie budowania. Zaczniemy od minifikacji kodu, procesu, dzięki któremu możemy zmniejszyć rozmiar naszego pakietu bez żadnych kosztów związanych z funkcjonalnością. Aby zminimalizować nasz kod, użyjemy dwóch wtyczek: terser-webpack-plugin dla kodu JavaScript iOptimize optimize-css-assets-webpack-plugin dla CSS.

Zainstalujmy je:

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

Następnie dodamy sekcję optimization do naszej konfiguracji:

 @@ -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() + ] + } }; };

Powyższe ustawienia zapewnią zgodność kodu ze wszystkimi nowoczesnymi przeglądarkami.

Dzielenie kodu

Podział kodu to kolejna technika, której możemy użyć do poprawy wydajności naszej aplikacji. Podział kodu może odnosić się do dwóch różnych podejść:

  1. Używając dynamicznej instrukcji import() , możemy wyodrębnić części aplikacji, które stanowią znaczną część naszego rozmiaru pakietu, i załadować je na żądanie.
  2. Możemy wyodrębnić kod, który zmienia się rzadziej, aby wykorzystać buforowanie przeglądarki i poprawić wydajność dla powracających użytkowników.

optimization.splitChunks sekcjęoptim.splitChunks naszej konfiguracji pakietu internetowego ustawieniami wyodrębniania zależności stron trzecich i typowych fragmentów do osobnych plików:

 @@ -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" } }; };

Przyjrzyjmy się dokładniej opcjom, których tutaj użyliśmy:

  • chunks: "all" : Domyślnie wspólne wyodrębnianie porcji dotyczy tylko modułów załadowanych za pomocą dynamicznego import() . To ustawienie umożliwia również optymalizację ładowania punktu wejścia.
  • minSize: 0 : domyślnie tylko fragmenty powyżej określonego progu rozmiaru kwalifikują się do wyodrębnienia. To ustawienie umożliwia optymalizację całego wspólnego kodu, niezależnie od jego rozmiaru.
  • maxInitialRequests: 20 i maxAsyncChunks: 20 : te ustawienia zwiększają maksymalną liczbę plików źródłowych, które można ładować równolegle odpowiednio dla importów punktu wejścia i importu punktu podziału.

Dodatkowo określamy następującą konfigurację cacheGroups :

  • vendors : konfiguruje wyodrębnianie modułów innych firm.
    • test: /[\\/]node_modules[\\/]/ : Wzorzec nazwy pliku do dopasowywania zależności innych firm.
    • name(module, chunks, cacheGroupKey) : Grupy oddzielają fragmenty tego samego modułu, nadając im wspólną nazwę.
  • common : Konfiguruje wyodrębnianie wspólnych porcji z kodu aplikacji.
    • minChunks: 2 : porcja zostanie uznana za wspólną, jeśli będzie się do niej odwoływać co najmniej z dwóch modułów.
    • priority: -10 : Przypisuje ujemny priorytet do common grupy pamięci podręcznej, aby porcje dla grupy pamięci podręcznej vendors były brane pod uwagę jako pierwsze.

Wyodrębniamy również kod środowiska wykonawczego Webpack w jednej porcji, która może być współużytkowana przez wiele punktów wejścia, określając runtimeChunk: "single" .

Serwer deweloperski

Do tej pory skupialiśmy się na tworzeniu i optymalizacji produkcyjnego buildu naszej aplikacji, ale Webpack posiada również własny serwer WWW z przeładowywaniem na żywo i raportowaniem błędów, co pomoże nam w procesie rozwoju. Nazywa się webpack-dev-server i musimy go zainstalować osobno:

 npm install -D webpack-dev-server

W tym fragmencie wprowadzamy sekcję devServer do naszej konfiguracji Webpack:

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

Tutaj wykorzystaliśmy następujące opcje:

  • compress: true : Włącza kompresję zasobów w celu szybszego przeładowania.
  • historyApiFallback: true : Włącza powrót do index.html dla routingu opartego na historii.
  • open: true : otwiera przeglądarkę po uruchomieniu serwera deweloperskiego.
  • overlay: true : Wyświetla błędy pakietu Webpack w oknie przeglądarki.

Może być również konieczne skonfigurowanie ustawień proxy, aby przekazywać żądania interfejsu API do serwera zaplecza.

Webpack i React: zoptymalizowane pod kątem wydajności i gotowe!

Właśnie dowiedzieliśmy się, jak ładować różne typy zasobów za pomocą Webpack, jak używać Webpack w środowisku programistycznym i kilka technik optymalizacji kompilacji produkcyjnej. Jeśli zajdzie taka potrzeba, zawsze możesz przejrzeć pełny plik konfiguracyjny, aby uzyskać inspirację dla własnej konfiguracji React/Webpack. Wyostrzanie takich umiejętności to standardowa opłata dla każdego, kto oferuje usługi programistyczne React.

W następnej części tej serii rozszerzymy tę konfigurację o instrukcje dotyczące bardziej konkretnych przypadków użycia, w tym użycia TypeScript, preprocesorów CSS oraz zaawansowanych technik optymalizacji obejmujących renderowanie po stronie serwera i weryfikatory usług. Bądź na bieżąco, aby dowiedzieć się wszystkiego, co musisz wiedzieć o Webpack, aby wprowadzić swoją aplikację React do produkcji.