Front-End-Größe verringern

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:

  1. Sie schreiben den Code so:

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. 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!');
    }
    
  3. 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

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:

  1. 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.');
    }
    
  2. 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

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:

  1. 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();
    
  2. 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 */
    })
    
  3. 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

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

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:

Screenshot einer Video-Hosting-Website
(Eine völlig zufällige Video-Hosting-Website)

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

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 durch production 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.