Gardez le contrôle : un guide pour Webpack et React, Pt. 1

Publié: 2022-03-11

Lorsque vous démarrez un nouveau projet React, vous avez le choix entre de nombreux modèles : Create React App, react-boilerplate et React Starter Kit, pour n'en nommer que quelques-uns.

Ces modèles, adoptés par des milliers de développeurs, sont capables de prendre en charge le développement d'applications à très grande échelle. Mais ils laissent l'expérience du développeur et la sortie du bundle aux prises avec divers défauts, ce qui n'est peut-être pas idéal.

Si vous souhaitez conserver un plus grand contrôle sur votre processus de génération, vous pouvez choisir d'investir dans une configuration Webpack personnalisée. Comme vous l'apprendrez dans ce didacticiel Webpack, cette tâche n'est pas très compliquée et les connaissances peuvent même être utiles lors du dépannage des configurations d'autres personnes.

Webpack : mise en route

La façon dont nous écrivons JavaScript aujourd'hui est différente du code que le navigateur peut exécuter. Nous nous appuyons fréquemment sur d'autres types de ressources, de langages transpilés et de fonctionnalités expérimentales qui ne sont pas encore prises en charge par les navigateurs modernes. Webpack est un bundler de modules pour JavaScript qui peut combler cette lacune et produire du code compatible avec tous les navigateurs sans frais en ce qui concerne l'expérience des développeurs.

Avant de commencer, vous devez garder à l'esprit que tout le code présenté dans ce tutoriel Webpack est également disponible sous la forme d'un exemple de fichier de configuration Webpack/React complet sur GitHub. N'hésitez pas à vous y référer et à revenir sur cet article si vous avez des questions.

Configuration de base

Depuis Legato (version 4), Webpack ne nécessite aucune configuration pour fonctionner. Le choix d'un mode de construction appliquera un ensemble de valeurs par défaut plus adaptées à l'environnement cible. Dans l'esprit de cet article, nous allons écarter ces valeurs par défaut et implémenter nous-mêmes une configuration raisonnable pour chaque environnement cible.

Tout d'abord, nous devons installer webpack et webpack-cli :

 npm install -D webpack webpack-cli

Ensuite, nous devons remplir webpack.config.js avec une configuration comportant les options suivantes :

  • devtool : Active la génération de source-map en mode développement.
  • entry : Le fichier principal de notre application React.
  • output.path : Le répertoire racine dans lequel stocker les fichiers de sortie.
  • output.filename : Le modèle de nom de fichier à utiliser pour les fichiers générés.
  • output.publicPath : Le chemin d'accès au répertoire racine où les fichiers seront déployés sur le serveur 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 configuration ci-dessus fonctionne bien pour les fichiers JavaScript simples. Mais lors de l'utilisation de Webpack et de React, nous devrons effectuer des transformations supplémentaires avant d'expédier le code à nos utilisateurs. Dans la section suivante, nous utiliserons Babel pour modifier la façon dont Webpack charge les fichiers JavaScript.

Chargeur JS

Babel est un compilateur JavaScript avec de nombreux plugins pour la transformation de code. Dans cette section, nous allons l'introduire en tant que chargeur dans notre configuration Webpack et le configurer pour transformer le code JavaScript moderne en un code compréhensible par les navigateurs courants.

Tout d'abord, nous devrons installer babel-loader et @babel/core :

 npm install -D @babel/core babel-loader

Ensuite, nous ajouterons une section de module à notre configuration Webpack, rendant babel-loader responsable du chargement des fichiers 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"] } }; };

Nous allons configurer Babel en utilisant un fichier de configuration séparé, babel.config.js . Il utilisera les fonctionnalités suivantes :

  • @babel/preset-env : Transforme les fonctionnalités JavaScript modernes en code rétrocompatible.
  • @babel/preset-react : Transforme la syntaxe JSX en appels de fonction JavaScript plain-vanilla.
  • @babel/plugin-transform-runtime : Réduit la duplication de code en extrayant les helpers Babel dans des modules partagés.
  • @babel/plugin-syntax-dynamic-import : Active la syntaxe dynamique import() dans les navigateurs ne prenant pas en charge nativement Promise .
  • @babel/plugin-proposal-class-properties : Active la prise en charge de la proposition de syntaxe de champ d'instance publique, pour l'écriture de composants React basés sur des classes.

Nous activerons également quelques optimisations de production spécifiques à React :

  • babel-plugin-transform-react-remove-prop-types supprime les prop-types inutiles du code de production.
  • @babel/plugin-transform-react-inline-elements évalue React.createElement lors de la compilation et inline le résultat.
  • @babel/plugin-transform-react-constant-elements extrait les éléments React statiques en tant que constantes.

