Mit Webpack Ihre App so klein wie möglich machen
Wenn Sie eine Anwendung optimieren möchten, sollten Sie sie zuerst so klein wie möglich halten. Mit webpack geht das so:
Produktionsmodus verwenden (nur Webpack 4)
Mit Webpack 4 wurde das neue mode
-Flag 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 wendet das Webpack Optimierungen wie die Reduzierung an, entfernt den reinen Entwicklungscode in Bibliotheken und mehr.
Weitere Informationen
Reduzierung aktivieren
Bei der Minimierung wird der Code durch das Entfernen zusätzlicher Leerzeichen, das Kürzen von Variablennamen usw. komprimiert. 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 Reduzieren des Codes: Reduzierung auf Bundle-Ebene und loaderspezifische Optionen. Sie sollten gleichzeitig verwendet werden.
Komprimierung auf Bundle-Ebene
Bei der Komprimierung 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 sie in ungefähr folgende Elemente:
// 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 Bundle-Ebene automatisch aktiviert – sowohl im Produktionsmodus als auch ohne. Dafür wird im Hintergrund der UglifyJS-Minifier verwendet. Wenn Sie die Reduzierung deaktivieren müssen, 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 Webpack enthalten. Fügen Sie es dem Abschnitt plugins
der Konfiguration hinzu, um es zu aktivieren:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
Loader-spezifische Optionen
Die zweite Möglichkeit, den Code zu komprimieren, sind loader-spezifische Optionen (was ein Loader ist). 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 Code kann nicht komprimiert werden, da es sich um einen String handelt. Um den Dateiinhalt zu komprimieren, müssen wir den Loader so konfigurieren:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { minimize: true } },
],
},
],
},
};
Weitere Informationen
- The UglifyJsPlugin-Dokumentation
- Andere beliebte Minifier: Babel Minify, Google Closure Compiler
Geben Sie NODE_ENV=production
an
Sie können die Frontend-Größe auch reduzieren, indem Sie die Umgebungsvariable NODE_ENV
in Ihrem Code auf den Wert production
festlegen.
Bibliotheken lesen die Variable NODE_ENV
, um zu erkennen, in welchem Modus sie arbeiten sollen – im Entwicklungs- oder im 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 Entwicklungs-Build geladen, der die folgenden 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()
]
};
Die Option optimization.nodeEnv
und DefinePlugin
funktionieren auf die gleiche Weise – sie ersetzen alle Vorkommen von process.env.NODE_ENV
durch den angegebenen Wert. Mit der Konfiguration von 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.'); }
Und dann entfernt der Minifier alle diese
if
-Zweige – da"production" !== 'production'
immer „false“ ist und das Plug-in versteht, dass der Code in diesen Zweigen 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 Baumwackeln durchläuft ein Bundler die gesamte Abhängigkeitsstruktur, prüft, welche Abhängigkeiten verwendet werden, und entfernt nicht verwendete Abhängigkeiten. 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 (UglifyJsPlugin
) von Webpack 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
Webpack-Dokumentation zum Baumschütteln
Bilder optimieren
Bilder machen mehr als die Hälfte der Seitengröße aus. Sie sind zwar nicht so wichtig wie JavaScript (z.B. blockieren das Rendering), verbrauchen 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 wird 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 genau wie url-loader
, nur dass Dateien mit URL-Codierung und nicht mit Base64 codiert werden. Dies ist bei SVG-Bildern nützlich, da SVG-Dateien nur Text sind. Diese Codierung ist größeneffizienter.
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 Typen.
Dieser Lader bettet keine Bilder in die App ein. Er muss daher mit url-loader
und svg-url-loader
kombiniert werden. Damit das Kopieren und Einfügen in beide Regeln (eine für JPG-/PNG-/GIF-Bilder und eine für SVG-Bilder) vermieden wird, fügen wir dieses Ladeprogramm als separate Regel in 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 Ladeprogramms sind bereits aktiviert. Wenn Sie sie jedoch weiter konfigurieren möchten, sehen Sie sich die Plug-in-Optionen an. Informationen zur Auswahl der Optionen finden Sie im hervorragenden 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 (ab Version 4.17.4) fügt dem Bundle beispielsweise 72 KB reduzierten Code hinzu. Wenn Sie jedoch nur 20 der Methoden verwenden, sind etwa 65 KB reduzierten Code einfach nichts.
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, wird das Bundle durch diese Dateien ohne Zweck aufgebläht.
Alle diese Abhängigkeiten können ganz einfach optimiert werden. Wir haben Optimierungsansätze in einem GitHub-Repository zusammengestellt – jetzt ansehen!
Verkettung von Modulen für ES-Module aktivieren (auch „Scope Hoisting“ genannt)
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.
Durch Webpack 2 werden ES-Module unterstützt, die im Gegensatz zu CommonJS- und AMD-Modulen gebündelt werden können, ohne sie mit einer Funktion zu versehen. Webpack 3 ermöglichte dies durch Modulkonkatenierung. So funktioniert die Modulverkettung:
// 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 Paket umfasst weniger Module – und weniger Modulaufwand.
Um dieses Verhalten zu aktivieren, aktivieren Sie in Webpack 4 die Option optimization.concatenateModules
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true
}
};
Verwende in Webpack 3 das 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 ModuleConcatenationPlugin
- „Kurze Einführung in das Heben von Projektumfang“
- Ausführliche Beschreibung der Funktionen dieses Plug-ins
Verwenden Sie externals
, wenn Sie sowohl webpack- als auch nicht webpack-Code haben.
Möglicherweise haben Sie ein großes Projekt, bei dem ein Teil des Codes mit Webpack kompiliert wird, ein anderer Code jedoch nicht. Beispiel: Eine Video-Hosting-Website, auf der das Player-Widget mit Webpack erstellt wurde, die umgebende Seite aber nicht:
Wenn beide Codeabschnitte gemeinsame Abhängigkeiten haben, können Sie sie freigeben, um zu vermeiden, dass der Code mehrfach 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 zu Webpack gehört, 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 werden sie so ersetzt:
// 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 Aliasmodule für Bibliotheks-URLs:
// webpack.config.js
module.exports = {
output: {
libraryTarget: 'amd'
},
externals: {
'react': {
amd: '/libraries/react.min.js'
},
'react-dom': {
amd: '/libraries/react-dom.min.js'
}
}
};
Webpack umschließt das Bundle in define()
und richtet es von den folgenden URLs abhängig:
// 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
- Produktionsmodus bei Verwendung von Webpack 4 aktivieren
- Code mit den Minifyer- und Loader-Optionen auf Bundle-Ebene minimieren
- Entfernen Sie den Entwicklungscode, indem Sie
NODE_ENV
durchproduction
ersetzen - ES-Module zum Aktivieren von Tree Shaking verwenden
- Bilder komprimieren
- Abhängigkeitsspezifische Optimierungen anwenden
- Modulkonkatenierung aktivieren
- Verwende
externals
, wenn dies für dich sinnvoll ist