Menține controlul: un ghid pentru Webpack și React, Pt. 1
Publicat: 2022-03-11Când începeți un nou proiect React, aveți multe șabloane din care să alegeți: Create React App, react-boilerplate
și React Starter Kit, pentru a numi câteva.
Aceste șabloane, adoptate de mii de dezvoltatori, sunt capabile să susțină dezvoltarea de aplicații la scară foarte mare. Dar ele lasă experiența dezvoltatorului și ieșirea pachetului cu diverse setări implicite, care ar putea să nu fie ideale.
Dacă doriți să mențineți un grad mai mare de control asupra procesului dvs. de construire, atunci puteți alege să investiți într-o configurație personalizată Webpack. După cum veți afla din acest tutorial Webpack, această sarcină nu este foarte complicată și cunoștințele ar putea fi chiar utile atunci când depanați configurațiile altor persoane.
Webpack: Noțiuni introductive
Modul în care scriem JavaScript astăzi este diferit de codul pe care îl poate executa browserul. Ne bazăm frecvent pe alte tipuri de resurse, limbi translate și funcții experimentale care nu sunt încă acceptate în browserele moderne. Webpack este un bundler de module pentru JavaScript care poate reduce acest decalaj și poate produce cod compatibil între browsere fără cheltuială atunci când vine vorba de experiența dezvoltatorului.
Înainte de a începe, ar trebui să rețineți că tot codul prezentat în acest tutorial Webpack este disponibil și sub forma unui exemplu de fișier de configurare Webpack/React complet pe GitHub. Vă rugăm să nu ezitați să îl consultați acolo și să reveniți la acest articol dacă aveți întrebări.
Configurație de bază
De la Legato (versiunea 4), Webpack nu necesită nicio configurație pentru a rula. Alegerea unui mod de construcție va aplica un set de valori implicite mai potrivite pentru mediul țintă. În spiritul acestui articol, vom elimina aceste valori implicite și vom implementa o configurație sensibilă pentru fiecare mediu țintă.
Mai întâi, trebuie să instalăm webpack
și webpack-cli
:
npm install -D webpack webpack-cli
Apoi trebuie să webpack.config.js
cu o configurație care să includă următoarele opțiuni:
-
devtool
: Permite generarea hărții sursă în modul de dezvoltare. -
entry
: Fișierul principal al aplicației noastre React. -
output.path
: directorul rădăcină în care să stocați fișierele de ieșire. -
output.filename
: Modelul numelui de fișier care trebuie utilizat pentru fișierele generate. -
output.publicPath
: Calea către directorul rădăcină unde fișierele vor fi implementate pe serverul web.
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: "/" } }; };
Configurația de mai sus funcționează bine pentru fișierele JavaScript simple. Dar când folosim Webpack și React, va trebui să efectuăm transformări suplimentare înainte de a expedia codul către utilizatorii noștri. În secțiunea următoare, vom folosi Babel pentru a schimba modul în care Webpack încarcă fișierele JavaScript.
JS Loader
Babel este un compilator JavaScript cu multe plugin-uri pentru transformarea codului. În această secțiune, îl vom introduce ca încărcător în configurația noastră Webpack și îl vom configura pentru a transforma codul JavaScript modern într-un astfel de lucru care este înțeles de browserele obișnuite.
Mai întâi, va trebui să instalăm babel-loader
și @babel/core
:
npm install -D @babel/core babel-loader
Apoi vom adăuga o secțiune de module
la configurația Webpack, făcându-l pe babel-loader
responsabil pentru încărcarea fișierelor 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"] } }; };
Vom configura Babel folosind un fișier de configurare separat, babel.config.js
. Va folosi următoarele caracteristici:
-
@babel/preset-env
: Transformă caracteristicile JavaScript moderne în cod compatibil cu versiunea inversă. -
@babel/preset-react
: Transformă sintaxa JSX în apeluri de funcții JavaScript simple. -
@babel/plugin-transform-runtime
: Reduce duplicarea codului prin extragerea ajutoarelor Babel în module partajate. -
@babel/plugin-syntax-dynamic-import
: Activează sintaxa dynamicimport()
în browserele care nu au suport nativ pentruPromise
. -
@babel/plugin-proposal-class-properties
: Activează suportul pentru propunerea de sintaxă a câmpului instanței publice, pentru scrierea componentelor React bazate pe clasă.
De asemenea, vom activa câteva optimizări ale producției specifice React:
-
babel-plugin-transform-react-remove-prop-types
elimină tipurile de prop inutile din codul de producție. -
@babel/plugin-transform-react-inline-elements
evalueazăReact.createElement
în timpul compilării și aliniază rezultatul. -
@babel/plugin-transform-react-constant-elements
extrage elementele React statice ca constante.
Comanda de mai jos va instala toate dependențele necesare:
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
Apoi vom popula babel.config.js
cu aceste setări:
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" ] } } };
Această configurație ne permite să scriem JavaScript modern într-un mod compatibil cu toate browserele relevante. Există și alte tipuri de resurse de care am putea avea nevoie într-o aplicație React, pe care le vom acoperi în secțiunile următoare.
Încărcător CSS
Când vine vorba de stilarea aplicațiilor React, trebuie să putem include cel puțin fișiere CSS simple. Vom face acest lucru în Webpack folosind următoarele încărcătoare:
-
css-loader
: analizează fișierele CSS, rezolvând resurse externe, cum ar fi imagini, fonturi și importuri suplimentare de stil. -
style-loader
: în timpul dezvoltării, injectează stiluri încărcate în document în timpul rulării. -
mini-css-extract-plugin
: extrage stilurile încărcate în fișiere separate pentru utilizare în producție pentru a profita de memorarea în cache a browserului.
Să instalăm încărcătoarele CSS de mai sus:
npm install -D css-loader style-loader mini-css-extract-plugin
Apoi vom adăuga o nouă regulă la secțiunea module.rules
din configurația 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" + ] } ] },
Vom adăuga, de asemenea, MiniCssExtractPlugin
la secțiunea de plugins
, pe care o vom activa numai în modul producție:
@@ -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) }; };
Această configurație funcționează pentru fișiere CSS simple și poate fi extinsă pentru a funcționa cu diferite procesoare CSS, cum ar fi Sass și PostCSS, despre care vom discuta în articolul următor.
Încărcător de imagini
Webpack poate fi folosit și pentru a încărca resurse statice, cum ar fi imagini, videoclipuri și alte fișiere binare. Cel mai generic mod de a gestiona astfel de tipuri de fișiere este utilizarea file-loader
sau url-loader
, care va oferi consumatorilor săi o referință URL pentru resursele necesare.
În această secțiune, vom adăuga url-loader
pentru a gestiona formatele comune de imagine. Ceea ce diferențiază url-loader
de file-loader
este că, dacă dimensiunea fișierului original este mai mică decât un anumit prag, acesta va încorpora întregul fișier în adresa URL ca conținut codificat base64, eliminând astfel necesitatea unei cereri suplimentare.
Mai întâi instalăm url-loader
:
npm install -D url-loader
Apoi adăugăm o nouă regulă la secțiunea module.rules
din configurația Webpack:
@@ -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
Pentru imaginile SVG, vom folosi @svgr/webpack
loader, care transformă fișierele importate în componente React.
Instalăm @svgr/webpack
:
npm install -D @svgr/webpack
Apoi adăugăm o nouă regulă la secțiunea module.rules
din configurația Webpack:
@@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },
Imaginile SVG ca componente React pot fi convenabile, iar @svgr/webpack
efectuează optimizarea folosind SVGO.