La commande ci-dessous installera toutes les dépendances nécessaires :

 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

Ensuite, nous babel.config.js avec ces paramètres :

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

Cette configuration nous permet d'écrire du JavaScript moderne d'une manière compatible avec tous les navigateurs concernés. Il existe d'autres types de ressources dont nous pourrions avoir besoin dans une application React, que nous aborderons dans les sections suivantes.

Chargeur CSS

En ce qui concerne le style des applications React, nous devons au minimum pouvoir inclure des fichiers CSS simples. Nous allons le faire dans Webpack en utilisant les chargeurs suivants :

  • css-loader : analyse les fichiers CSS, résolvant les ressources externes, telles que les images, les polices et les importations de styles supplémentaires.
  • style-loader : lors du développement, injecte les styles chargés dans le document lors de l'exécution.
  • mini-css-extract-plugin : extrait les styles chargés dans des fichiers séparés pour une utilisation en production afin de tirer parti de la mise en cache du navigateur.

Installons les chargeurs CSS ci-dessus :

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

Ensuite, nous ajouterons une nouvelle règle à la section module.rules de notre configuration 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" + ] } ] },

Nous ajouterons également MiniCssExtractPlugin à la section plugins , que nous n'activerons qu'en mode production :

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

Cette configuration fonctionne pour les fichiers CSS simples et peut être étendue pour fonctionner avec divers processeurs CSS, tels que Sass et PostCSS, dont nous parlerons dans le prochain article.

Chargeur d'images

Webpack peut également être utilisé pour charger des ressources statiques telles que des images, des vidéos et d'autres fichiers binaires. La manière la plus générique de gérer ces types de fichiers consiste à utiliser file-loader ou url-loader , qui fournira une référence URL pour les ressources requises à ses consommateurs.

Dans cette section, nous ajouterons url-loader pour gérer les formats d'image courants. Ce qui distingue url-loader de file-loader , c'est que si la taille du fichier d'origine est inférieure à un seuil donné, il intégrera le fichier entier dans l'URL en tant que contenu encodé en base64, supprimant ainsi le besoin d'une requête supplémentaire.

Nous installons d'abord url-loader :

 npm install -D url-loader

Ensuite, nous ajoutons une nouvelle règle à la section module.rules de notre configuration 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

Pour les images SVG, nous allons utiliser le @svgr/webpack , qui transforme les fichiers importés en composants React.

On installe @svgr/webpack :

 npm install -D @svgr/webpack

Ensuite, nous ajoutons une nouvelle règle à la section module.rules de notre configuration Webpack :

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

Les images SVG en tant que composants React peuvent être pratiques, et @svgr/webpack effectue l'optimisation à l'aide de SVGO.

Remarque : Pour certaines animations ou même des effets de survol de la souris, vous devrez peut-être manipuler le SVG à l'aide de JavaScript. Heureusement, @svgr/webpack intègre le contenu SVG dans le bundle JavaScript par défaut, vous permettant de contourner les restrictions de sécurité nécessaires pour cela.

Chargeur de fichiers

Lorsque nous devons référencer d'autres types de fichiers, le file-loader de fichiers générique fera le travail. Il fonctionne de la même manière que url-loader , en fournissant une URL d'actif au code qui l'exige, mais il ne tente pas de l'optimiser.

Comme toujours, nous installons d'abord le module Node.js. Dans ce cas, file-loader :

 npm install -D file-loader

Ensuite, nous ajoutons une nouvelle règle à la section module.rules de notre configuration Webpack. Par exemple:

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

Ici, nous avons ajouté file-loader pour le chargement des polices, que vous pouvez référencer à partir de vos fichiers CSS. Vous pouvez étendre cet exemple pour charger tout autre type de fichiers dont vous avez besoin.

Plug-in d'environnement

Nous pouvons utiliser DefinePlugin DefinePlugin() de Webpack pour exposer les variables d'environnement de l'environnement de construction à notre code d'application. Par exemple:

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

Ici, nous avons remplacé process.env.NODE_ENV par une chaîne représentant le mode de construction : "development" ou "production" .

Plug-in HTML

En l'absence d'un fichier index.html , notre bundle JavaScript est inutile, juste assis sans que personne ne puisse le trouver. Dans cette section, nous présenterons html-webpack-plugin pour générer un fichier HTML pour nous.

Nous installons html-webpack-plugin :

 npm install -D html-webpack-plugin

Ensuite, nous ajoutons html-webpack-plugin à la section plugins de notre configuration 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) };

Le fichier public/index.html généré chargera notre bundle et démarrera notre application.

Optimisation

