Ein Leitfaden zum Verwalten von Webpack-Abhängigkeiten
Veröffentlicht: 2022-03-11Das Konzept der Modularisierung ist ein fester Bestandteil der meisten modernen Programmiersprachen. JavaScript hat jedoch bis zum Erscheinen der neuesten Version von ECMAScript ES6 keinen formalen Ansatz zur Modularisierung.
In Node.js, einem der heute beliebtesten JavaScript-Frameworks, ermöglichen Modul-Bundler das Laden von NPM-Modulen in Webbrowsern, und komponentenorientierte Bibliotheken (wie React) fördern und erleichtern die Modularisierung von JavaScript-Code.
Webpack ist einer der verfügbaren Modul-Bundler, der JavaScript-Code sowie alle statischen Assets wie Stylesheets, Bilder und Schriftarten in einer gebündelten Datei verarbeitet. Die Verarbeitung kann alle notwendigen Aufgaben zum Verwalten und Optimieren von Codeabhängigkeiten wie Kompilieren, Verketten, Minimieren und Komprimieren umfassen.
Die Konfiguration von Webpack und seinen Abhängigkeiten kann jedoch stressig sein und ist nicht immer ein einfacher Prozess, insbesondere für Anfänger.
Dieser Blogbeitrag enthält Richtlinien mit Beispielen zur Konfiguration von Webpack für verschiedene Szenarien und weist auf die häufigsten Fallstricke im Zusammenhang mit der Bündelung von Projektabhängigkeiten mit Webpack hin.
Der erste Teil dieses Blogbeitrags erläutert, wie Sie die Definition von Abhängigkeiten in einem Projekt vereinfachen können. Als Nächstes diskutieren und demonstrieren wir die Konfiguration für das Code-Splitting von Anwendungen mit mehreren und einzelnen Seiten. Abschließend besprechen wir, wie Webpack konfiguriert wird, wenn wir Bibliotheken von Drittanbietern in unser Projekt einbeziehen möchten.
Konfigurieren von Aliassen und relativen Pfaden
Relative Pfade stehen nicht in direktem Zusammenhang mit Abhängigkeiten, aber wir verwenden sie, wenn wir Abhängigkeiten definieren. Wenn eine Projektdateistruktur komplex ist, kann es schwierig sein, relevante Modulpfade aufzulösen. Einer der grundlegendsten Vorteile der Webpack-Konfiguration besteht darin, dass sie die Definition relativer Pfade in einem Projekt vereinfacht.
Nehmen wir an, wir haben die folgende Projektstruktur:
- Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js
Wir können Abhängigkeiten durch relative Pfade zu den benötigten Dateien referenzieren, und wenn wir Komponenten in Container in unserem Quellcode importieren möchten, sieht das wie folgt aus:
Home.js
Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';
Modal.js
import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';
Jedes Mal, wenn wir ein Skript oder ein Modul importieren möchten, müssen wir den Speicherort des aktuellen Verzeichnisses kennen und den relativen Pfad zu dem finden, was wir importieren möchten. Wir können uns vorstellen, wie dieses Problem an Komplexität eskalieren kann, wenn wir ein großes Projekt mit einer verschachtelten Dateistruktur haben oder einige Teile einer komplexen Projektstruktur umgestalten möchten.
Wir können dieses Problem einfach mit der Option resolve.alias von resolve.alias
. Wir können sogenannte Aliase deklarieren – Namen eines Verzeichnisses oder Moduls mit seinem Speicherort, und wir verlassen uns nicht auf relative Pfade im Quellcode des Projekts.
webpack.config.js
resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }
In der Datei Modal.js
können wir Datepicker jetzt viel einfacher importieren:
import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';
Code-Splitting
Wir können Szenarien haben, in denen wir ein Skript an das endgültige Bundle anhängen oder das endgültige Bundle aufteilen müssen, oder wir möchten bei Bedarf separate Bundles laden. Das Einrichten unserer Projekt- und Webpack-Konfiguration für diese Szenarien ist möglicherweise nicht einfach.
In der Webpack-Konfiguration teilt die Entry
-Option Webpack mit, wo der Ausgangspunkt für das endgültige Bundle ist. Ein Einstiegspunkt kann drei verschiedene Datentypen haben: String, Array oder Objekt.
Wenn wir einen einzigen Ausgangspunkt haben, können wir jedes dieser Formate verwenden und dasselbe Ergebnis erzielen.
Wenn wir mehrere Dateien anhängen möchten und diese nicht voneinander abhängen, können wir ein Array-Format verwenden. Zum Beispiel können wir analytics.js
an das Ende von 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 ' } };
Verwalten mehrerer Einstiegspunkte
Angenommen, wir haben eine mehrseitige Anwendung mit mehreren HTML-Dateien wie index.html
und admin.html
. Wir können mehrere Bundles generieren, indem wir den Einstiegspunkt als Objekttyp verwenden. Die folgende Konfiguration generiert zwei JavaScript-Bundles:
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>
Beide JavaScript-Pakete können gemeinsame Bibliotheken und Komponenten verwenden. Dafür können wir CommonsChunkPlugin
verwenden, das Module findet, die in Blöcken mit mehreren Einträgen vorkommen, und ein gemeinsames Bündel erstellt, das auf mehreren Seiten zwischengespeichert werden kann.
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] };
Jetzt dürfen wir nicht vergessen, <script src="build/common.js"></script>
vor gebündelten Skripten hinzuzufügen.
Lazy Loading aktivieren
Webpack kann statische Assets in kleinere Teile aufteilen, und dieser Ansatz ist flexibler als die Standardverkettung. Wenn wir eine große Einzelseitenanwendung (SPA) haben, ist eine einfache Verkettung zu einem Bundle kein guter Ansatz, da das Laden eines riesigen Bundles langsam sein kann und Benutzer normalerweise nicht alle Abhängigkeiten für jede Ansicht benötigen.
Wir haben bereits erklärt, wie man eine Anwendung in mehrere Bundles aufteilt, gemeinsame Abhängigkeiten verkettet und vom Browser-Caching-Verhalten profitiert. Dieser Ansatz funktioniert sehr gut für mehrseitige Anwendungen, aber nicht für einseitige Anwendungen.
Für die SPA sollten wir nur die statischen Assets bereitstellen, die zum Rendern der aktuellen Ansicht erforderlich sind. Der clientseitige Router in der SPA-Architektur ist ein perfekter Ort, um das Code-Splitting zu handhaben. Wenn der Benutzer eine Route eingibt, können wir nur die erforderlichen Abhängigkeiten für die resultierende Ansicht laden. Alternativ können wir Abhängigkeiten laden, wenn der Benutzer eine Seite nach unten scrollt.
Zu diesem Zweck können wir require.ensure
oder System.import
Funktionen verwenden, die Webpack statisch erkennen kann. Webpack kann basierend auf diesem Splitpunkt ein separates Bundle generieren und bei Bedarf aufrufen.
In diesem Beispiel haben wir zwei React-Container; eine Admin-Ansicht und eine Dashboard-Ansicht.
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>; } }
Wenn der Benutzer entweder die URL /dashboard
oder /admin
eingibt, wird nur das entsprechende erforderliche JavaScript-Bundle geladen. Unten sehen wir Beispiele mit und ohne den clientseitigen Router.
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') );
Extrahieren von Stilen in separate Bundles
In Webpack verarbeiten Ladeprogramme wie style-loader
und css-loader
die Stylesheets vor und betten sie in das ausgegebene JavaScript-Bundle ein, aber in einigen Fällen können sie den Flash von nicht formatiertem Inhalt (FOUC) verursachen.

