Jak na paczce internetowej mogę zaimportować skrypt bez jego oceny?

9

Niedawno pracuję nad niektórymi pracami związanymi z optymalizacją witryny i zacząłem używać podziału kodu w pakiecie internetowym, używając instrukcji importu w następujący sposób:

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

Który prawidłowo stworzyć stronaB-chunk.js , teraz powiedzmy chcę wstępne pobieranie ten kawałek w Strona A, mogę to zrobić przez dodanie tego oświadczenia w Strona A:

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

Co spowoduje, że

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

dołączając do nagłówka HTML, przeglądarka go pobierze, jak dotąd tak dobrze.

Problem polega na tym , że używam tutaj instrukcji importu , nie tylko pobierając plik js, ale także oceniam plik js, co oznacza, że ​​kod tego pliku js jest analizowany i kompilowany do kodów bajtowych, wykonywany jest kod najwyższego poziomu tego JS.

Jest to bardzo czasochłonna operacja na urządzeniu mobilnym i chcę ją zoptymalizować, chcę tylko część pobierania wstępnego , nie chcę części oceniania i wykonywania , ponieważ później, gdy wystąpią pewne interakcje użytkownika, uruchomię analizę składniową i oceniam siebie

wprowadź opis zdjęcia tutaj

↑ ↑↑↑↑↑↑ Tylko chcę uruchomić dwa pierwsze kroki, zdjęcia pochodzą z https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑ ↑↑

Jasne, że mogę to zrobić, dodając link pobierania wstępnego osobiście, ale oznacza to, że muszę wiedzieć, który adres URL powinienem umieścić w łączu pobierania wstępnego, webpack zdecydowanie zna ten adres URL, jak mogę go uzyskać z pakietu internetowego?

Czy webpack ma łatwy sposób na osiągnięcie tego?

migcoder
źródło
if (false) import(…)- Wątpię, czy webpack analizuje martwy kod?
Bergi
Gdzie / kiedy nie rzeczywiście chcesz ocenić moduł? Tam importpowinien iść kod dynamiczny .
Bergi
Jestem teraz taki zmieszany. Dlaczego ocena jest ważna? ponieważ w końcu plik JS powinien zostać oceniony przez urządzenie przeglądarki klienta. Lub nie rozumiem poprawnie pytania.
AmerllicA
@AmerllicA ostatecznie tak, należy ocenić js, ale pomyśl o tym: moja witryna ma dwie strony A, B, odwiedzający stronę A często odwiedzają stronę B po „wykonaniu pewnych prac” na stronie A. W takim przypadku uzasadnione jest wcześniejsze pobranie strony B JS, ale jeśli mogę kontrolować czas, w którym JS tego B jest oceniany, jestem w 100% pewien, że nie blokuję głównego wątku, który powoduje usterki, gdy odwiedzający próbuje „wykonać swoje prace” na stronie A. Potrafię ocenić J's B po kliknięciu przez użytkownika linku prowadzącego do strony B, ale w tym czasie JS B jest najprawdopodobniej pobrany, potrzebuję tylko trochę czasu, aby go ocenić.
migcoder
Oczywiście, zgodnie z blogiem chrome v8: v8.dev/blog/cost-of-javascript-2019 , zrobili wiele optymalizacji, aby osiągnąć niesamowicie szybki czas analizy JS, wykorzystując wątek Worker i wiele innych technologii, szczegóły tutaj youtube.com / watch? v = D1UJgiG4_NI . Ale inne przeglądarki nie implementują jeszcze takiej optymalizacji.
migcoder

Odpowiedzi:

2

AKTUALIZACJA

Możesz użyć preload-webpack-plugin z html-webpack-plugin , pozwoli ci to określić, co ma zostać wstępnie załadowane w konfiguracji i automatycznie wstawi tagi, aby wstępnie załadować swoją porcję

Uwaga: jeśli używasz WebPack v4, będziesz musiał zainstalować tę wtyczkę za pomocą preload-webpack-plugin@next

przykład

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

W przypadku projektu generującego dwa skrypty asynchroniczne z dynamicznie generowanymi nazwami, takimi jak chunk.31132ae6680e598f8879.jsi chunk.d15e7fdfc91b34bb78c4.js, następujące wstępne obciążenia zostaną wstrzyknięte do dokumentuhead

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

AKTUALIZACJA 2

jeśli nie chcesz wstępnie ładować całego fragmentu asynchronicznego, ale tylko określony, gdy możesz to zrobić

możesz użyć wtyczki babcodera migcodera lub wykonaćpreload-webpack-plugin następujące czynności

  1. najpierw będziesz musiał nazwać ten fragment asynchroniczny za pomocą webpack magic commentprzykładu

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. a następnie w konfiguracji wtyczki użyj tej nazwy jak

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

