Webpack
Eine Snippet-Sammlung für Webpack.
Quellen:
- Tutorial von Webpack: Getting started
- Webpack-Doku: Concepts
- Webpack-Doku: Konfigurations-Übersicht
- Alte Webpack-Doku: Liste der Loader, Liste der Plugins
- Migration von Webpack 1 nach 2
- Sehr, sehr gute Einführung: Webpack your bags (veraltet, da Webpack 1)
- Einführung: A Detailed Introduction To Webpack
- TODO Tree-shaking example with Babel and Webpack
- TODO http://webpack.github.io/docs/configuration.html#devtool (Verschiedene Wege für Sourcemaps?)
- TODO http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/ (Sourcemaps)
- TODO Vergleich von Webpack, Browserify und Gulp
- TODO html-webpack-plugin
TODO Einheitliche Terminologie: Main-Bundle (statt Haupt-Datei, bundle.js
)
Was ist Webpack?
Webpack ist ein Module-Bundler und ein Build-System. Das bedeutet, dass es nicht nur beides tut, es kombiniert beides. Webpack bundelt nicht nur die JavaScript-Abhängigkeiten und baut dann die Assets dazu, es sieht die Assets ebenfalls als Abhängigkeiten.
Alle Abhängigkeiten - in Webpack "Module" genannt - können importiert, manipuliert und am Ende ins finale Bundle gepackt werden. Egal ob es nun JavaScript, CSS, HTML-Snippets oder Bilder sind.
Webpack in Projekt einbinden
npm init -y
npm install --save-dev webpack
Script in package.json
definieren:
{
"scripts": {
"clean": "rm -rf assets",
"build": "node_modules/.bin/webpack --progress --colors",
"watch": "node_modules/.bin/webpack --progress --colors --watch",
"production": "npm run clean && NODE_ENV=production npm run build --"
}
}
Hinweise:
- Ruft man
npm run {script}
mit dem Parameter--
auf, dann leitet npm die folgenden Parameter an das Skript weiter. So kann man Zusatz-Parameter an Webpack weitergeben.
Beispiel:npm run build -- --display-chunks --display-modules
- Per Default werden Third-Party-Module in der Konsolenausgabe von Webpack nicht gezeigt. Statt dessen steht da z.B.
+ 5 hidden modules
. Mit dem Parameter--display-modules
kann man sich alle Module anzeigen lassen. - Ein Clean kann auch mit dem CleanPlugin gemacht werden. Aber als npm-Script ist es einfacher.
Webpack-Konfiguration als webpack.config.js
anlegen:
var path = require('path');
module.exports = {
// Entry point(s) to the application (the root of the dependency tree)
entry: {
app: './src/app/index.js',
someOtherModule: [ './src/bla.js', './src/blubb.js' ]
},
output: {
path: path.resolve(__dirname, 'assets'), // The path where to build
publicPath: 'assets/', // The path where to build seen from the browser
filename: '[name].js' // The script to build (e.g. 'app.js')
},
resolve: {
modules: [
// directories where to look for modules
path.resolve('./src'),
'node_modules'
]
}
};
TODO Wofür genau nochmal der publicPath
? Allgemein für Dev-Server oder für HMR oder sonstwas?
Gebautes Skript in index.html
einbinden:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My project</title>
</head>
<body>
<script src="assets/app.js"></script>
</body>
</html>
Projekt einmal bauen:
npm run build
Projekt bei jeder Änderung automatisch bauen (Watch-Mode):
npm run watch
Im Watch-Mode behält Webpack bereits gebaute Module im Arbeitsspeicher und baut nur die geänderten Module neu.
Abhängigkeiten definieren
Durch Webpack kann man im JavaScript-Code Abhängigkeiten anfordern. Dabei werden zwei Stile unterstützt: require
(ES5 / CommonJS) und import
(ES6).
// ES5
require('WantedModule.js')
var Button = require('./Components/Button').default; // Man beachte das `.default` am Ende
// ES6
import Button from './Components/Button';
Hinweis: Bei require
muss man den Default-Export manuell mit .default
auswählen. Das liegt daran, dass require
nicht zwischen dem Default-Export und anderen Exports unterscheidet, so dass man selbst den gewünschten Export wählen muss.
Bei import
dagegen kann man über die Syntax festlegen, ob man den Default-Export (z.B. import foo from 'bar'
) oder einen anderen Export meint (z.B. import {baz} from 'bar'
).
Production Build
Es gibt verschiedene Schalter und Plugins, die man in der Entwicklung nicht braucht, jedoch für den produktiven Build sinnvoll sind.
Plugins installieren:
npm i --save-dev circular-dependency-plugin
In webpack.config.js
Production Build einrichten:
const path = require('path')
const webpack = require('webpack')
const CircularDependencyPlugin = require('circular-dependency-plugin')
const production = process.env.NODE_ENV === 'production'
console.log('Building in ' + (production ? 'production' : 'dev') + ' mode')
let plugins = [
// Plugins for both dev and production go here
// Detect circular dependencies
new CircularDependencyPlugin({
//exclude: /a\.js|node_modules/, // This is a Regex
failOnError: true
})
]
if (production) {
plugins = plugins.concat([
// Production plugins go here
// This plugin minifies all the Javascript code of the final bundle
new webpack.optimize.UglifyJsPlugin({
mangle: { screw_ie8 : true },
compress: { screw_ie8: true, warnings: false },
comments: false
}),
// This plugins defines various variables that we can set to false
// in production to avoid code related to them from being compiled
// in our final bundle
new webpack.DefinePlugin({
__SERVER__: !production,
__DEVELOPMENT__: !production,
__DEVTOOLS__: !production,
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
BABEL_ENV: JSON.stringify(process.env.NODE_ENV)
}
})
])
}
module.exports = {
...
// Use sourcemaps only in dev build
// If using less, this must be 'source-map' or 'inline-source-map'
// (see: https://webpack.js.org/loaders/less-loader/#sourcemaps)
devtool: production ? false : 'source-map',
plugins: plugins,
module: {
...
}
}
Hinweis: Im Attribut devtool
kann man neben source-map
auch andere Optionen für die Sourcemap-Generierung angeben.
Beispiele von Plugins für den Production-Build:
TODO Beispiel ist Webpack 1
if (production) {
plugins = plugins.concat([
// This plugin prevents Webpack from creating chunks
// that would be too small to be worth loading separately
new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 51200 // ~50kb
})
]);
}
Hinweis: HTML und CSS werden automatisch von html-loader
und css-loader
minimiert, sobald die Option debug
auf false
gesetzt ist (das ist Default). Hierzu ist also kein Plugin wie das UglifyJsPlugin
nötig.
ES6-Code einbinden
Hinweis: Wenn auch React verwendet werden soll, dann Abschnitt "React einbinden" folgen.
Man kann auch sein JavaScript in ES6 (aka ES2015) schreiben und automatisch nach ES5 transpilieren lassen.
Babel-Loader, Babel selbst und das ES2015-Preset lokal installieren:
npm install --save-dev babel-loader babel-core babel-preset-es2015
Regel in webpack.config.js
definieren:
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'), // Only pass our code to babel (no libs)
loader: 'babel-loader',
options: {
presets: [ ['es2015', { modules: false }] ]
}
}
]
}
}
Hinweise:
- Die
include
-Regel sorgt dafür, dass nur der eigene Code durch Babel gejagt wird, also keine Third-Party-Abhängigkeiten (die ja innode_modules
und nicht insrc
liegen). - Die Option
modules: false
wird benötigt, damit Babel Imports und Exports nicht übersetzt. So kann Webpack diese auflösen und für die Entfernung von Dead-Code verwenden (Tree-Shaking).
React einbinden
React lokal installieren:
npm install --save react react-dom prop-types
Babel-Loader, Babel selbst und die Presets "ES2015" und React lokal installieren:
npm install --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-stage-0 babel-preset-react babel-plugin-autobind-class-methods
Regel in webpack.config.js
definieren:
module.exports = {
// ...
module: {
rules: [
{
test: /\.jsx?$/,
include: path.resolve(__dirname, 'src'),
exclude: path.resolve(__dirname, 'src/lib'),
loader: 'babel-loader',
options: {
presets: [ ['es2015', { modules: false }], 'stage-0', 'react'],
plugins: [ 'autobind-class-methods' ].concat(production ? [] : [ 'react-hot-loader/babel' ])
}
}
]
}
}
Hinweis: Zu include
-Regel und Option modules: false
siehe Abschnitt "ES6-Code einbinden".
Jetzt kann React mit ES6 verwendet werden:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class MyView extends Component {
render() {
return <div>Hallo</div>
}
}
ReactDOM.render(<MyView />, document.body);
Hinweis: Man kann auch die ES6-Schreibweise zur Definition von React-Klassen verwenden (also class MyView extends Component { ... }
). Dann hat man jedoch kein automatisches Function-Binding mehr (d.h. Listener werden mit falschem this
aufgerufen). Ein automatisches Function-Binding kann man jedoch auch per Babel-Preset stage-0 bekommen.
CSS einbinden
Hinweis: Wenn auch Less oder Scss verwendet werden soll, dann Abschnitt "Less einbinden" bzw. "Scss einbinden" folgen.
Benötigte Plugins und Loader lokal installieren:
npm install --save-dev extract-text-webpack-plugin css-loader style-loader
webpack.config.js
erweitern:
TODO Beispiel ist Webpack 1
var ExtractPlugin = require('extract-text-webpack-plugin');
module.exports = {
// ...
plugins: [
new ExtractPlugin('[name].css') // Target CSS file
],
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractPlugin.extract('style', 'css?sourceMap')
}
]
}
};
CSS in HTML anfordern:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="assets/app.css">
</head>
<body>
// Static markup goes here
<script src="assets/app.js"></script>
</body>
</html>
Im JavaScript-Code benötigtes CSS anfordern:
// ES5
require('./MyComponent.css');
// ES6
import './MyComponent.css';
Hinweise:
ExtractPlugin.extract
hat zwei Parameter:- Der erste ist die Loader-Pipeline für Chunks. Hier soll das CSS Teil des Chunks sein, also nicht in eine CSS-Datei extrahiert werden. Der
style-loader
bindet das CSS als dynamisch erzeugtescript
-Tags in die Seite ein. - Der zweite Parameter ist die Loader-Pipeline für die Hauptdatei. Der
css-loader
lädt das CSS und optimiert es ein wenig. Danach wird es nachapp.css
extrahiert.
- Der erste ist die Loader-Pipeline für Chunks. Hier soll das CSS Teil des Chunks sein, also nicht in eine CSS-Datei extrahiert werden. Der
- Eine extra CSS-Datei hat den Vorteil, dass man im HTML die CSS-Datei im
head
anfordern kann, während man das JavaScript am Ende desbody
anfordert. Damit bekommt man eine schnell ladende Seite, die beim Laden nicht häßlich blinkt, weil irgendwelche Styles fehlen. - Du kannst auch das CSS der Chunks extrahieren, so dass alles CSS in der
app.css
landet. Dazu verwendest DuExtractTextPlugin('style.css', {allChunks: true})
. - Du kannst auch ohne das
ExtractPlugin
arbeiten - dann erzeugt Webpack keine extra CSS-Datei, so dass im Haupt-Bundle (z.B.app.js
) auch das CSS integriert wird. Dazu lässt Du dasExtractPlugin
einfach weg und setztloader: 'style!css'
.
TODO devtool
TODO autoprefixer
TODO Beispiel ist Webpack 1
{
// When you encounter SCSS files, parse them with node-sass, then pass autoprefixer on them
// then return the results as a string of CSS
test: /\.scss/,
loaders: ['css', 'autoprefixer', 'sass']
}
TODO autoprefixer-loader ist deprecated. Statt dessen postcss-loader verwenden.
TODO Link: autoprefixer
TODO index.html
erweitern
less einbinden
Hinweis: Einbindung von SCSS funktioniert analog. Allerdings wird sass-loader node-sass
statt less-loader less
installiert, der Test wird auf /\.scss$/
gesetzt und dann der sass-loader
statt dem less-loader
verwendet.
Benötigte Plugins und Loader lokal installieren:
npm install --save-dev extract-text-webpack-plugin@beta css-loader style-loader less-loader less
Hinweis: Aktuell (Stand 13.02.17) ist die Stable-Version vom extract-text-webpack-plugin
noch eine 1.x-Version und nicht kompatibel zu Webpack 2. Daher muss mit @beta
installiert werden, damit eine 2.x-Version gezogen wird.
webpack.config.js
erweitern:
var ExtractPlugin = require('extract-text-webpack-plugin')
var plugins = [
// Plugins for both dev and production go here
// Extract CSS into separate file
new ExtractPlugin({
filename: '[name].css' // Target CSS file
})
]
module.exports = {
...
// Use sourcemaps only in dev build
// Must be 'source-map' or 'inline-source-map'
// (see: https://webpack.js.org/loaders/less-loader/#sourcemaps)
devtool: production ? false : 'source-map',
plugins: plugins,
module: {
rules: [
{
test: /\.(less|css)$/,
// We don't extract our CSS in dev mode in order to make hot-reload work
use: production ?
ExtractPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'less-loader']
}) :
[
{ loader: 'style-loader', options: { sourceMap: true } },
{ loader: 'css-loader', options: { sourceMap: true, importLoaders: 1 } },
{ loader: 'less-loader', options: { sourceMap: true } }
]
}
]
}
};
Hinweis: Laut Doku vom less-loader muss devtool
entweder 'source-map'
oder 'inline-source-map'
sein, damit Sourcemaps funktionieren.
index.html
erweitern:
...
<head>
...
<link rel="stylesheet" href="assets/app.css">
</head>
...
Bootstrap einbinden
Wenn man less eingebunden hat, kann man recht einfach Bootstrap verwenden - am besten mit einem Custom-Build. So werden nur die Teile eingebunden, die man auch verwendet und man kann die Variablen umdefinieren.
Bootstrap lokal installieren:
npm install --save bootstrap
Bootstrap-Haupt-Datei kopieren:
mkdir src/app/style
cp node_modules/bootstrap/less/bootstrap.less src/app/style/custom-bootstrap.less
custom-bootstrap.less
anpassen, nicht benötigte Teile auskommentieren:
// Copied and adjusted from: node_modules/bootstrap/less/bootstrap.less
@bootstrap-src: '../../../node_modules/bootstrap/less';
/*!
* Bootstrap v...
*/
// Core variables and mixins
@import "@{bootstrap-src}/variables.less";
...
//@import "@{bootstrap-src}/print.less";
...
custom-bootstrap.less
in index.js
importieren:
import 'app/style/custom-bootstrap.less'
Bilder einbinden
Loader lokal installieren:
npm install --save-dev url-loader file-loader
Loader-Pipeline in webpack.config.js
definieren:
TODO Beispiel ist Webpack 1
module.exports = {
// ...
module: {
loaders: [
{
test: /\.(png|gif|jpe?g|ico|svg)$/i,
// If the asset is smaller than 10kb inline it,
// else, fallback to the file-loader and reference it
loader: 'url?limit=10000'
}
]
}
};
Hinweise:
- In Webpack gibt es zwei Loader für Binärdaten: Den
file-loader
und denurl-loader
. Derfile-loader
gibt einfach nur eine URL zur Datei zurück (sinnvoll für große Dateien). Derurl-loader
bindet die Datei alsdata:image/jpeg;base64
-URL ein (sinnvoll für kleine Dateien). -
Statt dem Query-String
?limit=10000
kann man die Query auch als Objekt angeben:TODO Beispiel ist Webpack 1
{ test: /\.(png|gif|jpe?g|svg)$/i, loader: 'url', query: { limit: 10000 } }
- Fügt man noch den image-webpack-loader zur Pipeline hinzu (
loaders: ['url', 'image-webpack']
), dann werden alle Bilder zusätzlich noch mitimagemin
optimiert. Zusammen mit dem Query-Parameter?bypassOnDebug
kann man erreichen, dass dies nur bei Production-Builds ausgeführt wird.
TODO Woher weiß Webpack, dass file-loader
der Fallback ist?
Statischen Kram kopieren
Plugin lokal installieren:
npm install --save-dev copy-webpack-plugin
webpack.config.js
erweitern:
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
// ...
plugins: [
new CopyPlugin([
// Copy everything from the directory `static` to the output
{ from: 'static' }
])
]
};
Hinweis: Weitere Details siehe Doku vom copy-webpack-plugin.
HTML-Snippets einbinden
Loader lokal installieren:
npm install --save-dev html-loader
Loader-Pipeline in webpack.config.js
definieren:
TODO Beispiel ist Webpack 1
module.exports = {
// ...
module: {
loaders: [
{
test: /\.html$/,
loader: 'html'
}
]
}
};
Im JavaScript-Code benötigtes Snippet anfordern:
// ES5
var template = require('./MyComponent.html').default;
// ES6
import template from './MyComponent.html';
Automatische Imports
Wenn man z.B. Komponenten baut und neben jeder Komponente MyComponent.js
auch ein MyComponent.css
und ein MyComponent.html
liegen hat, dann kann WebPack diese Module automatisch importieren.
TODO Scheint aktuell (Stand 22.02.17) noch nicht mit Webpack 2 zu gehen. Siehe auch: https://github.com/deepsweet/baggage-loader/issues/9
Loader lokal installieren:
npm install --save-dev baggage-loader
webpack.config.js
erweitern:
TODO Beispiel ist Webpack 1
module.exports = {
// ...
module: {
loaders: [
{
test: /\.js$/,
loader: 'baggage?[file].html=template&[file].css'
}
]
}
};
Das sagt Webpack: Wenn Du eine HTML-Datei mit dem gleichen Namen findest, dann importiere sie als template
. Und wenn Du eine CSS-Datei mit dem gleichen Namen findest, dann importiere diese auch.
Damit kann man den Import von:
import $ from 'jquery';
import Mustache from 'mustache';
import template from './MyComponent.html';
import './MyComponent.css';
Zu diesem Import ändern:
import $ from 'jquery';
import Mustache from 'mustache';
Wevpack Dev-Server
Der webpack-dev-server ist ein kleiner express-Server, der alle statischen Assets und das Bundle ausliefert. Der Dev-Server verwendet Webpacks Watch-Mode um bei Änderungen neu zu bauen. Das Ergebnis wird jedoch nicht auf die Platte geschrieben, sondern existiert nur im Arbeitsspeicher. Die geöffnete Seite wird dann automatisch per Sock.js neu geladen.
Development-Server lokal im Projekt installieren:
npm install --save-dev webpack-dev-server
Script in package.json
definieren:
{
"scripts": {
"start": "node_modules/.bin/webpack-dev-server --progress --colors"
}
}
Dev-Server in webpack.config.js
konfigurieren:
module.exports = {
// ...
devServer: {
host: '0.0.0.0', // Make dev-server accessible from local network
port: 3000
}
}
Proxy definieren:
TODO Beispiel ist Webpack 1
module.exports = {
// ...
devServer: {
// Proxy to another server
proxy: {
// Proxy a certain path to another server
'/myservice': {
target: 'http://other-server.example.com/myservice',
secure: false,
pathRewrite: {'^/myservice' : ''} // Optional: rewrite the request path
},
// Proxy everything but a certain path to another server
'**': {
target: 'http://localhost:8000/myappname',
secure: false,
bypass: function(req, res, proxyOptions) {
if (req.path.match('^/assets/')) {
return req.path;
}
}
}
}
}
};
Hinweise:
- Für die Proxy-Regeln verwendet der Dev-Server
http-proxy-middleware
. Weitere Möglichkeiten siehe Doku von http-proxy-middleware. pathRewrite
unterstützt auch Funktionen. Siehe Doku von http-proxy-middleware.
Development-Server starten:
npm start
Projekt im Browser öffnen: http://localhost:8080/webpack-dev-server/bundle
Hot Module Replacement (HMR)
Man kann statt einem automatischen Reload auch HMR (Hot Module Replacement) verwenden. Dabei wird bei einer Änderung die Seite nicht neu geladen. Statt dessen wird das geänderte Modul in der laufenden Seite ersetzt.
Ändert sich ein Modul, dann wird dieses Modul und alle Module die davon abhängen neu initialisiert. Ein Modul kann auf ein solches Update reagieren, um bestehende Objekt-Instanzen in der laufenden App zu aktualisieren (Details siehe Doku zu Hot Module Replacement). Manche Bibliotheken (z.B. Redux) unterstützen dies bereits out-of-the-box, für andere gibt es entsprechende Bibliotheken (z.B. react-hot-loader für React).
Um CSS HMR-fähig zu machen, muss der style
-Loader (statt dem css
-Loader) verwendet werden.
Verwendung
Webpack mit webpack-dev-server --inline --hot
aufrufen.
Statt als Parameter kann man HMR auch in der Konfiguration anschalten:
devServer: {
hot: true
},
React Hot Loader
react-hot-loader
lokal installieren:
npm install --save-dev react-hot-loader@next
Hinweis: Wir verwenden die Version 3 von react-hot-loader
, welche aktuell (Stand 22.02.17) nur als Beta verfügbar ist (daher Installation mit @next
).
In package.json
beim Start von webpack-dev-server
HMR (--hot
) verwenden (Entsprechende Option in webpack.config.js
reicht nicht):
{
"scripts": {
"server": "node_modules/.bin/webpack-dev-server --progress --colors --hot"
}
}
webpack.config.js
erweitern ('react-hot-loader'
als Babel-Plugin eintragen):
var plugins = ...
if (production) {
...
} else {
plugins = plugins.concat([
// Prints more readable module names in the browser console on HMR updates
new webpack.NamedModulesPlugin()
])
}
module.exports = {
...
plugins: plugins,
module: {
rules: [
{
test: /\.jsx?$/,
include: path.resolve(__dirname, 'src'),
exclude: path.resolve(__dirname, 'src/lib'),
loader: 'babel-loader',
options: {
plugins: production ? [] : [ 'react-hot-loader/babel' ],
presets: [ ['es2015', { modules: false }], 'react']
}
},
...
]
}
}
Nun muss noch ganz oben in der UI-Hierarchie (z.B. index.jsx
) ein Aufruf von module.hot.accept
rein:
if (module.hot) {
module.hot.accept('app/ui/main/Main', () => {
const NextMain = require('app/ui/main/Main').default;
render(
<Provider store={store}>
<NextMain />
</Provider>,
rootElem)
});
}
Links:
- Offizieller Webpack-Guide "HMR React"
- Demo siehe Video
- Schnelles Arbeiten mit kurzem Turn-Around durch Zusammenspiel von React mit react-hot-loader und Webpacks HMR (Hot Module Replacement).
- Beispiel-Boilerplate-Projekte, das minimale: react-hot-boilerplate
- Anleitung
Code-Splitting (Chunks)
Normalerweise erzeugt Webpack ein einziges monolitisches Bundle pro Entry. Wenn man nun große Teile im Code hat, die nur selten benötigt werden, dann kann man diese in einen Chunk - also ein separates Bundle - auslagern. Ein Chunk wird nur dann geladen, sobald er benötigt wird. Man definiert einen Chunk, indem man im Code einen Split-Point vorsieht.
Split-Point im JavaScript-Code:
require.ensure([], function () {
// Dieser Code landet mit seinen Abhängigkeiten in einer separaten Datei
var library = require('some-big-library');
library.doSomething();
});
TODO Chunks benennen mit require.ensure([], function () { ... }, 'myChunkName')
geht nicht.
In webpack.config.js
definieren, unter welchem Pfad die Chunks geladen werden können:
TODO Beispiel ist Webpack 1
module.exports = {
// ...
output: {
// ...
publicPath: 'build/'
},
};
Hinweis: Das Attribut output.publicPath
gibt den Pfad zu den Chunks an - aus der Sicht der Seite zur Laufzeit im Browser.
Tipp: Wenn man beim Webpack-Aufruf die Parameter --display-modules --display-chunks
mitgibt, dann sieht man genau, welches Modul in welchem Chunk landet.
Chunks automatisch erzeugen
Wenn man mehrere Entries definiert hat oder wenn man manuelle Chunks verwendet, dann kann es passieren, dass mehrere Bundles oder Chunks die gleichen Abhängigkeiten haben. Diese werden dann unter Umständen doppelt geladen.
Dies kann man mit dem CommonChunksPlugin vermeiden. Das CommonChunksPlugin findet doppelte Abhängigkeiten in Bundles und Chunks und kann diese in eine separate Datei (z.B. vendor.js
) oder auch in das Haupt-Bundle auslagern.
CommonChunksPlugin in webpack.config.js
hinzufügen:
TODO Beispiel ist Webpack 1
var webpack = require('webpack');
module.exports = {
// ...
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'main', // Füge die gemeinsamen Abhängigkeiten zum Haupt-Bundle hinzu
children: true, // Prüfe alle Kinder auf gemeinsame Abhängigkeiten
minChunks: 2 // Wie oft eine Abhängigkeit auftauchen muss, damit sie verschoben wird
})
],
module: {
// ...
}
};
Hinweis: Gibt man im Attribut name
einen andern Namen an, dann wird ein separates Bundle für die gemeinsamen Abhängigkeiten generiert. Ein name: 'common'
erzeugt beispielsweise ein common.js
. Dieses Bundle müsste man dann manuell z.B. im HTML-Code einbinden.
Hinweis: Statt einem name
-Attribut, kann man auch async: true
angeben. Dann wird der Chunk mit den gemeinsamen Abhängigkeiten bei Bedarf automatisch geladen.
Dependency-Tree analysieren
TODO Siehe Webpack your bags, Abschnitt "Would you like to know more?".
Hintergrund: Loader-Pipelines definieren
Webpack kann von sich aus nur JavaScript verarbeiten. Für alle anderen Formate muss man Loader oder Pipelines von Loadern definieren.
TODO Loaders are small plugins that basically say “When you encounter this kind of file, do this with it”. Ultimately at the end of the food chain all loaders return strings. This allows Webpack to wrap them into Javascript modules.
Loader-Pipeline im require-Statement definieren
Man kann die Loader-Pipeline direkt im require-Statement definieren.
Im JavaScript-Code benötigtes CSS anfordern:
require('!style!css!./style.css');
Mit den !
definiert man eine Loader-Pipeline - diese muss man von rechts nach links lesen. Im Beispiel: ./style.css
laden, an css
-Loader weitergeben und dessen Ergebnis dem style
-Loader geben.
Loader-Pipeline im Aufruf von Webpack definieren
Damit man nicht bei jeder CSS-Abhängigkeit immer wieder die gleiche Pipeline definieren muss, kann man sie auch einmalig im Aufruf von Webpack definieren.
webpack ./entry.js bundle.js --module-bind 'css=style!css'
Das reqire-Statement sieht dann so aus:
require('./style.css');
Loader-Pipeline in Config-Datei definieren
Noch besser ist jedoch, Loader-Pipelines in der Config-Datei zu definieren. Dann muss man sich beim Aufruf von Webpack nicht mehr darum kümmern.
Loader-Pipeline in webpack.config.js
definieren:
TODO Beispiel ist Webpack 1
module.exports = {
// ...
module: {
loaders: [
{
test: /\.css$/,
loader: "style!css"
// Oder als Array:
loaders: ['style', 'css'],
}
]
}
};
Die Regel besagt also: Webpack soll alles mit der Dateiendung css
erst durch den css
-Loader und dann durch den style
-Loader jagen.
Das reqire-Statement bleibt wie gehabt:
require('./style.css');
Hintergrund: Plugins
TODO Plugins differ from loaders in the sense that instead of only executing on a certain set of files, and being more of a “pipe”, they execute on all files and perform more advanced actions, that aren’t necessarily related to transformations.