Mit Webpack die App so klein wie möglich machen
Eine der ersten Maßnahmen bei der Optimierung einer Anwendung besteht darin, sie so klein wie möglich zu machen. Mit webpack geht das so:
Produktionsmodus verwenden (nur Webpack 4)
In Webpack 4 wurde das neue Flag mode
eingeführt. Sie können dieses Flag auf 'development'
oder 'production'
setzen, um webpack zu signalisieren, dass Sie die Anwendung für eine bestimmte Umgebung erstellen:
// webpack.config.js
module.exports = {
mode: 'production',
};
Aktivieren Sie den production
-Modus, wenn Sie Ihre App für die Produktion erstellen.
Dadurch führt webpack Optimierungen wie Minimierung und das Entfernen von nur für die Entwicklung vorgesehenem Code in Bibliotheken durch. Weitere Informationen
Weitere Informationen
Minimierung aktivieren
Beim Minimieren wird der Code komprimiert, indem z. B. zusätzliche Leerzeichen entfernt und Variablennamen verkürzt werden. Ein Beispiel:
// Original code
function map(array, iteratee) {
let index = -1;
const length = array == null ? 0 : array.length;
const result = new Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
↓
// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}
Webpack unterstützt zwei Möglichkeiten zum Minimieren des Codes: Minimierung auf Bundle-Ebene und loaderspezifische Optionen. Sie sollten gleichzeitig verwendet werden.
Minimierung auf Bundle-Ebene
Bei der Minimierung auf Bundle-Ebene wird das gesamte Bundle nach der Kompilierung komprimiert. So funktionierts:
Sie schreiben den Code so:
// comments.js import './comments.css'; export function render(data, target) { console.log('Rendered!'); }
Webpack kompiliert es in etwa in Folgendes:
// bundle.js (part of) "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony export (immutable) */ __webpack_exports__["render"] = render; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__); function render(data, target) { console.log('Rendered!'); }
Ein Minifier komprimiert es in etwa auf Folgendes:
// minified bundle.js (part of) "use strict";function t(e,n){console.log("Rendered!")} Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
In webpack 4 ist die Minimierung auf Bundleebene automatisch aktiviert – sowohl im Produktionsmodus als auch ohne. Dabei wird der UglifyJS-Minifier verwendet. Wenn Sie die Minimierung deaktivieren möchten, verwenden Sie einfach den Entwicklungsmodus oder übergeben Sie false
an die Option optimization.minimize
.
In webpack 3 müssen Sie das UglifyJS-Plug-in direkt verwenden. Das Plug-in ist im Lieferumfang von webpack enthalten. Wenn Sie es aktivieren möchten, fügen Sie es dem Abschnitt plugins
der Konfiguration hinzu:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
Ladegerätespezifische Optionen
Die zweite Möglichkeit zum Minimieren des Codes sind ladespezifische Optionen (Was ist ein Loader?). Mit den Ladeoptionen können Sie Elemente komprimieren, die vom Minifier nicht minimiert werden können. Wenn Sie beispielsweise eine CSS-Datei mit css-loader
importieren, wird die Datei in einen String kompiliert:
/* comments.css */
.comment {
color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n color: black;\r\n}",""]);
Der Minifier kann diesen Code nicht komprimieren, da es sich um einen String handelt. Um den Dateiinhalt zu minimieren, müssen wir den Lader entsprechend konfigurieren:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { minimize: true } },
],
},
],
},
};
Weitere Informationen
- Dokumentation zum UglifyJsPlugin
- Weitere beliebte Minifier: Babel Minify, Google Closure Compiler
Geben Sie NODE_ENV=production
an
Eine weitere Möglichkeit, die Größe des Front-Ends zu verringern, besteht darin, die NODE_ENV
-Umgebungsvariable in Ihrem Code auf den Wert production
festzulegen.
Bibliotheken lesen die Variable NODE_ENV
, um zu erkennen, in welchem Modus sie arbeiten sollen – im Entwicklungs- oder Produktionsmodus. Einige Bibliotheken verhalten sich je nach dieser Variablen unterschiedlich. Wenn NODE_ENV
beispielsweise nicht auf production
festgelegt ist, führt Vue.js zusätzliche Prüfungen durch und druckt Warnungen aus:
// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
// …
React funktioniert ähnlich: Es wird ein Entwicklungsbuild geladen, der die Warnungen enthält:
// react/index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
// react/cjs/react.development.js
// …
warning$3(
componentClass.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.'
);
// …
Solche Prüfungen und Warnungen sind in der Produktion in der Regel nicht erforderlich, sie bleiben jedoch im Code und erhöhen die Größe der Bibliothek. In webpack 4 entfernen Sie sie,indem Sie die Option optimization.nodeEnv: 'production'
hinzufügen:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};
In Webpack 3 verwenden Sie stattdessen DefinePlugin
:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.optimize.UglifyJsPlugin()
]
};
Sowohl die Option optimization.nodeEnv
als auch DefinePlugin
funktionieren auf die gleiche Weise: Sie ersetzen alle Vorkommen von process.env.NODE_ENV
durch den angegebenen Wert. Mit der Konfiguration oben:
Webpack ersetzt alle Vorkommen von
process.env.NODE_ENV
durch"production"
:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
Der Minifier entfernt dann alle diese
if
-Äste, da"production" !== 'production'
immer falsch ist und das Plug-in weiß, dass der Code in diesen Ästen nie ausgeführt wird:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js (without minification) if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; }
Weitere Informationen
- Was sind Umgebungsvariablen?
- Webpack-Dokumente zu:
DefinePlugin
,EnvironmentPlugin
ES-Module verwenden
Eine weitere Möglichkeit, die Größe des Front-Ends zu verringern, besteht darin, ES-Module zu verwenden.
Wenn Sie ES-Module verwenden, kann webpack Tree-Shaking durchführen. Beim Tree-Shaking durchläuft ein Bundler den gesamten Abhängigkeitsbaum, prüft, welche Abhängigkeiten verwendet werden, und entfernt nicht verwendete. Wenn Sie also die ES-Modulsyntax verwenden, kann webpack den nicht verwendeten Code entfernen:
Sie schreiben eine Datei mit mehreren Exporten, aber die App verwendet nur einen davon:
// comments.js export const render = () => { return 'Rendered!'; }; export const commentRestEndpoint = '/rest/comments'; // index.js import { render } from './comments.js'; render();
Webpack erkennt, dass
commentRestEndpoint
nicht verwendet wird, und generiert keinen separaten Exportpunkt im Bundle:// bundle.js (part that corresponds to comments.js) (function(module, __webpack_exports__, __webpack_require__) { "use strict"; const render = () => { return 'Rendered!'; }; /* harmony export (immutable) */ __webpack_exports__["a"] = render; const commentRestEndpoint = '/rest/comments'; /* unused harmony export commentRestEndpoint */ })
Der Minifier entfernt die nicht verwendete Variable:
// bundle.js (part that corresponds to comments.js) (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
Das funktioniert auch mit Bibliotheken, wenn sie mit ES-Modulen geschrieben wurden.
Sie müssen jedoch nicht unbedingt den integrierten Minifier von webpack (UglifyJsPlugin
) verwenden.
Jedes Minify-Tool, das das Entfernen von Totcode unterstützt (z.B. das Babel-Minify-Plug-in oder das Google Closure Compiler-Plug-in), ist geeignet.
Weitere Informationen
Bilder optimieren
Bilder machen mehr als die Hälfte der Seitengröße aus. Sie sind zwar nicht so kritisch wie JavaScript (z.B. blockieren sie das Rendering nicht), belegen aber dennoch einen großen Teil der Bandbreite. Verwenden Sie url-loader
, svg-url-loader
und image-webpack-loader
, um sie in webpack zu optimieren.
url-loader
fügt kleine statische Dateien inline in die App ein. Ohne Konfiguration wird eine übergebene Datei neben dem kompilierten Bundle abgelegt und eine URL dieser Datei zurückgegeben. Wenn wir jedoch die Option limit
angeben, werden Dateien, die kleiner als dieses Limit sind, als Base64-Daten-URL codiert und diese URL zurückgegeben. Dadurch wird das Bild in den JavaScript-Code eingefügt und eine HTTP-Anfrage gespart:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
},
},
],
}
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`
svg-url-loader
funktioniert genauso wie url-loader
, mit der Ausnahme, dass Dateien mit der URL-Codierung anstelle der Base64-Codierung codiert werden. Das ist nützlich für SVG-Bilder, da SVG-Dateien nur reiner Text sind. Diese Codierung ist platzsparender.
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: "svg-url-loader",
options: {
limit: 10 * 1024,
noquotes: true
}
}
]
}
};
image-webpack-loader
komprimiert Bilder, die durch sie hindurchgehen. Sie unterstützt JPG-, PNG-, GIF- und SVG-Bilder. Wir verwenden sie daher für alle diese Bildtypen.
Dieser Lader bettet keine Bilder in die App ein. Er muss daher mit url-loader
und svg-url-loader
kombiniert werden. Um zu vermeiden, dass wir ihn in beide Regeln kopieren und einfügen müssen (eine für JPG-/PNG-/GIF-Bilder und eine für SVG-Bilder), fügen wir diesen Loader als separate Regel mit enforce: 'pre'
ein:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
// This will apply the loader before the other ones
enforce: 'pre'
}
]
}
};
Die Standardeinstellungen des Laders sind bereits in Ordnung. Wenn du ihn weiter konfigurieren möchtest, sieh dir die Plug-in-Optionen an. Welche Optionen Sie festlegen sollten, erfahren Sie im Leitfaden zur Bildoptimierung von Addy Osmani.
Weitere Informationen
- „Wozu dient die Base64-Codierung?“
- Leitfaden zur Bildoptimierung von Addy Osmani
Abhängigkeiten optimieren
Über die Hälfte der durchschnittlichen JavaScript-Größe entfällt auf Abhängigkeiten. Ein Teil dieser Größe ist möglicherweise unnötig.
Lodash (Version 4.17.4) fügt dem Bundle beispielsweise 72 KB minimierten Code hinzu. Wenn Sie jedoch nur etwa 20 Methoden davon verwenden, sind etwa 65 KB minimierter Code völlig nutzlos.
Ein weiteres Beispiel ist Moment.js. Die Version 2.19.1 benötigt 223 KB minimierten Code. Das ist enorm – die durchschnittliche Größe von JavaScript auf einer Seite betrug im Oktober 2017 452 KB. 170 KB davon entfallen jedoch auf Dateien für die Lokalisierung. Wenn Sie Moment.js nicht mit mehreren Sprachen verwenden, vergrößern diese Dateien das Bundle unnötig.
Alle diese Abhängigkeiten können ganz einfach optimiert werden. Wir haben Optimierungsansätze in einem GitHub-Repository zusammengestellt. Sehen Sie sich das an.
Modulkonkatenierung für ES-Module aktivieren (auch als Bereichsaufhebung bezeichnet)
Wenn Sie ein Bundle erstellen, umhüllt webpack jedes Modul in eine Funktion:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// bundle.js (part of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_exports__["a"] = render;
function render(data, target) {
console.log('Rendered!');
}
})
Früher war dies erforderlich, um CommonJS-/AMD-Module voneinander zu isolieren. Dies führte jedoch zu einem Größen- und Leistungsaufwand für jedes Modul.
Webpack 2 führte die Unterstützung für ES-Module ein, die im Gegensatz zu CommonJS- und AMD-Modulen gebündelt werden können, ohne dass jedes Modul in eine Funktion verpackt werden muss. Webpack 3 ermöglichte dies durch Modulkonkatenierung. So funktioniert die Modulkonkatenierung:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files
// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// CONCATENATED MODULE: ./comments.js
function render(data, target) {
console.log('Rendered!');
}
// CONCATENATED MODULE: ./index.js
render();
})
Sehen Sie den Unterschied? Im einfachen Bundle benötigte Modul 0 render
von Modul 1. Bei der Modulkonkatenierung wird require
einfach durch die erforderliche Funktion ersetzt und Modul 1 wird entfernt. Das Bundle enthält weniger Module – und weniger Modul-Overhead!
Wenn Sie dieses Verhalten aktivieren möchten, aktivieren Sie in webpack 4 die Option optimization.concatenateModules
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true
}
};
In webpack 3 verwenden Sie die ModuleConcatenationPlugin
:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
Weitere Informationen
- Webpack-Dokumentation für das ModulConcatenationPlugin
- „Kurze Einführung in die Variablenbereichsaufhebung“
- Ausführliche Beschreibung der Funktionsweise dieses Plug-ins
Verwenden Sie externals
, wenn Sie sowohl webpack- als auch nicht webpack-Code haben.
Angenommen, Sie haben ein großes Projekt, bei dem ein Teil des Codes mit webpack und ein anderer Teil nicht kompiliert wird. Beispiel: Eine Video-Hosting-Website, auf der das Player-Widget mit Webpack erstellt wurde, die umgebende Seite aber nicht:
Wenn beide Codeteile gemeinsame Abhängigkeiten haben, können Sie sie freigeben, um zu vermeiden, dass der Code mehrmals heruntergeladen wird. Dazu wird die externals
-Option von webpack verwendet, die Module durch Variablen oder andere externe Importe ersetzt.
Wenn Abhängigkeiten in window
verfügbar sind
Wenn Ihr Code, der nicht von Webpack verwendet wird, auf Abhängigkeiten angewiesen ist, die in window
als Variablen verfügbar sind, weisen Sie den Abhängigkeitsnamen Aliasse zu Variablennamen zu:
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
Mit dieser Konfiguration bündelt webpack die Pakete react
und react-dom
nicht. Stattdessen wird etwa Folgendes angezeigt:
// bundle.js (part of)
(function(module, exports) {
// A module that exports `window.React`. Without `externals`,
// this module would include the whole React bundle
module.exports = React;
}),
(function(module, exports) {
// A module that exports `window.ReactDOM`. Without `externals`,
// this module would include the whole ReactDOM bundle
module.exports = ReactDOM;
})
Wenn Abhängigkeiten als AMD-Pakete geladen werden
Wenn Ihr Code, der nicht mit Webpack erstellt wurde, keine Abhängigkeiten in window
freigibt, ist die Situation etwas komplizierter.
Sie können jedoch verhindern, dass derselbe Code zweimal geladen wird, wenn der Code, der nicht zu Webpack gehört, diese Abhängigkeiten als AMD-Pakete verwendet.
Dazu kompilieren Sie den Webpack-Code als AMD-Bundle und weisen Sie Aliasmodulen Bibliotheks-URLs zu:
// webpack.config.js
module.exports = {
output: {
libraryTarget: 'amd'
},
externals: {
'react': {
amd: '/libraries/react.min.js'
},
'react-dom': {
amd: '/libraries/react-dom.min.js'
}
}
};
Webpack verpackt das Bundle in define()
und hängt es von diesen URLs ab:
// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });
Wenn nicht webpack-Code dieselben URLs zum Laden seiner Abhängigkeiten verwendet, werden diese Dateien nur einmal geladen. Für zusätzliche Anfragen wird der Loader-Cache verwendet.
Weitere Informationen
- Webpack-Dokumente auf
externals
Zusammenfassung
- Aktivieren Sie den Produktionsmodus, wenn Sie webpack 4 verwenden.
- Code mit den Minifyer- und Loader-Optionen auf Bundle-Ebene minimieren
- Entfernen Sie den Code, der nur für die Entwicklung gedacht ist, indem Sie
NODE_ENV
durchproduction
ersetzen. - ES-Module verwenden, um das Entfernen von Elementen aus dem DOM zu aktivieren
- Bilder komprimieren
- Abhängigkeitsspezifische Optimierungen anwenden
- Modulkonkatenierung aktivieren
- Verwende
externals
, wenn das für dich sinnvoll ist.