Przede wszystkim zobaczmy zachowanie przeglądarki, gdy określimy scripttag lub linktag, aby załadować skrypt

  1. za każdym razem, gdy przeglądarka napotka scriptznacznik, załaduje go, przeanalizuje i natychmiast wykona
  2. możesz jedynie opóźnić parsowanie i ocenę za pomocą tagu asynci tylko do zdarzeniadeferDOMContentLoaded
  3. możesz opóźnić wykonanie (ocenę), jeśli nie wstawisz znacznika skryptu (tylko wstępnie go załaduj link)

Teraz istnieje jakiś inny nie zaleca hackey sposobem jest transportowanie cały scenariusz i stringalbo comment(bo czas oceny z komentarzem lub ciąg jest znikomy) i gdy trzeba wykonać, że można użyć Function() constructoralbo evalobie nie są zalecane


Inne podejście do usług pracowników: (pozwoli to zachować pamięć podręczną po ponownym załadowaniu strony lub użytkownik przejdzie w tryb offline po załadowaniu pamięci podręcznej)

W nowoczesnej przeglądarce możesz użyć service workerdo pobrania i buforowania odwołania (JavaScript, obraz, css cokolwiek), a gdy żądanie głównego wątku dla tego odwołania możesz przechwycić to żądanie i zwrócić odwołanie z pamięci podręcznej w ten sposób, nie analizujesz i nie oceniasz skryptu, gdy ładujesz go do pamięci podręcznej. Dowiedz się więcej o pracownikach serwisu tutaj

przykład

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
      });
    }
  }));
});

jak widać, nie jest to zależne od pakietu WebPack, nie wchodzi ono w zakres pakietu WebPack, jednak za pomocą pakietu WebPack można podzielić pakiet, co pomoże lepiej wykorzystać pracownika serwisu

Tripurari Shankar
źródło
ale nadal mam problem z tym, że nie mogę łatwo pobrać adresu URL pliku z paczki internetowej, nawet jeśli korzystam z oprogramowania SW, nadal muszę poinformować SW, jakie pliki powinny mieć pamięć podręczną ... wtyczka manifestu pakietu internetowego może generować informacje o pliku manifestu do SW, ale wszystko działa, oznacza, że ​​SW nie ma wyboru, musi buforować wszystkie pliki wymienione w manifeście ...
migcoder
Idealnie, mam nadzieję, że webpack może dodać kolejny magiczny komentarz, taki jak / * webpackOnlyPrefetch: true * /, więc mogę wywołać instrukcję importu dwa razy dla każdego leniwego fragmentu, który można załadować, jeden do pobrania wstępnego, jeden do oceny kodu i wszystko dzieje się na żądanie.
migcoder
1
@migcoder, który jest prawidłowym punktem (nie można uzyskać nazwy pliku, ponieważ jest on generowany dynamicznie w czasie wykonywania), sprawdzi każde rozwiązanie, jeśli uda mi się je znaleźć
Tripurari Shankar
@migcoder Zaktualizowałem odpowiedź, proszę zobaczyć, która rozwiązuje problem
Tripurari Shankar
rozwiązuje część problemu, może odfiltrować fragmenty asynchroniczne, co jest dobre, ale moim ostatecznym celem jest tylko wstępne pobranie żądanych fragmentów asynchronicznych. Obecnie patrzę na tę wtyczkę github.com/sebastian-software/babel-plugin-smart-webpack-import , pokazuje mi, jak zebrać wszystkie instrukcje importu i dokonać modyfikacji kodu w oparciu o magiczne komentarze, może uda mi się stworzyć podobna wtyczka do wstawiania kodu pobierania wstępnego w instrukcjach importu z magicznym komentarzem „webpackOnlyPrefetch: true”.
migcoder
1

Aktualizacje: dołączam wszystkie rzeczy do pakietu npm, sprawdź to! https://www.npmjs.com/package/webpack-prefetcher


Po kilku dniach badań kończę pisanie dostosowanej wtyczki babel ...

Krótko mówiąc, wtyczka działa w następujący sposób:

  • Zbierz wszystkie instrukcje importu (args) w kodzie
  • Jeśli import (args) zawiera / * prefetch: true * / comment
  • Znajdź chunkId z () import wyciągu
  • Zamień go na Prefetcher.fetch (chunkId)

Prefetcher to klasa pomocnicza, która zawiera manifest danych wyjściowych webpacka i może nam pomóc w wstawieniu linku pobierania wstępnego:

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)
  }
}

Przykład użycia:

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

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

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

Szczegółowy opis tej wtyczki można znaleźć tutaj:

Pobieranie wstępne - przejmij kontrolę z pakietu internetowego

Ponownie, jest to naprawdę hacking sposób i nie podoba mi się, jeśli chcesz, aby zespół webpack to zaimplementował, proszę zagłosuj tutaj:

Funkcja: pobranie dynamiczne importu na żądanie

migcoder
źródło
0

Zakładając, że rozumiem, co próbujesz osiągnąć, chcesz przeanalizować i wykonać moduł po danym zdarzeniu (np. Kliknąć przycisk). Możesz po prostu umieścić instrukcję importu w tym zdarzeniu:

element.addEventListener('click', async () => {
  const module = await import("...");
});
Eliya Cohen
źródło