Mantenere il controllo: una guida a Webpack e reagire, pt. 1
Pubblicato: 2022-03-11Quando avvii un nuovo progetto React, hai molti modelli tra cui scegliere: Create React App, react-boilerplate
e React Starter Kit, solo per citarne alcuni.
Questi modelli, adottati da migliaia di sviluppatori, sono in grado di supportare lo sviluppo di applicazioni su larga scala. Ma lasciano l'esperienza dello sviluppatore e l'output in bundle sellato con varie impostazioni predefinite, il che potrebbe non essere l'ideale.
Se desideri mantenere un maggiore livello di controllo sul processo di compilazione, puoi scegliere di investire in una configurazione Webpack personalizzata. Come imparerai da questo tutorial sul Webpack, questa attività non è molto complicata e la conoscenza potrebbe anche essere utile durante la risoluzione dei problemi delle configurazioni di altre persone.
Pacchetto Web: per iniziare
Il modo in cui scriviamo JavaScript oggi è diverso dal codice che il browser può eseguire. Ci affidiamo spesso ad altri tipi di risorse, linguaggi trasferiti e funzionalità sperimentali che devono ancora essere supportate nei browser moderni. Webpack è un bundler di moduli per JavaScript in grado di colmare questa lacuna e produrre codice compatibile con più browser senza alcun costo per quanto riguarda l'esperienza degli sviluppatori.
Prima di iniziare, tieni presente che tutto il codice presentato in questo tutorial Webpack è disponibile anche sotto forma di un file di configurazione di esempio Webpack/React completo su GitHub. Sentiti libero di fare riferimento ad esso lì e di tornare a questo articolo in caso di domande.
Configurazione di base
A partire da Legato (versione 4), Webpack non richiede alcuna configurazione per essere eseguito. La scelta di una modalità di compilazione applicherà una serie di impostazioni predefinite più adatte all'ambiente di destinazione. Nello spirito di questo articolo, elimineremo queste impostazioni predefinite e implementeremo noi stessi una configurazione ragionevole per ogni ambiente di destinazione.
Innanzitutto, dobbiamo installare webpack
e webpack-cli
:
npm install -D webpack webpack-cli
Quindi dobbiamo popolare webpack.config.js
con una configurazione con le seguenti opzioni:
-
devtool
: abilita la generazione della mappa dei sorgenti in modalità di sviluppo. -
entry
: Il file principale della nostra applicazione React. -
output.path
: la directory principale in cui archiviare i file di output. -
output.filename
: il modello del nome file da utilizzare per i file generati. -
output.publicPath
: il percorso della directory principale in cui i file verranno distribuiti sul server 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: "/" } }; };
La configurazione sopra funziona bene per i file JavaScript semplici. Ma quando utilizziamo Webpack e React, dovremo eseguire ulteriori trasformazioni prima di spedire il codice ai nostri utenti. Nella prossima sezione, useremo Babel per cambiare il modo in cui Webpack carica i file JavaScript.
Caricatore JS
Babel è un compilatore JavaScript con molti plugin per la trasformazione del codice. In questa sezione, lo introdurremo come caricatore nella nostra configurazione di Webpack e lo configureremo per trasformare il codice JavaScript moderno in modo tale che sia compreso dai browser comuni.
Per prima cosa, dovremo installare babel-loader
e @babel/core
:
npm install -D @babel/core babel-loader
Quindi aggiungeremo una sezione del module
alla nostra configurazione del Webpack, rendendo babel-loader
responsabile del caricamento dei file 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"] } }; };
Configureremo Babel utilizzando un file di configurazione separato, babel.config.js
. Utilizzerà le seguenti funzionalità:
-
@babel/preset-env
: trasforma le moderne funzionalità JavaScript in codice compatibile con le versioni precedenti. -
@babel/preset-react
: trasforma la sintassi JSX in semplici chiamate di funzione JavaScript. -
@babel/plugin-transform-runtime
: riduce la duplicazione del codice estraendo gli helper Babel in moduli condivisi. -
@babel/plugin-syntax-dynamic-import
: abilita la sintassi dynamicimport()
nei browser privi del supporto nativo diPromise
. -
@babel/plugin-proposal-class-properties
: abilita il supporto per la proposta di sintassi del campo dell'istanza pubblica, per la scrittura di componenti React basati sulla classe.
Consentiremo anche alcune ottimizzazioni di produzione specifiche di React:
-
babel-plugin-transform-react-remove-prop-types
rimuove i tipi prop non necessari dal codice di produzione. -
@babel/plugin-transform-react-inline-elements
valutaReact.createElement
durante la compilazione e incorpora il risultato. -
@babel/plugin-transform-react-constant-elements
estrae gli elementi statici di React come costanti.
Il comando seguente installerà tutte le dipendenze necessarie:
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
Poi babel.config.js
con queste impostazioni:
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" ] } } };
Questa configurazione ci consente di scrivere JavaScript moderno in un modo compatibile con tutti i browser pertinenti. Ci sono altri tipi di risorse di cui potremmo aver bisogno in un'applicazione React, che tratteremo nelle sezioni seguenti.
Caricatore CSS
Quando si tratta di dare uno stile alle applicazioni React, come minimo, dobbiamo essere in grado di includere semplici file CSS. Lo faremo in Webpack utilizzando i seguenti caricatori:
-
css-loader
: analizza i file CSS, risolvendo risorse esterne, come immagini, caratteri e importazioni di stile aggiuntive. -
style-loader
: durante lo sviluppo, inserisce gli stili caricati nel documento in fase di esecuzione. -
mini-css-extract-plugin
: estrae gli stili caricati in file separati per l'uso in produzione per sfruttare la memorizzazione nella cache del browser.
Installiamo i caricatori CSS di cui sopra:
npm install -D css-loader style-loader mini-css-extract-plugin
Quindi aggiungeremo una nuova regola alla sezione module.rules
della nostra configurazione 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" + ] } ] },
Aggiungeremo anche MiniCssExtractPlugin
alla sezione dei plugins
, che abiliteremo solo in modalità produzione:
@@ -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) }; };
Questa configurazione funziona per file CSS semplici e può essere estesa per funzionare con vari processori CSS, come Sass e PostCSS, di cui parleremo nel prossimo articolo.
Caricatore di immagini
Webpack può essere utilizzato anche per caricare risorse statiche come immagini, video e altri file binari. Il modo più generico per gestire tali tipi di file è utilizzare file-loader
o url-loader
, che fornirà ai consumatori un riferimento URL per le risorse richieste.
In questa sezione, aggiungeremo url-loader
per gestire formati di immagine comuni. Ciò che distingue url-loader
da file-loader
è che se la dimensione del file originale è inferiore a una determinata soglia, incorporerà l'intero file nell'URL come contenuto con codifica base64, eliminando così la necessità di una richiesta aggiuntiva.
Per prima cosa installiamo url-loader
:
npm install -D url-loader
Quindi aggiungiamo una nuova regola alla sezione module.rules
della nostra configurazione 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
Per le immagini SVG, utilizzeremo il @svgr/webpack
, che trasforma i file importati in componenti React.
Installiamo @svgr/webpack
:
npm install -D @svgr/webpack
Quindi aggiungiamo una nuova regola alla sezione module.rules
della nostra configurazione Webpack:
@@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },
Le immagini SVG come componenti React possono essere convenienti e @svgr/webpack
esegue l'ottimizzazione utilizzando SVGO.

