Langfristiges Caching nutzen

So unterstützt Webpack das Asset-Caching

Als Nächstes (nach der Optimierung der App-Größe um die Ladezeit der App im Cache zu verbessern. Hiermit können Sie dafür sorgen, dass Teile der App und nicht jedes Mal wieder herunterladen.

Bundle-Versionsverwaltung und Cache-Header verwenden

Der gängige Ansatz beim Caching ist:

  1. den Browser anweisen, eine Datei sehr lange (z.B. ein Jahr) im Cache zu speichern:

    # Server header
    Cache-Control: max-age=31536000
    

    Wenn Sie nicht wissen, was Cache-Control tut, sehen Sie sich die hervorragender Post zum Caching .

  2. und benennen Sie die Datei nach der Änderung um, um den erneuten Download zu erzwingen:

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

Dadurch wird der Browser angewiesen, die JS-Datei herunterzuladen, im Cache zu speichern und im Cache gespeicherter Kopie. Der Browser trifft nur dann auf das Netzwerk, wenn sich der Dateiname ändert (oder nach Ablauf eines Jahres).

Mit Webpack tun Sie dasselbe, aber Sie geben anstelle einer Versionsnummer die Datei-Hash. Um den Hash in den Dateinamen aufzunehmen, verwenden Sie [chunkhash]:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

Wenn Sie die Dateinamen, um ihn an den Client zu senden, entweder HtmlWebpackPlugin oder den WebpackManifestPlugin.

Das HtmlWebpackPlugin ist ein aber weniger flexibel ist. Während der Kompilierung generiert dieses Plug-in ein HTML-Datei, die alle kompilierten Ressourcen enthält. Wenn Ihre Serverlogik nicht ist, sollte es für Sie ausreichen:

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

Die WebpackManifestPlugin ist ein flexiblerer Ansatz, der bei einer komplexen Serverkomponente nützlich ist. Während des Builds wird eine JSON-Datei mit einer Zuordnung zwischen den Dateinamen generiert. ohne Hash und Dateinamen mit Hash. Verwenden Sie diesen JSON-Code auf dem Server, um mit welcher Datei Sie arbeiten möchten:

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

Weitere Informationen

Abhängigkeiten und Laufzeit in eine separate Datei extrahieren

Abhängigkeiten

App-Abhängigkeiten ändern sich tendenziell seltener als der tatsächliche App-Code. Wenn du umziehst in einer separaten Datei speichern, kann der Browser sie separat im Cache speichern. Sie werden nicht jedes Mal neu heruntergeladen, wenn sich der App-Code ändert.

Führen Sie drei Schritte aus, um Abhängigkeiten in einen separaten Block zu extrahieren:

  1. Ersetzen Sie den Namen der Ausgabedatei durch [name].[chunkname].js:

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    Wenn die App mit Webpack erstellt wird, ersetzt es [name] mit dem Namen eines Chunks. Ohne [name]-Teil erhalten wir Blöcke anhand ihrer Hashwerte zu unterscheiden. Das ist ziemlich schwierig!

  2. Konvertieren Sie das Feld entry in ein Objekt:

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    In diesem Snippet ist der Name eines Chunks. Dieser Name wird in anstelle von [name] aus Schritt 1.

    Wenn Sie die App erstellt haben, enthält dieser Block mittlerweile den gesamten App-Code. wie wir diese Schritte nicht gemacht haben. Aber das ändert sich in einer Sekunde.

  3. Füge in Webpack 4 die Option optimization.splitChunks.chunks: 'all' hinzu in Ihre Webpack-Konfiguration einfügen:

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    Mit dieser Option wird die intelligente Codeaufteilung aktiviert. Damit würde Webpack den Anbietercode extrahieren, wird sie größer als 30 KB (vor der Reduzierung und GZIP). Außerdem würde der Standardcode extrahiert werden, Dies ist nützlich, wenn Ihr Build mehrere Bundles (z.B. wenn Sie Ihre App in Routen aufteilen.

    Füge in Webpack 3 das CommonsChunkPlugin hinzu:

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    Dieses Plug-in verwendet alle Module, bei denen Pfade node_modules und werden sie in eine separate Datei namens vendor.[chunkhash].js verschoben.

Nach diesen Änderungen generiert jeder Build zwei Dateien statt einer: main.[chunkhash].js und vendor.[chunkhash].js (vendors~main.[chunkhash].js für Webpack 4). Bei Webpack 4 Bei kleinen Abhängigkeiten wird das Anbieterpaket möglicherweise nicht generiert. Das ist in Ordnung:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

Der Browser speichert diese Dateien separat im Cache und lädt nur Code noch einmal herunter, der sich ändert.

Webpack-Laufzeitcode

Leider reicht es nicht aus, nur den Anbietercode zu extrahieren. Wenn Sie versuchen, App-Code ändern:

// index.js
…
…

// E.g. add this:
console.log('Wat');

werden Sie feststellen, dass sich auch der Hash vendor ändert:

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

Dies liegt daran, dass das Webpack-Bundle neben dem Code der Module über einen Runtime – ein kleines Code-Snippet der die Modulausführung verwaltet. Wenn Sie den Code in mehrere Dateien aufteilen, beginnt dieser Code mit einer Zuordnung zwischen den Block-IDs und entsprechenden Dateien:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack fügt diese Laufzeit in den letzten generierten Block ein, nämlich vendor in unserem Fall. Jedes Mal, wenn Chunk-Änderungen geändert werden, ändert sich auch dieses Code-Snippet, Dadurch ändert sich der gesamte Block vendor.

Um dieses Problem zu lösen, verschieben wir die Laufzeit in eine separate Datei. In Webpack 4: durch Aktivieren der Option optimization.runtimeChunk erreicht:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    runtimeChunk: true
  }
};