Notă: pentru anumite animații sau chiar efecte de trecere cu mouse-ul, poate fi necesar să manipulați SVG-ul folosind JavaScript. Din fericire, @svgr/webpack
încorporează în mod implicit conținutul SVG în pachetul JavaScript, permițându-vă să ocoliți restricțiile de securitate necesare pentru aceasta.
Încărcător de fișiere
Când trebuie să facem referire la orice alte tipuri de fișiere, file-loader
generic va face treaba. Funcționează în mod similar cu url-loader
, oferind o adresă URL a materialului codului care o solicită, dar nu încearcă să-l optimizeze.
Ca întotdeauna, mai întâi instalăm modulul Node.js. În acest caz, file-loader
:
npm install -D file-loader
Apoi adăugăm o nouă regulă la secțiunea module.rules
din configurația Webpack. De exemplu:
@@ -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]" + } } ] },
Aici am adăugat un dispozitiv de încărcare file-loader
pentru încărcarea fonturilor, pe care le puteți consulta din fișierele dvs. CSS. Puteți extinde acest exemplu pentru a încărca orice alte tipuri de fișiere de care aveți nevoie.
Plugin de mediu
Putem folosi DefinePlugin()
de la Webpack pentru a expune variabilele de mediu din mediul de compilare la codul aplicației noastre. De exemplu:
@@ -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) }; };
Aici am înlocuit process.env.NODE_ENV
cu un șir reprezentând modul de construire: "development"
sau "production"
.
Plugin HTML
În absența unui fișier index.html
, pachetul nostru JavaScript este inutil, doar stând acolo fără ca nimeni să-l găsească. În această secțiune, vom introduce html-webpack-plugin
pentru a genera un fișier HTML pentru noi.
Instalăm html-webpack-plugin
:
npm install -D html-webpack-plugin
Apoi adăugăm html-webpack-plugin
la secțiunea de plugins
din configurația noastră 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) };
Fișierul public/index.html
generat va încărca pachetul nostru și va porni aplicația noastră.
Optimizare
Există mai multe tehnici de optimizare pe care le putem folosi în procesul nostru de construire. Vom începe cu minificarea codului, un proces prin care putem reduce dimensiunea pachetului nostru fără cheltuială în ceea ce privește funcționalitatea. Vom folosi două pluginuri pentru a minimiza codul nostru: terser-webpack-plugin
pentru codul JavaScript și optimize-css-assets-webpack-plugin
pentru CSS.
Să le instalăm:
npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin
Apoi vom adăuga o secțiune de optimization
la configurația noastră:
@@ -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() + ] + } }; };
Setările de mai sus vor asigura compatibilitatea codului cu toate browserele moderne.
Divizarea codului
Divizarea codului este o altă tehnică pe care o putem folosi pentru a îmbunătăți performanța aplicației noastre. Divizarea codului se poate referi la două abordări diferite:
- Folosind o instrucțiune
import()
dinamică, putem extrage părți ale aplicației care reprezintă o parte semnificativă din dimensiunea pachetului nostru și le putem încărca la cerere. - Putem extrage cod care se modifică mai rar, pentru a profita de stocarea în cache a browserului și pentru a îmbunătăți performanța pentru vizitatorii care repetă.
Vom completa secțiunea optimization.splitChunks
a configurației noastre Webpack cu setări pentru extragerea dependențelor terțelor părți și a fragmentelor comune în fișiere separate:
@@ -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" } }; };
Să aruncăm o privire mai profundă asupra opțiunilor pe care le-am folosit aici:
-
chunks: "all"
: În mod implicit, extragerea fragmentelor comune afectează numai modulele încărcate cu unimport()
dinamic. Această setare permite optimizarea și pentru încărcarea punctului de intrare. -
minSize: 0
: În mod implicit, numai bucățile peste un anumit prag de dimensiune devin eligibile pentru extracție. Această setare permite optimizarea pentru tot codul obișnuit, indiferent de dimensiunea acestuia. -
maxInitialRequests: 20
șimaxAsyncChunks: 20
: Aceste setări măresc numărul maxim de fișiere sursă care pot fi încărcate în paralel pentru importurile de puncte de intrare și, respectiv, importurile de puncte de împărțire.
În plus, specificăm următoarea configurație cacheGroups
:
-
vendors
: Configurați extracția pentru module terțe.-
test: /[\\/]node_modules[\\/]/
: model de nume de fișier pentru potrivirea dependențelor de la terți. -
name(module, chunks, cacheGroupKey)
: Grupează bucățile separate din același modul, dându-le un nume comun.
-
-
common
: Configurați extragerea bucăților comune din codul aplicației.-
minChunks: 2
: O bucată va fi considerată obișnuită dacă se face referire din cel puțin două module. -
priority: -10
: Atribuie o prioritate negativă grupului cachecommon
, astfel încât bucățile pentru grupul cache alvendors
să fie luate în considerare mai întâi.
-
De asemenea, extragem codul de rulare Webpack într-o singură bucată care poate fi partajată între mai multe puncte de intrare, prin specificarea runtimeChunk: "single"
.
Dev Server
Până acum, ne-am concentrat pe crearea și optimizarea versiunii de producție a aplicației noastre, dar Webpack are și propriul server web cu reîncărcare live și raportare a erorilor, ceea ce ne va ajuta în procesul de dezvoltare. Se numește webpack-dev-server
și trebuie să-l instalăm separat:
npm install -D webpack-dev-server
În acest fragment, introducem o secțiune devServer
în configurația Webpack:
@@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };
Aici am folosit următoarele opțiuni:
-
compress: true
: permite comprimarea activelor pentru reîncărcări mai rapide. -
historyApiFallback: true
: Activează o alternativă laindex.html
pentru rutarea bazată pe istoric. -
open: true
: Deschide browserul după lansarea serverului de dezvoltare. -
overlay: true
: afișează erorile Webpack în fereastra browserului.
De asemenea, ar putea fi necesar să configurați setările proxy pentru a redirecționa solicitările API către serverul de backend.
Webpack și React: Performanță optimizată și gata!
Tocmai am învățat cum să încărcăm diferite tipuri de resurse cu Webpack, cum să folosim Webpack într-un mediu de dezvoltare și câteva tehnici pentru optimizarea unei versiuni de producție. Dacă aveți nevoie, puteți oricând să examinați fișierul de configurare complet pentru a vă inspira propria configurație React/Webpack. Ascuțirea unor astfel de abilități este tariful standard pentru oricine oferă servicii de dezvoltare React.
În următoarea parte a acestei serii, vom extinde această configurație cu instrucțiuni pentru cazuri de utilizare mai specifice, inclusiv utilizarea TypeScript, preprocesoare CSS și tehnici avansate de optimizare care implică randarea pe server și ServiceWorkers. Rămâi la curent pentru a afla tot ce trebuie să știi despre Webpack pentru a duce aplicația React la producție.