Guide de gestion des dépendances Webpack
Publié: 2022-03-11Le concept de modularisation fait partie intégrante de la plupart des langages de programmation modernes. JavaScript, cependant, n'avait aucune approche formelle de la modularisation jusqu'à l'arrivée de la dernière version d'ECMAScript ES6.
Dans Node.js, l'un des frameworks JavaScript les plus populaires d'aujourd'hui, les bundles de modules permettent de charger des modules NPM dans les navigateurs Web, et les bibliothèques orientées composants (comme React) encouragent et facilitent la modularisation du code JavaScript.
Webpack est l'un des bundlers de modules disponibles qui traite le code JavaScript, ainsi que tous les actifs statiques, tels que les feuilles de style, les images et les polices, dans un fichier groupé. Le traitement peut inclure toutes les tâches nécessaires à la gestion et à l'optimisation des dépendances de code, telles que la compilation, la concaténation, la minification et la compression.
Cependant, la configuration de Webpack et de ses dépendances peut être stressante et n'est pas toujours un processus simple, en particulier pour les débutants.
Ce billet de blog fournit des directives, avec des exemples, sur la façon de configurer Webpack pour différents scénarios, et souligne les pièges les plus courants liés au regroupement de dépendances de projet à l'aide de Webpack.
La première partie de ce billet de blog explique comment simplifier la définition des dépendances dans un projet. Ensuite, nous discutons et démontrons la configuration pour le fractionnement de code d'applications à plusieurs pages et à une seule page. Enfin, nous expliquons comment configurer Webpack, si nous voulons inclure des bibliothèques tierces dans notre projet.
Configuration des alias et des chemins relatifs
Les chemins relatifs ne sont pas directement liés aux dépendances, mais nous les utilisons lorsque nous définissons des dépendances. Si la structure d'un fichier de projet est complexe, il peut être difficile de résoudre les chemins de module pertinents. L'un des avantages les plus fondamentaux de la configuration Webpack est qu'elle permet de simplifier la définition des chemins relatifs dans un projet.
Disons que nous avons la structure de projet suivante :
- Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js
Nous pouvons référencer les dépendances par des chemins relatifs vers les fichiers dont nous avons besoin, et si nous voulons importer des composants dans des conteneurs dans notre code source, cela ressemble à ceci :
Home.js
Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';
Modal.js
import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';
Chaque fois que nous voulons importer un script ou un module, nous devons connaître l'emplacement du répertoire actuel et trouver le chemin relatif vers ce que nous voulons importer. Nous pouvons imaginer comment ce problème peut dégénérer en complexité si nous avons un gros projet avec une structure de fichiers imbriquée, ou si nous voulons refactoriser certaines parties d'une structure de projet complexe.
Nous pouvons facilement gérer ce problème avec l'option resolve.alias
de Webpack. Nous pouvons déclarer des soi-disant alias - nom d'un répertoire ou d'un module avec son emplacement, et nous ne nous appuyons pas sur des chemins relatifs dans le code source du projet.
webpack.config.js
resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }
Dans le fichier Modal.js
, nous pouvons maintenant importer le datepicker beaucoup plus simplement :
import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';
Fractionnement de code
Nous pouvons avoir des scénarios où nous devons ajouter un script dans le bundle final, ou diviser le bundle final, ou nous voulons charger des bundles séparés à la demande. La configuration de notre projet et de la configuration Webpack pour ces scénarios peut ne pas être simple.
Dans la configuration Webpack, l'option Entry
indique à Webpack où se trouve le point de départ du bundle final. Un point d'entrée peut avoir trois types de données différents : chaîne, tableau ou objet.
Si nous avons un seul point de départ, nous pouvons utiliser n'importe lequel de ces formats et obtenir le même résultat.
Si nous voulons ajouter plusieurs fichiers et qu'ils ne dépendent pas les uns des autres, nous pouvons utiliser un format Array. Par exemple, nous pouvons ajouter analytics.js
à la fin du bundle.js
:
webpack.config.js
module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };
Gestion de plusieurs points d'entrée
Supposons que nous ayons une application multipage avec plusieurs fichiers HTML, tels que index.html
et admin.html
. Nous pouvons générer plusieurs bundles en utilisant le point d'entrée comme type d'objet. La configuration ci-dessous génère deux bundles JavaScript :
webpack.config.js
module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };
index.html
<script src=”build/index.js”></script>
admin.html
<script src=”build/admin.js”></script>
Les deux bundles JavaScript peuvent partager des bibliothèques et des composants communs. Pour cela, nous pouvons utiliser CommonsChunkPlugin
, qui trouve les modules qui se produisent dans plusieurs blocs d'entrée et crée un bundle partagé qui peut être mis en cache sur plusieurs pages.
webpack.config.js
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };
Maintenant, nous ne devons pas oublier d'ajouter <script src="build/common.js"></script>
avant les scripts groupés.
Activer le chargement différé
Webpack peut diviser les actifs statiques en plus petits morceaux, et cette approche est plus flexible que la concaténation standard. Si nous avons une grande application monopage (SPA), la simple concaténation en un seul bundle n'est pas une bonne approche car le chargement d'un énorme bundle peut être lent et les utilisateurs n'ont généralement pas besoin de toutes les dépendances sur chaque vue.
Nous avons expliqué précédemment comment diviser une application en plusieurs bundles, concaténer des dépendances communes et bénéficier du comportement de mise en cache du navigateur. Cette approche fonctionne très bien pour les applications multipages, mais pas pour les applications monopage.
Pour le SPA, nous ne devons fournir que les ressources statiques nécessaires pour rendre la vue actuelle. Le routeur côté client dans l'architecture SPA est l'endroit idéal pour gérer le fractionnement du code. Lorsque l'utilisateur entre dans une route, nous ne pouvons charger que les dépendances nécessaires pour la vue résultante. Alternativement, nous pouvons charger des dépendances lorsque l'utilisateur fait défiler une page.
À cette fin, nous pouvons utiliser les fonctions require.ensure
ou System.import
, que Webpack peut détecter de manière statique. Webpack peut générer un bundle séparé basé sur ce point de partage et l'appeler à la demande.
Dans cet exemple, nous avons deux conteneurs React ; une vue administrateur et une vue tableau de bord.
admin.jsx
import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }
dashboard.jsx
import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }
Si l'utilisateur saisit l'URL /dashboard
ou /admin
, seul le bundle JavaScript requis correspondant est chargé. Ci-dessous, nous pouvons voir des exemples avec et sans le routeur côté client.
index.jsx
if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }
index.jsx
ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );
Extraction de styles dans des ensembles séparés
Dans Webpack, les chargeurs, comme style-loader
et css-loader
, pré-traitent les feuilles de style et les intègrent dans le bundle JavaScript de sortie, mais dans certains cas, ils peuvent provoquer le Flash de contenu non stylisé (FOUC).