Nota: per alcune animazioni o anche effetti al passaggio del mouse, potrebbe essere necessario manipolare l'SVG utilizzando JavaScript. Fortunatamente, @svgr/webpack
incorpora i contenuti SVG nel bundle JavaScript per impostazione predefinita, consentendoti di aggirare le restrizioni di sicurezza necessarie per questo.
Caricatore di file
Quando abbiamo bisogno di fare riferimento a qualsiasi altro tipo di file, il file-loader
generico farà il lavoro. Funziona in modo simile a url-loader
, fornendo un URL di asset al codice che lo richiede, ma non tenta di ottimizzarlo.
Come sempre, prima installiamo il modulo Node.js. In questo caso, file-loader
:
npm install -D file-loader
Quindi aggiungiamo una nuova regola alla sezione module.rules
del nostro Webpack config. Per esempio:
@@ -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]" + } } ] },
Qui abbiamo aggiunto file-loader
per il caricamento dei caratteri, a cui puoi fare riferimento dai tuoi file CSS. Puoi estendere questo esempio per caricare qualsiasi altro tipo di file di cui hai bisogno.
Plug-in ambiente
Possiamo utilizzare DefinePlugin()
di Webpack per esporre le variabili di ambiente dall'ambiente di compilazione al codice dell'applicazione. Per esempio:
@@ -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) }; };
Qui abbiamo sostituito process.env.NODE_ENV
con una stringa che rappresenta la modalità di compilazione: "development"
o "production"
.
Plugin HTML
In assenza di un file index.html
, il nostro bundle JavaScript è inutile, semplicemente seduto lì senza che nessuno sia in grado di trovarlo. In questa sezione, introdurremo html-webpack-plugin
per generare un file HTML per noi.
Installiamo html-webpack-plugin
:
npm install -D html-webpack-plugin
Quindi aggiungiamo html-webpack-plugin
alla sezione plugins
della nostra configurazione 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) };
Il file public/index.html
generato caricherà il nostro bundle e avvierà la nostra applicazione.
Ottimizzazione
Esistono diverse tecniche di ottimizzazione che possiamo utilizzare nel nostro processo di creazione. Inizieremo con la minimizzazione del codice, un processo attraverso il quale possiamo ridurre le dimensioni del nostro pacchetto senza alcun costo in termini di funzionalità. Utilizzeremo due plugin per ridurre al minimo il nostro codice: terser-webpack-plugin
per il codice JavaScript e optimization optimize-css-assets-webpack-plugin
per CSS.
Installiamoli:
npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin
Quindi aggiungeremo una sezione di optimization
alla nostra configurazione:
@@ -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() + ] + } }; };
Le impostazioni sopra garantiranno la compatibilità del codice con tutti i browser moderni.
Divisione del codice
La suddivisione del codice è un'altra tecnica che possiamo utilizzare per migliorare le prestazioni della nostra applicazione. La suddivisione del codice può fare riferimento a due diversi approcci:
- Utilizzando un'istruzione
import()
dinamica, possiamo estrarre parti dell'applicazione che costituiscono una parte significativa della dimensione del nostro pacchetto e caricarle su richiesta. - Possiamo estrarre il codice che cambia meno frequentemente, al fine di sfruttare la memorizzazione nella cache del browser e migliorare le prestazioni per i visitatori abituali.
Popoleremo la sezione optimization.splitChunks
della nostra configurazione di Webpack con le impostazioni per l'estrazione di dipendenze di terze parti e blocchi comuni in file separati:
@@ -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" } }; };
Diamo uno sguardo più approfondito alle opzioni che abbiamo utilizzato qui:
-
chunks: "all"
: per impostazione predefinita, l'estrazione di chunk comune riguarda solo i moduli caricati con unimport()
dinamico. Questa impostazione consente l'ottimizzazione anche per il caricamento del punto di ingresso. -
minSize: 0
: per impostazione predefinita, solo i blocchi al di sopra di una determinata soglia di dimensione diventano idonei per l'estrazione. Questa impostazione consente l'ottimizzazione per tutto il codice comune indipendentemente dalle sue dimensioni. -
maxInitialRequests: 20
emaxAsyncChunks: 20
: queste impostazioni aumentano il numero massimo di file di origine che possono essere caricati in parallelo rispettivamente per l'importazione del punto di ingresso e per l'importazione del punto di divisione.
Inoltre, specifichiamo la seguente configurazione cacheGroups
:
-
vendors
: configura l'estrazione per i moduli di terze parti.-
test: /[\\/]node_modules[\\/]/
: modello di nome file per la corrispondenza di dipendenze di terze parti. -
name(module, chunks, cacheGroupKey)
: raggruppa insieme i blocchi dello stesso modulo assegnando loro un nome comune.
-
-
common
: configura l'estrazione di blocchi comuni dal codice dell'applicazione.-
minChunks: 2
: un blocco sarà considerato comune se referenziato da almeno due moduli. -
priority: -10
: assegna una priorità negativa al gruppo di cachecommon
in modo che i blocchi per il gruppo di cache deivendors
vengano considerati per primi.
-
Estraiamo anche il codice di runtime di Webpack in un unico blocco che può essere condiviso tra più punti di ingresso, specificando runtimeChunk: "single"
.
Server di sviluppo
Finora, ci siamo concentrati sulla creazione e l'ottimizzazione della build di produzione della nostra applicazione, ma Webpack ha anche un proprio server Web con ricarica in tempo reale e segnalazione degli errori, che ci aiuterà nel processo di sviluppo. Si chiama webpack-dev-server
e dobbiamo installarlo separatamente:
npm install -D webpack-dev-server
In questo frammento introduciamo una sezione devServer
nella nostra configurazione del Webpack:
@@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };
Qui abbiamo utilizzato le seguenti opzioni:
-
compress: true
: abilita la compressione delle risorse per ricaricamenti più rapidi. -
historyApiFallback: true
: abilita un fallback aindex.html
per il routing basato sulla cronologia. -
open: true
: apre il browser dopo aver avviato il server di sviluppo. -
overlay: true
: Visualizza gli errori di Webpack nella finestra del browser.
Potrebbe anche essere necessario configurare le impostazioni proxy per inoltrare le richieste API al server back-end.
Webpack and React: ottimizzato per le prestazioni e pronto!
Abbiamo appena appreso come caricare vari tipi di risorse con Webpack, come utilizzare Webpack in un ambiente di sviluppo e diverse tecniche per ottimizzare una build di produzione. Se necessario, puoi sempre rivedere il file di configurazione completo per trovare ispirazione per la tua configurazione di React/Webpack. Affinare tali abilità è una tariffa standard per chiunque offra servizi di sviluppo React.
Nella parte successiva di questa serie, espanderemo questa configurazione con istruzioni per casi d'uso più specifici, inclusi l'utilizzo di TypeScript, preprocessori CSS e tecniche di ottimizzazione avanzate che coinvolgono il rendering lato server e ServiceWorkers. Resta sintonizzato per scoprire tutto ciò che devi sapere su Webpack per portare la tua applicazione React in produzione.