Wie kann ich im Webpack ein Skript importieren, ohne es auszuwerten?

9

Ich arbeite kürzlich an einigen Arbeiten zur Website-Optimierung und beginne mit der Code-Aufteilung im Webpack, indem ich die folgende import-Anweisung verwende:

import(/* webpackChunkName: 'pageB-chunk' */ './pageB')

Was die pageB-chunk.js korrekt erstellt, sagen wir jetzt, ich möchte diesen Chunk in pageA vorab abrufen. Ich kann dies tun, indem ich diese Anweisung in pageA hinzufüge:

import(/* webpackChunkName: 'pageB-chunk' */ /* webpackPrefetch: true */ './pageB')

Was zu einem führen wird

<link rel="prefetch" href="pageB-chunk.js">

Wenn der Browser an den Kopf von HTML angehängt wird, wird er vom Browser vorab abgerufen, soweit so gut.

Das Problem ist die Importanweisung , die ich hier verwende, um nicht nur die js-Datei vorab abzurufen, sondern auch die js-Datei auszuwerten. Dies bedeutet, dass der Code dieser js-Datei analysiert und zu Bytecodes kompiliert wird. Der Code der obersten Ebene dieses JS wird ausgeführt.

Dies ist ein sehr zeitaufwändiger Vorgang auf einem mobilen Gerät, und ich möchte ihn optimieren. Ich möchte nur den Prefetch- Teil und nicht den Evaluate & Execute- Teil, da ich später, wenn einige Benutzerinteraktionen stattfinden, die Analyse auslösen werde & bewerte mich

Geben Sie hier die Bildbeschreibung ein

↑↑↑↑↑↑↑↑ Ich möchte nur die ersten beiden Schritte auslösen. Die Bilder stammen von https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

Natürlich kann ich dies tun, indem ich den Prefetch-Link selbst hinzufüge, aber dies bedeutet, dass ich wissen muss, welche URL ich in den Prefetch-Link einfügen soll. Webpack kennt diese URL definitiv. Wie kann ich sie aus dem Webpack erhalten?

Hat Webpack eine einfache Möglichkeit, dies zu erreichen?

Migcoder
quelle
if (false) import(…)- Ich bezweifle, dass Webpack eine Dead-Code-Analyse durchführt.
Bergi
Wo / wann Sie möchten , dass Sie tatsächlich das Modul bewerten? Dort sollte der dynamische importCode hingehen.
Bergi
Ich bin jetzt so verwirrt. Warum ist die Bewertung wichtig? denn endlich sollte die JS-Datei vom Client-Browser-Gerät ausgewertet werden. Oder ich verstehe die Frage nicht richtig.
AmerllicA
@AmerllicA schließlich ja, die js sollten ausgewertet werden, aber denken Sie an diesen Fall: Meine Website hat A, B zwei Seiten, Besucher auf Seite A besuchen häufig Seite B, nachdem sie auf Seite A "einige Arbeiten erledigt" haben. Dann ist es sinnvoll, Seite B vorab abzurufen JS, aber wenn ich die Zeit steuern kann, zu der das JS dieses B ausgewertet wird, kann ich zu 100% sicher sein, dass ich den Haupt-Thread nicht blockiere, der Störungen verursacht, wenn der Besucher versucht, "seine Arbeiten zu erledigen" auf Seite A. Ich kann auswerten B's JS Nachdem der Besucher auf einen Link geklickt hat, der auf Seite B verweist, aber zu diesem Zeitpunkt wird das B's JS höchstwahrscheinlich heruntergeladen. Ich brauche nur ein wenig Zeit, um es zu bewerten.
Migcoder
Laut dem Blog von Chrome V8: v8.dev/blog/cost-of-javascript-2019 haben sie viele Optimierungen vorgenommen, um die blitzschnelle JS- Analysezeit zu erreichen, indem sie den Worker-Thread und viele andere Technologien verwendet haben. Details hier auf youtube.com / watch? v = D1UJgiG4_NI . Andere Browser implementieren eine solche Optimierung jedoch noch nicht.
Migcoder

Antworten:

2

AKTUALISIEREN