Erstellen Sie dazu in Webpack 3 einen zusätzlichen leeren Block mit dem CommonsChunkPlugin:

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

Nach diesen Änderungen generiert jeder Build drei Dateien:

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

Fügen Sie sie in umgekehrter Reihenfolge unter index.html ein. Damit sind Sie fertig:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

Weitere Informationen

Inline-Webpack-Laufzeit zum Speichern einer zusätzlichen HTTP-Anfrage

Zur Verbesserung kannst du die Webpack-Laufzeit in den HTML-Code aufnehmen. Antwort. Anstelle von:

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

Gehen Sie wie folgt vor:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

Da die Laufzeit klein ist, können Sie HTTP-Anfragen speichern, wichtig bei HTTP/1; bei HTTP/2 weniger wichtig ist, aber trotzdem Effekt).

Und so geht's!

Wenn Sie HTML mit dem HTMLWebpackPlugin generieren

Wenn Sie die Methode HtmlWebpackPlugin zum Generieren der eine HTML-Datei enthält, InlineSourcePlugin ist alles, was Sie brauchen:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

Wenn Sie HTML mithilfe einer benutzerdefinierten Serverlogik generieren

Mit Webpack 4:

  1. Fügen Sie den WebpackManifestPlugin finden Sie den generierten Namen des Laufzeit-Chunks:

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    Ein Build mit diesem Plug-in würde eine Datei erstellen, die so aussieht:

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. Fügen Sie den Inhalt des Laufzeit-Chunks auf eine bequeme Weise ein. Beispiel: mit Node.js und Express:

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

Oder mit Webpack 3:

  1. Machen Sie den Laufzeitnamen statisch, indem Sie filename angeben:

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. Fügen Sie den runtime.js-Inhalt auf eine bequeme Weise ein. Beispiel: mit Node.js und Express:

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

Lazy-Loading-Code, den Sie gerade nicht benötigen

Manchmal besteht eine Seite aus mehr und weniger wichtigen Teilen:

  • Wenn Sie eine Videoseite auf YouTube laden, ist das Video für Sie wichtiger als das Video. Kommentare. Hier ist das Video wichtiger als Kommentare.
  • Wenn Sie einen Artikel auf einer Nachrichten-Website öffnen, ist Ihnen der Text des als über Anzeigen. Hier ist der Text wichtiger als die Anzeigen.

Verbessern Sie in diesen Fällen die anfängliche Ladeleistung, indem Sie nur die die wichtigsten Elemente zuerst und das Lazy Loading für die verbleibenden Teile später. Verwenden Sie das import()-Funktion und code-splitting dafür:

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import() gibt an, dass ein bestimmtes Modul dynamisch geladen werden soll. Wann? Webpack erkennt import('./module.js') und verschiebt dieses Modul in ein chunk:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

und lädt sie erst herunter, wenn die Ausführung die Funktion import() erreicht.

Dadurch wird das main-Bundle kleiner, was die anfängliche Ladezeit verkürzt. Umso mehr wird das Caching verbessert – wenn Sie den Code im Haupt-Chunk ändern, Dies hat keinen Einfluss auf den Kommentar-Chunk.

Weitere Informationen

Code in Routen und Seiten aufteilen

Wenn Ihre App mehrere Routen oder Seiten umfasst, aber nur eine einzige JS-Datei mit (ein einzelner main-Chunk) haben, stellen Sie wahrscheinlich zusätzliche Bytes für jede Anfrage. Angenommen, ein Nutzer besucht eine Startseite Ihrer Website:

Eine WebFundamentals-Startseite

müssen sie den Code zum Rendern eines Artikels nicht laden, Seite, aber sie wird geladen. Wenn ein Nutzer immer nur das Zuhause besucht, und Sie eine Änderung am Artikelcode vornehmen, wird das Webpack gesamtes Bundle, und der Nutzer muss die ganze App noch einmal herunterladen.