Wir können den FOUC mit ExtractTextWebpackPlugin
vermeiden, das es ermöglicht, alle Stile in separaten CSS-Bundles zu generieren, anstatt sie in das endgültige JavaScript-Bundle einzubetten.
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') ] }
Umgang mit Bibliotheken und Plugins von Drittanbietern
Oft müssen wir Bibliotheken von Drittanbietern, verschiedene Plugins oder zusätzliche Skripte verwenden, weil wir keine Zeit damit verbringen wollen, dieselben Komponenten von Grund auf neu zu entwickeln. Es gibt viele veraltete Bibliotheken und Plugins, die nicht aktiv gepflegt werden, JavaScript-Module nicht verstehen und das Vorhandensein von Abhängigkeiten global unter vordefinierten Namen annehmen.
Nachfolgend finden Sie einige Beispiele mit jQuery-Plugins, mit einer Erläuterung, wie Webpack richtig konfiguriert wird, um das endgültige Bundle generieren zu können.
Plugin bereitstellen
Die meisten Plugins von Drittanbietern sind auf das Vorhandensein bestimmter globaler Abhängigkeiten angewiesen. Im Fall von jQuery verlassen sich Plugins darauf, dass die $
- oder jQuery
-Variable definiert wird, und wir können jQuery-Plugins verwenden, indem wir $('div.content').pluginFunc()
in unserem Code aufrufen.
Wir können das Webpack-Plugin ProvidePlugin
, um var $ = require("jquery")
jedes Mal voranzustellen, wenn es auf die globale $
-Kennung trifft.
webpack.config.js
webpack.ProvidePlugin({ '$': 'jquery', })
Wenn Webpack den Code verarbeitet, sucht es nach Präsenz $
und stellt einen Verweis auf globale Abhängigkeiten bereit, ohne das von der require
-Funktion angegebene Modul zu importieren.
Import-Loader
Einige jQuery-Plug-ins setzen $
im globalen Namensraum voraus oder verlassen sich darauf, dass this
das window
ist. Zu diesem Zweck können wir imports-loader
verwenden, der globale Variablen in Module einfügt.
example.js
$('div.content').pluginFunc();
Dann können wir die Variable $
in das Modul einfügen, indem wir den imports-loader
konfigurieren:
require("imports?$=jquery!./example.js");
Dies wird einfach vorangestellt var $ = require("jquery");
zu example.js
.
Im zweiten Anwendungsfall:
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }
Durch die Verwendung des =>
-Symbols (nicht zu verwechseln mit den ES6-Pfeilfunktionen) können wir beliebige Variablen setzen. Der letzte Wert definiert die globale Variable this
so um, dass sie auf das window
zeigt. Es ist dasselbe, als würde man den gesamten Inhalt der Datei mit (function () { ... }).call(window);
und Aufruf this
Funktion mit window
als Argument.
Wir können auch Bibliotheken im CommonJS- oder AMD-Modulformat anfordern:
// CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });
Einige Bibliotheken und Module können unterschiedliche Modulformate unterstützen.
Im nächsten Beispiel haben wir ein jQuery-Plugin, das das AMD- und CommonJS-Modulformat verwendet und eine jQuery-Abhängigkeit hat:
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" }] }
Wir können wählen, welches Modulformat wir für die spezifische Bibliothek verwenden möchten. Wenn wir define
als gleich false
deklarieren, parst Webpack das Modul nicht im AMD-Modulformat, und wenn wir variable exports
als gleich false
deklarieren, parst Webpack das Modul nicht im CommonJS-Modulformat.
Expose-Loader
Wenn wir ein Modul im globalen Kontext verfügbar machen müssen, können wir expose-loader
verwenden. Dies kann beispielsweise hilfreich sein, wenn wir externe Skripte haben, die nicht Teil der Webpack-Konfiguration sind und auf das Symbol im globalen Namensraum angewiesen sind, oder wenn wir Browser-Plugins verwenden, die auf ein Symbol in der Konsole des Browsers zugreifen müssen.
webpack.config.js
module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }
Die jQuery-Bibliothek ist jetzt im globalen Namensraum für andere Skripte auf der Webseite verfügbar.
window.$ window.jQuery
Externe Abhängigkeiten konfigurieren
Wenn wir Module aus extern gehosteten Skripten einbinden möchten, müssen wir sie in der Konfiguration definieren. Andernfalls kann Webpack das endgültige Bundle nicht generieren.
Wir können externe Skripte konfigurieren, indem wir die externals
-Option in der Webpack-Konfiguration verwenden. Beispielsweise können wir eine Bibliothek aus einem CDN über ein separates <script>
-Tag verwenden und sie dennoch explizit als Modulabhängigkeit in unserem Projekt deklarieren.
webpack.config.js
externals: { react: 'React', 'react-dom': 'ReactDOM' }
Unterstützung mehrerer Instanzen einer Bibliothek
Es ist großartig, den NPM-Paketmanager in der Frontend-Entwicklung zum Verwalten von Bibliotheken und Abhängigkeiten von Drittanbietern zu verwenden. Manchmal können wir jedoch mehrere Instanzen derselben Bibliothek mit unterschiedlichen Versionen haben, und sie spielen in einer Umgebung nicht gut zusammen.
Dies könnte zum Beispiel mit der React-Bibliothek passieren, wo wir React von NPM installieren können und später eine andere Version von React mit einem zusätzlichen Paket oder Plugin verfügbar werden kann. Unsere Projektstruktur kann wie folgt aussehen:
project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react
Komponenten, die aus dem React react-plugin
stammen, haben eine andere React-Instanz als die übrigen Komponenten im Projekt. Jetzt haben wir zwei separate Kopien von React, und das können verschiedene Versionen sein. In unserer Anwendung kann dieses Szenario unser global änderbares DOM durcheinanderbringen, und wir können Fehlermeldungen im Webkonsolenprotokoll sehen. Die Lösung für dieses Problem besteht darin, im gesamten Projekt dieselbe Version von React zu verwenden. Wir können es durch Webpack-Aliase lösen.
webpack.config.js
module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }
Wenn das React react-plugin
versucht, React anzufordern, verwendet es die Version in den node_modules
des Projekts. Wenn wir herausfinden möchten, welche Version von React wir verwenden, können wir console.log(React.version)
im Quellcode hinzufügen.
Konzentrieren Sie sich auf die Entwicklung, nicht auf die Webpack-Konfiguration
Dieser Beitrag kratzt nur an der Oberfläche der Leistungsfähigkeit und Nützlichkeit von Webpack.
Es gibt viele andere Webpack-Loader und Plugins, die Ihnen helfen, die JavaScript-Bündelung zu optimieren und zu rationalisieren.
Selbst wenn Sie ein Anfänger sind, bietet Ihnen dieser Leitfaden eine solide Grundlage für den Einstieg in die Verwendung von Webpack, sodass Sie sich mehr auf die Entwicklung und weniger auf die Bündelkonfiguration konzentrieren können.