Wie du mit Webpack deine App so klein wie möglich gestalten kannst
Einer der ersten Schritte bei der Optimierung einer Anwendung besteht darin, sie so klein wie möglich zu machen. So gehts mit Webpack.
Produktionsmodus verwenden (nur Webpack 4)
Webpack 4 hat das neue Flag mode
eingeführt. Sie können dieses Flag auf 'development'
oder 'production'
setzen, um Webpack darauf hinzuweisen, dass Sie die Anwendung für eine bestimmte Umgebung erstellen:
// webpack.config.js
module.exports = {
mode: 'production',
};
Achte darauf, den production
-Modus zu aktivieren, wenn du deine App für die Produktion erstellst.
Dadurch wendet Webpack Optimierungen wie die Reduzierung von Code in Bibliotheken und mehr an, der nur für die Entwicklung vorgesehen ist.
Weitere Informationen
Reduzierung aktivieren
Bei der Reduzierung wird der Code komprimiert, indem u. a. zusätzliche Leerzeichen entfernt oder Variablennamen gekü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 Reduzieren des Codes: die Komprimierung auf Bundle-Ebene und loader-spezifische Optionen. Sie sollten gleichzeitig verwendet werden.
Reduzierung auf Bundle-Ebene
Durch die Reduzierung auf Bundle-Ebene wird das gesamte Bundle nach der Kompilierung komprimiert. Das geht so:
Sie schreiben Code wie folgt:
// comments.js import './comments.css'; export function render(data, target) { console.log('Rendered!'); }
Webpack kompiliert es in etwa so:
// 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!'); }
Die Komprimierung erfolgt in etwa so:
// 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 Reduzierung auf Bundle-Ebene automatisch aktiviert – sowohl im Produktionsmodus als auch ohne. Es nutzt den UglifyJS-Minifier im Hintergrund. Wenn Sie die Minimierung 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. Um es zu aktivieren, müssen Sie es dem Abschnitt plugins
der Konfiguration hinzufügen:
// 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 Ladeprogramm ist). Mit den Loader-Optionen können Sie Elemente komprimieren, die sich nicht komprimieren lassen. 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 von der Minifier nicht komprimiert werden, da er ein String ist. Um den Dateiinhalt zu reduzieren, 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
- Die UglifyJsPlugin-Dokumentation
- Weitere beliebte Minifier: Babel Minify, Google Closure Compiler
Geben Sie NODE_ENV=production
an
Eine weitere Möglichkeit, die Frontend-Größe zu verringern, besteht darin, die Umgebungsvariable NODE_ENV
im Code auf den Wert production
festzulegen.
Bibliotheken lesen die Variable NODE_ENV
, um zu ermitteln, in welchem Modus sie arbeiten sollten – in der Entwicklungs- oder in der Produktion. Einige Bibliotheken verhalten sich aufgrund dieser Variablen anders. Wenn NODE_ENV
beispielsweise nicht auf production
gesetzt ist, führt Vue.js zusätzliche Prüfungen durch und gibt 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 verbleiben jedoch im Code und erhöhen die Größe der Bibliothek. Entfernen Sie sie in Webpack 4,indem Sie die Option optimization.nodeEnv: 'production'
hinzufügen:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};
Verwende in Webpack 3 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. Führen Sie die obige Konfiguration aus:
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.'); }
Anschließend entfernt der Minifier alle entsprechenden
if
-Zweige, da"production" !== 'production'
immer falsch ist und das Plug-in versteht, dass der Code in diesen Zweigen niemals 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-Dokumentation zu:
DefinePlugin
,EnvironmentPlugin
ES-Module verwenden
Die nächste Möglichkeit, die Frontend-Größe zu verringern, besteht in der Verwendung von ES-Modulen.
Wenn Sie ES-Module verwenden, kann Webpack das Tree Shaking übernehmen. Baumwackeln tritt auf, wenn ein Bundler den gesamten Abhängigkeitsbaum durchläuft, überprüft, welche Abhängigkeiten verwendet werden, und nicht verwendete Abhängigkeiten entfernt. Wenn Sie also die ES-Modulsyntax verwenden, kann Webpack den nicht verwendeten Code entfernen:
Sie schreiben eine Datei mit mehreren Exporten, aber die Anwendung 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 versteht, dass
commentRestEndpoint
nicht verwendet wird, und generiert im Bundle keinen separaten Exportpunkt:// 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})
Dies funktioniert auch mit Bibliotheken, wenn diese mit ES-Modulen geschrieben wurden.
Sie müssen jedoch nicht den genauen integrierten Minifier von Webpack (UglifyJsPlugin
) verwenden.
Dazu können Sie jeden Minifier, der die Entfernung von inaktivem Code unterstützt (z.B. Babel Minify-Plug-in oder Google Closure Compiler-Plug-in) unterstützt.
Weitere Informationen
Webpack-Dokumentation zu Baumwackeln
Bilder optimieren
Bilder machen mehr als die Hälfte der Seitengröße aus. Sie sind zwar nicht so wichtig wie JavaScript (sie blockieren das Rendering), verbrauchen aber dennoch einen großen Teil der Bandbreite. Verwende url-loader
, svg-url-loader
und image-webpack-loader
, um sie im Webpack zu optimieren.
Mit url-loader
werden kleine statische Dateien in die Anwendung inline eingefügt. Ohne Konfiguration wird eine übergebene Datei genommen, neben das kompilierte Bundle platziert und die URL dieser Datei zurückgegeben. Wenn wir jedoch die Option limit
angeben, werden Dateien, die kleiner sind, als Base64-Daten-URL codiert und diese URL wird zurückgegeben. Dadurch wird das Bild in den JavaScript-Code eingebettet und eine HTTP-Anfrage gespeichert:
// 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 wie url-loader
, mit der Ausnahme, dass Dateien mit der URL-Codierung statt mit der Base64-Codierung codiert werden. Dies ist nützlich für SVG-Bilder, da SVG-Dateien nur aus Text bestehen und diese Codierung eine größere Größenoptimierung ermöglicht.
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: "svg-url-loader",
options: {
limit: 10 * 1024,
noquotes: true
}
}
]
}
};
Mit image-webpack-loader
werden die durchlaufenen Images komprimiert. Es unterstützt JPG-, PNG-, GIF- und SVG-Bilder, sodass wir es für alle diese Typen verwenden werden.
Dieses Ladeprogramm bettet keine Bilder in die App ein. Daher muss es zusammen mit url-loader
und svg-url-loader
funktionieren. Damit es nicht kopiert und in beide Regeln (eine für JPG-/PNG-/GIF-Bilder und eine für SVG-Bilder) eingefügt wird, fügen wir dieses Ladeprogramm 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 Ladeprogramms sind bereits festgelegt. Wenn Sie die Einstellungen weiter anpassen möchten, sehen Sie sich die Plug-in-Optionen an. Informationen zur Auswahl der Optionen finden Sie in Addy Osmanis ausgezeichnetem Leitfaden zur Bildoptimierung.
Weitere Informationen
Abhängigkeiten optimieren
Mehr als die Hälfte der durchschnittlichen JavaScript-Größe stammt aus Abhängigkeiten, und ein Teil dieser Größe ist möglicherweise einfach unnötig.
Lodash (ab Version 4.17.4) fügt dem Bundle zum Beispiel 72 KB komprimierten Code hinzu. Wenn Sie jedoch nur 20 der Methoden verwenden, werden ca. 65 KB an reduziertem Code nichts.
Ein weiteres Beispiel ist Moment.js. Die Version 2.19.1 benötigt 223 KB reduzierten Code. Das ist eine enorme Summe. Die durchschnittliche Größe von JavaScript auf einer Seite betrug im Oktober 2017 452 KB. 170 KB dieser Größe sind jedoch Lokalisierungsdateien. Wenn Sie Moment.js nicht mit mehreren Sprachen verwenden, blähen diese Dateien das Bundle ohne Zweck auf.
All diese Abhängigkeiten können einfach optimiert werden. Wir haben Optimierungsansätze in einem GitHub-Repository zusammengestellt. Weitere Informationen
Modulverkettung für ES-Module aktivieren (auch Bereichshub)
Wenn Sie ein Bundle erstellen, verpackt 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!');
}
})
In der Vergangenheit war dies erforderlich, um CommonJS/AMD-Module voneinander zu isolieren. Dies führte jedoch zu zusätzlichem Umfang und Leistungsaufwand für jedes Modul.
Webpack 2 unterstützt jetzt ES-Module, die im Gegensatz zu CommonJS- und AMD-Modulen gebündelt werden können, ohne jedes mit einer Funktion zu umschließen. Und Webpack 3 ermöglichte eine solche Bündelung – mit der Modulverkettung. Die Modulverkettung bewirkt Folgendes:
// 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 erforderte Modul 0 render
aus Modul 1. Bei der Modulverkettung wird require
einfach durch die erforderliche Funktion ersetzt und Modul 1 entfernt. Das Bundle enthält weniger Module – und weniger Modul-Overhead!
Aktivieren Sie in Webpack 4 die Option optimization.concatenateModules
, um dieses Verhalten zu aktivieren:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true
}
};
Verwende in Webpack 3 den 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 Hochheben des Umfangs“
- Ausführliche Beschreibung der Funktionen dieses Plug-ins
Verwende externals
, wenn du sowohl Webpack- als auch Nicht-Webpack-Code hast
Vielleicht haben Sie ein großes Projekt, bei dem mit Webpack nur ein Teil des Codes kompiliert wurde. Wie bei einer Videohosting-Website, bei der das Player-Widget mit Webpack und die umgebende Seite möglicherweise nicht erstellt wurde:
Wenn beide Codeteile gemeinsame Abhängigkeiten haben, können Sie sie freigeben, um zu vermeiden, dass der Code mehrmals heruntergeladen wird. Dazu verwenden Sie die Option externals
des Webpacks. Sie ersetzt Module durch Variablen oder andere externe Importe.
Wenn Abhängigkeiten in window
verfügbar sind
Wenn der Nicht-Webpack-Code von Abhängigkeiten abhängig ist, die in window
als Variablen verfügbar sind, geben Sie Abhängigkeitsnamen einen Alias für Variablennamen:
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
Mit dieser Konfiguration bündelt Webpack keine react
- und react-dom
-Pakete. Stattdessen werden sie etwa 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 der Code, der nicht aus dem Webpack stammt, keine Abhängigkeiten in window
bereitstellt, ist die Sache komplizierter.
Sie können jedoch trotzdem vermeiden, denselben Code zweimal zu laden, wenn der Nicht-Webpack-Code diese Abhängigkeiten als AMD-Pakete verwendet.
Kompilieren Sie dazu den Webpack-Code als AMD-Bundle und Aliasmodule zu 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 packt das Bundle in define()
ein und macht es von diesen URLs abhängig:
// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });
Wenn der Nicht-Webpack-Code dieselben URLs zum Laden der Abhängigkeiten verwendet, werden diese Dateien nur einmal geladen. Zusätzliche Anfragen nutzen den Lade-Cache.
Weitere Informationen
- Webpack-Dokumentation für
externals
Zusammenfassung
- Produktionsmodus aktivieren, wenn Sie Webpack 4 verwenden
- Code mit den Komprimierungs- und Ladeprogrammoptionen auf Bundle-Ebene minimieren
- Entfernen Sie den Entwicklercode, indem Sie
NODE_ENV
durchproduction
ersetzen. - Baumschütteln mit ES-Modulen aktivieren
- Bilder komprimieren
- Abhängigkeitsspezifische Optimierungen anwenden
- Modulverkettung aktivieren
- Verwende
externals
, wenn dies für dich sinnvoll ist