Nous pouvons éviter le FOUC avec ExtractTextWebpackPlugin
qui permet de générer tous les styles dans des bundles CSS séparés au lieu de les intégrer dans le bundle JavaScript final.
webpack.config.js
var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }
Gestion des bibliothèques et plugins tiers
Souvent, nous devons utiliser des bibliothèques tierces, divers plugins ou des scripts supplémentaires, car nous ne voulons pas passer du temps à développer les mêmes composants à partir de zéro. Il existe de nombreuses bibliothèques et plugins hérités disponibles qui ne sont pas activement maintenus, ne comprennent pas les modules JavaScript et supposent la présence de dépendances globalement sous des noms prédéfinis.
Vous trouverez ci-dessous quelques exemples avec des plugins jQuery, avec une explication sur la façon de configurer correctement Webpack pour pouvoir générer le bundle final.
ProvidePlugin
La plupart des plugins tiers reposent sur la présence de dépendances globales spécifiques. Dans le cas de jQuery, les plugins reposent sur la définition de la variable $
ou jQuery
, et nous pouvons utiliser les plugins jQuery en appelant $('div.content').pluginFunc()
dans notre code.
Nous pouvons utiliser le plugin ProvidePlugin
pour ajouter var $ = require("jquery")
chaque fois qu'il rencontre l'identifiant global $
.
webpack.config.js
webpack.ProvidePlugin({ '$': 'jquery', })
Lorsque Webpack traite le code, il recherche la présence $
et fournit une référence aux dépendances globales sans importer le module spécifié par la fonction require
.
Imports-loader
Certains plugins jQuery supposent $
dans l'espace de noms global ou s'appuient sur this
qui est l'objet window
. Pour cela, nous pouvons utiliser imports-loader
qui injecte des variables globales dans les modules.
example.js
$('div.content').pluginFunc();
Ensuite, nous pouvons injecter la variable $
dans le module en configurant le imports-loader
:
require("imports?$=jquery!./example.js");
Cela ajoute simplement var $ = require("jquery");
à example.js
.
Dans le deuxième cas d'utilisation :
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }
En utilisant le symbole =>
(à ne pas confondre avec les fonctions ES6 Arrow), nous pouvons définir des variables arbitraires. La dernière valeur redéfinit la variable globale this
pour qu'elle pointe vers l'objet window
. C'est la même chose que d'envelopper tout le contenu du fichier avec le (function () { ... }).call(window);
et appeler this
fonction avec window
comme argument.
Nous pouvons également exiger des bibliothèques utilisant le format de module CommonJS ou AMD :
// CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });
Certaines bibliothèques et certains modules peuvent prendre en charge différents formats de module.
Dans l'exemple suivant, nous avons un plugin jQuery qui utilise le format de module AMD et CommonJS et a une dépendance jQuery :
jquery-plugin.js
(function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }
Nous pouvons choisir le format de module que nous voulons utiliser pour la bibliothèque spécifique. Si nous déclarons define
comme égal à false
, Webpack n'analyse pas le module au format de module AMD, et si nous déclarons les exports
de variables égales à false
, Webpack n'analyse pas le module au format de module CommonJS.
Exposer-chargeur
Si nous devons exposer un module au contexte global, nous pouvons utiliser expose-loader
. Cela peut être utile, par exemple, si nous avons des scripts externes qui ne font pas partie de la configuration de Webpack et qui reposent sur le symbole dans l'espace de noms global, ou si nous utilisons des plug-ins de navigateur qui doivent accéder à un symbole dans la console du navigateur.
webpack.config.js
module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }
La bibliothèque jQuery est désormais disponible dans l'espace de noms global pour les autres scripts de la page Web.
window.$ window.jQuery
Configuration des dépendances externes
Si nous voulons inclure des modules à partir de scripts hébergés en externe, nous devons les définir dans la configuration. Sinon, Webpack ne peut pas générer le bundle final.
Nous pouvons configurer des scripts externes en utilisant l'option externals
dans la configuration Webpack. Par exemple, nous pouvons utiliser une bibliothèque d'un CDN via une <script>
distincte, tout en la déclarant explicitement en tant que dépendance de module dans notre projet.
webpack.config.js
externals: { react: 'React', 'react-dom': 'ReactDOM' }
Prise en charge de plusieurs instances d'une bibliothèque
Il est bon d'utiliser le gestionnaire de packages NPM dans le développement frontal pour gérer les bibliothèques et les dépendances tierces. Cependant, nous pouvons parfois avoir plusieurs instances de la même bibliothèque avec différentes versions, et elles ne fonctionnent pas bien ensemble dans un environnement.
Cela pourrait arriver, par exemple, avec la bibliothèque React, où nous pouvons installer React à partir de NPM et plus tard, une version différente de React peut devenir disponible avec un package ou un plugin supplémentaire. Notre structure de projet peut ressembler à ceci :
project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react
Les composants provenant du react-plugin
ont une instance de React différente du reste des composants du projet. Nous avons maintenant deux copies distinctes de React, et elles peuvent être des versions différentes. Dans notre application, ce scénario peut perturber notre DOM mutable global et nous pouvons voir des messages d'erreur dans le journal de la console Web. La solution à ce problème est d'avoir la même version de React tout au long du projet. Nous pouvons le résoudre par des alias Webpack.
webpack.config.js
module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }
Lorsque react-plugin
tente d'exiger React, il utilise la version dans node_modules
du projet. Si nous voulons savoir quelle version de React nous utilisons, nous pouvons ajouter console.log(React.version)
dans le code source.
Concentrez-vous sur le développement, pas sur la configuration de Webpack
Cet article ne fait qu'effleurer la surface de la puissance et de l'utilité de Webpack.
Il existe de nombreux autres chargeurs et plug-ins Webpack qui vous aideront à optimiser et à rationaliser le regroupement JavaScript.
Même si vous êtes débutant, ce guide vous donne une base solide pour commencer à utiliser Webpack, ce qui vous permettra de vous concentrer davantage sur le développement que sur la configuration groupée.