Sur webpack, comment puis-je importer un script sans l'évaluer?

9

Je travaille récemment sur des travaux d'optimisation de site Web, et je commence à utiliser le fractionnement de code dans le webpack en utilisant une instruction d'importation comme celle-ci:

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

Qui créent correctement le pageB-chunk.js , disons maintenant que je veux précharger ce morceau dans la pageA, je peux le faire en ajoutant cette déclaration dans la pageA:

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

Ce qui se traduira par une

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

étant ajouté à la tête de HTML, le navigateur le prélecture, jusqu'à présent tout va bien.

Le problème est que l' instruction d' importation que j'utilise ici non seulement prélecture du fichier js, mais évalue également le fichier js, signifie que le code de ce fichier js est analysé et compilé en bytecodes, le code de niveau supérieur de ce JS est exécuté.

C'est une opération qui prend beaucoup de temps sur un appareil mobile et je veux l'optimiser, je veux seulement la partie prefetch , je ne veux pas la partie évaluer et exécuter , parce que plus tard, lorsque certaines interactions utilisateur se produiront, je déclencherai l' analyse & m'évaluer

entrez la description de l'image ici

↑↑↑↑↑↑↑↑↑ Je veux seulement déclencher les deux premières étapes, les images proviennent de https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑↑ ↑↑

Bien sûr, je peux le faire en ajoutant moi-même le lien de prélecture, mais cela signifie que je dois savoir quelle URL je dois mettre dans le lien de prélecture, webpack connaît définitivement cette URL, comment puis-je l'obtenir à partir de webpack?

Webpack a-t-il un moyen simple d'y parvenir?

migcoder
la source
if (false) import(…)- Je doute que webpack fasse une analyse de code mort?
Bergi
Où / quand voulez - vous réellement évaluer le module? C'est là que le importcode dynamique devrait aller.
Bergi
Je suis tellement confus maintenant. Pourquoi l'évaluation est-elle importante? car enfin, le fichier JS doit être évalué par le périphérique du navigateur client. Ou je ne comprends pas la question correctement.
AmerllicA
@AmerllicA finalement oui, le js devrait être évalué, mais pensez à ce cas: mon site Web a A, B deux pages, les visiteurs de la page A visitent souvent la page B après avoir "fait quelques travaux" à la page A. Il est alors raisonnable de pré-lire la page B JS, mais si je peux contrôler le temps pendant lequel le JS de ce B est évalué, je peux être sûr à 100% de ne pas bloquer le fil principal qui crée des problèmes lorsque le visiteur essaie de "faire son travail" à la page A. Je peux évaluer Le JS de B après que le visiteur a cliqué sur un lien pointant vers la page B, mais à ce moment-là, le JS de B est probablement téléchargé, j'ai juste besoin de passer un peu de temps pour l'évaluer.
migcoder
Bien sûr, selon le blog de chrome v8: v8.dev/blog/cost-of-javascript-2019 , ils ont fait beaucoup d'optimisations pour atteindre le temps d'analyse JS extrêmement rapide, en utilisant le fil de travail et de nombreuses autres technologies, détails ici youtube.com / watch? v = D1UJgiG4_NI . Mais d'autres navigateurs n'implémentent pas encore une telle optimisation.
migcoder

Réponses:

2

MISE À JOUR

Vous pouvez utiliser preload-webpack-plugin avec html-webpack-plugin, il vous permettra de définir quoi précharger dans la configuration et il insérera automatiquement des balises pour précharger votre bloc

notez que si vous utilisez webpack v4 à partir de maintenant, vous devrez installer ce plugin en utilisant preload-webpack-plugin@next

exemple

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

Pour un projet générant deux scripts asynchrones avec des noms générés dynamiquement, tels que chunk.31132ae6680e598f8879.jset chunk.d15e7fdfc91b34bb78c4.js, les précharges suivantes seront injectées dans le documenthead

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

MISE À JOUR 2

si vous ne voulez pas précharger tous les morceaux asynchrones, mais seulement une fois que vous pouvez le faire aussi

soit vous pouvez utiliser le plugin babel de migcoder ou preload-webpack-plugincomme suit

  1. vous devrez d'abord nommer ce morceau asynchrone à l'aide d'un webpack magic commentexemple

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. puis dans la configuration du plugin, utilisez ce nom comme

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