Wenn wir die App in Seiten (oder Routen, bei einer App mit nur einer Seite) aufteilen, wird nur der relevante Code heruntergeladen. Außerdem speichert der Browser den App-Code im Cache. besser: Wenn Sie den Startseitencode ändern, entwertet Webpack nur die entsprechenden Block aus.

Für Apps mit nur einer Seite

Um Single-Page-Anwendungen nach Routen aufzuteilen, verwenden Sie import() (siehe Lazy-Loading-Code die Sie im Moment nicht benötigen.“ Wenn Sie ein Framework verwenden, könnte es bereits eine Lösung dafür geben:

Für herkömmliche mehrseitige Apps

Um herkömmliche Apps nach Seiten aufzuteilen, verwenden Sie den Eintrag von Webpack. Punkte. Wenn Ihre App über drei z. B. die Startseite, die Artikelseite und die Kontoseite. sollte drei Einträge haben:

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

Für jede Eintragsdatei erstellt Webpack eine separate Abhängigkeitsstruktur und generiert Ein Bundle, das nur Module enthält, die von diesem Eintrag verwendet werden:

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

Wenn also nur auf der Artikelseite Lodash, die Sets home und profile verwendet werden ist nicht enthalten – und der Nutzer muss diese Bibliothek nicht herunterladen, auf die Startseite geleitet.

Separate Abhängigkeitsstrukturen haben jedoch auch ihre Nachteile. Wenn zwei Einstiegspunkte und Sie haben Ihre Abhängigkeiten nicht in ein Anbieter-Bundle verschoben, Die Punkte enthalten eine Kopie von Lodash. Fügen Sie in Webpack 4 den optimization.splitChunks.chunks: 'all' in die Webpack-Konfiguration ein:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

Diese Option ermöglicht die intelligente Codeaufteilung. Mit dieser Option würde Webpack automatisch und extrahieren Sie ihn in separate Dateien.

Alternativ kannst du in Webpack 3 das CommonsChunkPlugin verwenden. – werden gängige Abhängigkeiten in eine neue angegebene Datei verschoben:

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

Sie können gerne mit dem Wert von minChunks experimentieren, um die beste zu finden. Im Allgemeinen sollten Sie sie klein halten, aber erhöhen, wenn die Anzahl der Blöcke zunimmt. Für Bei drei Blöcken könnte minChunks beispielsweise 2 sein, aber bei 30 Blöcken könnte es 8 sein. Denn bei 2 laufen zu viele Module in der gemeinsamen Datei, zu viel aufblasen.

Weitere Informationen

Modul-IDs stabiler machen

Beim Erstellen des Codes weist Webpack jedem Modul eine ID zu. Später werden diese IDs im Bundle in require() Sekunden verwendet. Die Build-Ausgabe enthält normalerweise IDs direkt vor den Modulpfaden ein:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ Hier

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

Standardmäßig werden IDs mit einem Zähler berechnet (d.h. das erste Modul hat die ID 0, hat die zweite die ID 1 usw. Das Problem dabei ist, dass Sie eines neuen Moduls erscheint, könnte es in der Mitte der Modulliste erscheinen und alle nächste Module IDs:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ Wir haben ein neues Modul...

[4] ./webPlayer.js 24 kB {1} [built]

↓ Und sieh mal, wie das funktioniert! comments.js hat jetzt die ID 5 statt 4

[5] ./comments.js 58 kB {0} [built]

ads.js hat jetzt ID 6 statt 5

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

Dadurch werden alle Blöcke ungültig, die Module mit geänderten IDs enthalten oder von diesen abhängig sind. auch wenn sich ihr Code nicht geändert hat. In unserem Fall ist der Chunk 0 mit comments.js) und der Block main (der Teil mit dem anderen App-Code) erhalten ungültig gemacht werden, während nur die main hätte sein sollen.

Um dies zu beheben, ändern Sie die Berechnung von Modul-IDs mithilfe der HashedModuleIdsPlugin Sie ersetzt zählerbasierte IDs durch Hashes der Modulpfade:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ Hier

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

Bei diesem Ansatz ändert sich die ID eines Moduls nur, wenn Sie das Modul umbenennen oder verschieben -Modul. Neue Module haben keine Auswirkungen auf die IDs.

Fügen Sie das Plug-in zum Abschnitt plugins der Konfiguration hinzu, um es zu aktivieren:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

Weitere Informationen

Zusammenfassung

  • Bundle im Cache speichern und durch Ändern des Bundle-Namens zwischen Versionen unterscheiden
  • Paket in App-Code, Anbietercode und Laufzeit aufteilen
  • Laufzeit zum Speichern einer HTTP-Anfrage inline einbinden
  • Lazy Loading von nicht kritischem Code mit import
  • Code nach Routen/Seiten aufteilen, um unnötiges Laden zu vermeiden