Sie können das Preload-Webpack-Plugin mit dem HTML-Webpack-Plugin verwenden , mit dem Sie definieren können, was in der Konfiguration vorgeladen werden soll, und automatisch Tags einfügen, um Ihren Chunk vorzuladen

Hinweis: Wenn Sie ab sofort Webpack v4 verwenden, müssen Sie dieses Plugin mit installieren preload-webpack-plugin@next

Beispiel

plugins: [
  new HtmlWebpackPlugin(),
  new PreloadWebpackPlugin({
    rel: 'preload',
    include: 'asyncChunks'
  })
]

Bei einem Projekt, das zwei asynchrone Skripte mit dynamisch generierten Namen wie chunk.31132ae6680e598f8879.jsund generiert chunk.d15e7fdfc91b34bb78c4.js, werden die folgenden Vorladungen in das Dokument eingefügthead

<link rel="preload" as="script" href="chunk.31132ae6680e598f8879.js">
<link rel="preload" as="script" href="chunk.d15e7fdfc91b34bb78c4.js">

UPDATE 2

Wenn Sie nicht alle asynchronen Blöcke vorladen möchten, sondern nur bestimmte, können Sie dies auch tun

Entweder können Sie das Babel-Plugin von Migcoder verwenden oder preload-webpack-pluginwie folgt

  1. Zuerst müssen Sie diesen asynchronen Block anhand eines webpack magic commentBeispiels benennen

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. und dann in der Plugin-Konfiguration diesen Namen wie verwenden

    plugins: [
      new HtmlWebpackPlugin(),   
      new PreloadWebpackPlugin({
        rel: 'preload',
        include: ['myAsyncPreloadChunk']
      }) 
    ]

Lassen Sie uns zunächst das Verhalten des Browsers sehen, wenn wir scriptTag oder linkTag zum Laden des Skripts angeben

  1. Wenn ein Browser auf ein scriptTag stößt , lädt er es, analysiert es und führt es sofort aus
  2. Sie können das Parsen und Auswerten nur mit Hilfe von asyncund deferTag nur bis zumDOMContentLoaded Ereignis verzögern
  3. Sie können die Ausführung (Auswertung) verzögern, wenn Sie das Skript-Tag nicht einfügen (nur mit vorladen link).

Jetzt gibt es eine andere nicht empfohlene Art und Weise, wie Sie Ihr gesamtes Skript versenden und stringoder comment(da die Auswertungszeit für Kommentare oder Zeichenfolgen fast vernachlässigbar ist) und wenn Sie eine Ausführung benötigen, die Sie verwenden können Function() constructoroder evalbeide nicht empfohlen werden


Ein anderer Approach Service Worker: (Dadurch bleibt das Cache-Ereignis nach dem erneuten Laden der Seite erhalten oder der Benutzer wird nach dem Laden des Caches offline geschaltet.)

In modernen Browsern können Sie service workereinen Rückgriff (JavaScript, Bild, CSS) abrufen und zwischenspeichern. Wenn der Hauptthread diesen Rückgriff anfordert, können Sie diese Anforderung abfangen und den Rückgriff aus dem Cache zurückgeben, sodass Sie das Skript nicht analysieren und auswerten, wenn Sie laden sie in den Cache mehr über Service - Mitarbeiter lesen hier

Beispiel

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).then(function(response) {
    // caches.match() always resolves
    // but in case of success response will have value
    if (response !== undefined) {
      return response;
    } else {
      return fetch(event.request).then(function (response) {
        // response may be used only once
        // we need to save clone to put one copy in cache
        // and serve second one
        let responseClone = response.clone();

        caches.open('v1').then(function (cache) {
          cache.put(event.request, responseClone);
        });
        return response;
      }).catch(function () {
        // any fallback code here
      });
    }
  }));
});

Wie Sie sehen können, ist dies keine Webpack-abhängige Sache. Dies liegt außerhalb des Geltungsbereichs von Webpack. Mithilfe von Webpack können Sie jedoch Ihr Bundle aufteilen, um den Service Worker besser zu nutzen

