Front-End-Größe verringern

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:

  1. Sie schreiben den Code so:

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

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:

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

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:

  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 (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

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

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

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:

Screenshot einer Video-Hosting-Website
(Eine zufällig ausgewählte Website zum Hosten von Videos)

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

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