Il existe plusieurs techniques d'optimisation que nous pouvons utiliser dans notre processus de construction. Nous commencerons par la minification du code, un processus par lequel nous pouvons réduire la taille de notre bundle sans frais en termes de fonctionnalités. Nous utiliserons deux plugins pour minimiser notre code : terser-webpack-plugin pour le code JavaScript etoptimize optimize-css-assets-webpack-plugin pour CSS.

Installons-les :

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

Ensuite, nous ajouterons une section d' optimization à notre configuration :

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

Les paramètres ci-dessus assureront la compatibilité du code avec tous les navigateurs modernes.

Fractionnement de code

Le fractionnement de code est une autre technique que nous pouvons utiliser pour améliorer les performances de notre application. Le fractionnement de code peut faire référence à deux approches différentes :

  1. À l'aide d'une instruction import() dynamique, nous pouvons extraire des parties de l'application qui constituent une part importante de la taille de notre bundle et les charger à la demande.
  2. Nous pouvons extraire du code qui change moins fréquemment, afin de tirer parti de la mise en cache du navigateur et d'améliorer les performances pour les visiteurs réguliers.

Nous remplirons la section optimization.splitChunks de notre configuration Webpack avec les paramètres d'extraction des dépendances tierces et des morceaux communs dans des fichiers séparés :

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

Examinons plus en détail les options que nous avons utilisées ici :

  • chunks: "all" : Par défaut, l'extraction de chunk commune n'affecte que les modules chargés avec un import() dynamique. Ce paramètre permet également d'optimiser le chargement du point d'entrée.
  • minSize: 0 : Par défaut, seuls les morceaux au-dessus d'un certain seuil de taille deviennent éligibles pour l'extraction. Ce paramètre permet l'optimisation pour tout le code commun, quelle que soit sa taille.
  • maxInitialRequests: 20 et maxAsyncChunks: 20 : ces paramètres augmentent le nombre maximal de fichiers source pouvant être chargés en parallèle pour les importations de points d'entrée et les importations de points de partage, respectivement.

De plus, nous spécifions la configuration cacheGroups suivante :

  • vendors : configure l'extraction pour les modules tiers.
    • test: /[\\/]node_modules[\\/]/ : modèle de nom de fichier pour les dépendances tierces correspondantes.
    • name(module, chunks, cacheGroupKey) : les groupes séparent les morceaux du même module en leur donnant un nom commun.
  • common : configure l'extraction des fragments communs à partir du code de l'application.
    • minChunks: 2 : Un morceau sera considéré comme commun s'il est référencé à partir d'au moins deux modules.
    • priority: -10 : attribue une priorité négative au groupe de cache common afin que les blocs du groupe de cache des vendors soient pris en compte en premier.

Nous extrayons également le code d'exécution Webpack dans un seul morceau qui peut être partagé entre plusieurs points d'entrée, en spécifiant runtimeChunk: "single" .

Serveur de développement

Jusqu'à présent, nous nous sommes concentrés sur la création et l'optimisation de la version de production de notre application, mais Webpack dispose également de son propre serveur Web avec rechargement en direct et rapport d'erreurs, ce qui nous aidera dans le processus de développement. Il s'appelle webpack-dev-server , et nous devons l'installer séparément :

 npm install -D webpack-dev-server

Dans cet extrait, nous introduisons une section devServer dans notre configuration Webpack :

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

Ici, nous avons utilisé les options suivantes :

  • compress: true : Active la compression des ressources pour des rechargements plus rapides.
  • historyApiFallback: true : active un retour vers index.html pour le routage basé sur l'historique.
  • open: true : Ouvre le navigateur après le lancement du serveur de développement.
  • overlay: true : affiche les erreurs Webpack dans la fenêtre du navigateur.

Vous devrez peut-être également configurer les paramètres de proxy pour transférer les demandes d'API au serveur principal.

Webpack et React : performances optimisées et prêts !

Nous venons d'apprendre comment charger différents types de ressources avec Webpack, comment utiliser Webpack dans un environnement de développement et plusieurs techniques pour optimiser une version de production. Si vous en avez besoin, vous pouvez toujours consulter le fichier de configuration complet pour vous inspirer de votre propre configuration React/Webpack. Aiguiser ces compétences est un tarif standard pour quiconque propose des services de développement React.

Dans la prochaine partie de cette série, nous développerons cette configuration avec des instructions pour des cas d'utilisation plus spécifiques, y compris l'utilisation de TypeScript, les préprocesseurs CSS et les techniques d'optimisation avancées impliquant le rendu côté serveur et les ServiceWorkers. Restez à l'écoute pour apprendre tout ce que vous devez savoir sur Webpack pour mettre votre application React en production.