Tripurari Shankar
quelle
Mein Schmerzpunkt ist jedoch, dass ich die URL der Datei nicht einfach aus dem Webpack abrufen kann. Selbst wenn ich mit SW arbeite, muss ich SW wissen lassen, welche Dateien vor dem Cache gespeichert werden sollen. Ein Webpack-Manifest-Plugin kann Manifest-Informationen generieren SW, aber es ist alles in Betrieb, bedeutet, dass SW keine andere Wahl hat, als alle im Manifest aufgelisteten Dateien
vorzuspeichern
Im Idealfall hoffe ich, dass das Webpack einen weiteren magischen Kommentar wie / * webpackOnlyPrefetch: true * / hinzufügen kann, sodass ich die import-Anweisung zweimal für jeden faulen ladbaren Block aufrufen kann, einen für den Prefetch, einen für die Code-Auswertung, und alles geschieht bei Bedarf.
Migcoder
1
@migcoder, der ein gültiger Punkt ist (Sie können keinen Dateinamen erhalten, da dieser zur Laufzeit dynamisch generiert wird), wird nach einer Lösung suchen, wenn ich eine finde
Tripurari Shankar
@migcoder ich die Antwort aktualisiert sehen Sie es bitte , dass Ihr Problem löst
Tripurari Shankar
Es löst einen Teil des Problems. Es kann die asynchronen Blöcke herausfiltern, die gut sind. Mein letztes Ziel ist jedoch, nur die erforderlichen asynchronen Blöcke vorab abzurufen. Ich schaue mir gerade dieses Plugin an. Github.com/sebastian-software/babel-plugin-smart-webpack-import . Es zeigt mir, wie ich alle Importanweisungen sammle und einige Codeänderungen basierend auf den magischen Kommentaren vornehme. Vielleicht kann ich sie erstellen Ein ähnliches Plugin zum Einfügen des Prefetch-Codes in Importanweisungen mit dem magischen Kommentar 'webpackOnlyPrefetch: true'.
Migcoder
1

Updates: Ich füge alle Dinge in ein npm-Paket ein, schau es dir an! https://www.npmjs.com/package/webpack-prefetcher


Nach ein paar Tagen Recherche schreibe ich ein benutzerdefiniertes Babel-Plugin ...

Kurz gesagt, das Plugin funktioniert folgendermaßen:

  • Sammeln Sie alle Importanweisungen (args) im Code
  • Wenn der Import (args) / * prefetch enthält: true * / comment
  • Suchen Sie die chunkId in der import () - Anweisung
  • Ersetzen Sie es durch Prefetcher.fetch (chunkId)

Prefetcher ist eine Hilfsklasse, die das Manifest der Webpack-Ausgabe enthält und uns beim Einfügen des Prefetch-Links helfen kann:

export class Prefetcher {
  static manifest = {
    "pageA.js": "/pageA.hash.js",
    "app.js": "/app.hash.js",
    "index.html": "/index.html"
  }
  static function fetch(chunkId) {
    const link = document.createElement('link')
    link.rel = "prefetch"
    link.as = "script"
    link.href = Prefetcher.manifest[chunkId + '.js']
    document.head.appendChild(link)
  }
}

Ein Anwendungsbeispiel:

const pageAImporter = {
  prefetch: () => import(/* prefetch: true */ './pageA.js')
  load: () => import(/* webpackChunkName: 'pageA' */ './pageA.js')
}

a.onmousehover = () => pageAImporter.prefetch()

a.onclick = () => pageAImporter.load().then(...)

Das Detail dieses Plugins finden Sie hier:

Prefetch - Übernehmen Sie die Kontrolle über das Webpack

Auch dies ist ein sehr hackiger Weg und ich mag es nicht. Wenn Sie möchten, dass das Webpack-Team dies implementiert, stimmen Sie bitte hier ab:

Feature: Dynamischer Import bei Bedarf vorab abrufen

Migcoder
quelle
0

Angenommen, ich habe verstanden, was Sie erreichen möchten, möchten Sie ein Modul nach einem bestimmten Ereignis analysieren und ausführen (z. B. auf eine Schaltfläche klicken). Sie können einfach die import-Anweisung in dieses Ereignis einfügen:

element.addEventListener('click', async () => {
  const module = await import("...");
});
Eliya Cohen
quelle