Voyons tout d'abord le comportement du navigateur lorsque nous spécifions script balise ou une linkbalise pour charger le script

  1. chaque fois qu'un navigateur rencontre une scriptbalise, il la charge, l'analyse et l'exécute immédiatement
  2. vous pouvez uniquement retarder l'analyse et l'évaluation à l'aide de asyncet defertag uniquement jusqu'à l'DOMContentLoaded événement
  3. vous pouvez retarder l'exécution (évaluation) si vous n'insérez pas la balise de script (préchargez-la uniquement avec link )

maintenant, il existe une autre méthode de hackey non recommandée: expédiez l'intégralité de votre script et stringou comment(car le temps d'évaluation du commentaire ou de la chaîne est presque négligeable) et lorsque vous devez l'exécuter, vous pouvez utiliser Function() constructoroueval deux ne sont pas recommandés


Une autre approche des travailleurs du service: (cela préservera votre événement de cache après le rechargement de la page ou l'utilisateur se déconnecte après le chargement du cache)

Dans un navigateur moderne, vous pouvez utiliser service workerpour récupérer et mettre en cache un recours (JavaScript, image, css quoi que ce soit) et lorsque le thread principal demande ce recours, vous pouvez intercepter cette demande et renvoyer le recours du cache de cette façon, vous n'analysez pas et n'évaluez pas le script lorsque vous le chargez dans le cache en savoir plus sur les travailleurs des services ici

exemple

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

comme vous pouvez le voir, cela ne dépend pas du webpack , cela est hors de portée du webpack, mais avec l'aide de webpack, vous pouvez fractionner votre bundle, ce qui aidera à mieux utiliser le service worker

Tripurari Shankar
la source
mais mon problème est que je ne peux pas obtenir facilement l'URL du fichier à partir de webpack, même si je vais avec SW, je dois toujours faire savoir à SW quels fichiers doivent être pré-cache ... un plugin de manifeste webpack peut générer des informations de manifeste dans SW, mais c'est un tout en fonctionnement, signifie que SW n'a pas d'autre choix que de pré-mettre en cache tous les fichiers répertoriés dans le manifeste ...
migcoder
Idéalement, j'espère que webpack peut ajouter un autre commentaire magique comme / * webpackOnlyPrefetch: true * /, donc je peux appeler la déclaration d'importation deux fois pour chaque morceau chargeable paresseux, une pour la prélecture, une pour l'évaluation du code, et tout se passe à la demande.
migcoder
1
@migcoder qui est un point valide (vous ne pouvez pas obtenir le nom de fichier car il est généré dynamiquement lors de l'exécution) examinera toute solution si je peux en trouver
Tripurari Shankar
@migcoder J'ai mis à jour la réponse s'il vous plaît le voir qui résout votre problème
Tripurari Shankar
cela résout une partie du problème, il peut filtrer les morceaux asynchrones, c'est bien, mais mon objectif final est uniquement de récupérer les morceaux asynchrones demandés. Je regarde actuellement ce plugin github.com/sebastian-software/babel-plugin-smart-webpack-import , il me montre comment rassembler toutes les instructions d'importation et faire quelques modifications de code sur la base des commentaires magiques, peut-être que je peux créer un plugin similaire pour insérer le code de prélecture sur les instructions d'importation avec le commentaire magique «webpackOnlyPrefetch: true».
migcoder
1

Mises à jour: j'inclus toutes les choses dans un package npm, vérifiez-le! https://www.npmjs.com/package/webpack-prefetcher


Après quelques jours de recherche, je finis par écrire un plugin babel personnalisé ...

En bref, le plugin fonctionne comme ceci:

  • Rassemblez toutes les instructions d' importation (args) dans le code
  • Si l' importation (args) contient / * prefetch: true * / comment
  • Recherchez le chunkId dans l' instruction import ()
  • Remplacez-le par Prefetcher.fetch (chunkId)

Prefetcher est une classe d'assistance qui contient le manifeste de la sortie du webpack et peut nous aider à insérer le lien de prélecture:

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

Un exemple d'utilisation:

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

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

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

Le détail de ce plugin peut être trouvé ici:

Prefetch - Prenez le contrôle du webpack

Encore une fois, c'est une manière vraiment hacky et je n'aime pas ça, si vous voulez que l'équipe webpack implémente cela, veuillez voter ici:

Fonctionnalité: prélecture importation dynamique à la demande

migcoder
la source
0

En supposant que j'ai compris ce que vous essayez de réaliser, vous voulez analyser et exécuter un module après un événement donné (par exemple, cliquez sur un bouton). Vous pouvez simplement placer l'instruction import dans cet événement:

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