En webpack, ¿cómo puedo importar un script sin evaluarlo?

9

Recientemente estoy trabajando en algunos trabajos de optimización de sitios web, y empiezo a usar la división de código en el paquete web usando una declaración de importación como esta:

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

Que crean correctamente los páginaB-chunk.js , ahora digamos que quiero precargar este trozo en páginaA, puedo hacerlo por añadir esta declaración en páginaA:

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

Lo que resultará en un

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

si se agrega a la cabeza de HTML, el navegador lo captará previamente, hasta ahora todo bien.

El problema es que la declaración de importación que uso aquí no solo capta previamente el archivo js, ​​sino que también evalúa el archivo js, ​​significa que el código de ese archivo js se analiza y compila en bytecodes, se ejecuta el código de nivel superior de ese JS.

Esta es una operación que consume mucho tiempo en un dispositivo móvil y quiero optimizarla, solo quiero la parte de captación previa , no quiero la parte de evaluar y ejecutar , porque más tarde, cuando sucedan algunas interacciones del usuario, activaré el análisis y evaluarme

ingrese la descripción de la imagen aquí

↑↑↑↑↑↑↑↑ Solo quiero activar los primeros dos pasos, las imágenes provienen de https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑↑ ↑↑

Claro que puedo hacer esto agregando el enlace de captación previa yo mismo, pero esto significa que necesito saber qué URL debo poner en el enlace de captación previa, webpack definitivamente conoce esta URL, ¿cómo puedo obtenerla de webpack?

¿Webpack tiene alguna manera fácil de lograr esto?

migcoder
fuente
if (false) import(…)- Dudo que webpack haga análisis de código muerto?
Bergi
Donde / cuando no en realidad se desea evaluar el módulo? Ahí es donde importdebe ir el código dinámico .
Bergi
Estoy tan confundido ahora. ¿Por qué es importante la evaluación? porque finalmente, el archivo JS debe ser evaluado por el dispositivo del navegador del cliente. O no entiendo la pregunta correctamente.
AmerllicA
@AmerllicA eventualmente sí, se debe evaluar el js, pero piense en este caso: mi sitio web recibió A, B dos páginas, los visitantes en la página A a menudo visitan la página B después de "hacer algunos trabajos" en la página A. Luego es razonable buscar previamente las páginas B JS, pero si puedo controlar el tiempo en que se evalúa el JS de este B, puedo estar 100% seguro de que no bloqueo el hilo principal que crea fallas cuando el visitante está tratando de "hacer su trabajo" en la página A. Puedo evaluar El JS de B después de que el visitante haga clic en un enlace que apunta a la página B, pero en ese momento el JS de B probablemente se descargue, solo necesito pasar un poco de tiempo para evaluarlo.
migcoder
Claro, de acuerdo con el blog de chrome v8: v8.dev/blog/cost-of-javascript-2019 , hicieron muchas optimizaciones para lograr el increíble tiempo de análisis JS, utilizando el hilo de Worker y muchas otras tecnologías, detalles aquí youtube.com / watch? v = D1UJgiG4_NI . Pero otros navegadores aún no implementan dicha optimización.
migcoder

Respuestas:

2

ACTUALIZAR

Puede usar preload-webpack-plugin con html-webpack-plugin , le permitirá definir qué precargar en la configuración e insertará automáticamente etiquetas para precargar su fragmento

tenga en cuenta que si está utilizando webpack v4 a partir de ahora, tendrá que instalar este complemento utilizando preload-webpack-plugin@next

ejemplo

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

Para un proyecto que genera dos scripts asíncronos con nombres generados dinámicamente, como chunk.31132ae6680e598f8879.jsy chunk.d15e7fdfc91b34bb78c4.js, las siguientes precargas se inyectarán en el documentohead

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

ACTUALIZACIÓN 2

si no quieres precargar todo el fragmento asíncrono pero solo específico una vez que puedes hacerlo también

puede usar el complemento babel de migcoder o con los preload-webpack-pluginsiguientes

  1. primero tendrá que nombrar ese fragmento asíncrono con ayuda de webpack magic commentejemplo

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. y luego en la configuración del complemento use ese nombre como

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

En primer lugar, veamos el comportamiento del navegador cuando especificamos scriptetiqueta o linketiqueta para cargar el script

  1. cada vez que un navegador encuentre una scriptetiqueta, la cargará, la analizará y la ejecutará de inmediato
  2. solo puede retrasar el análisis y la evaluación con ayuda asyncy deferetiquetar solo hasta elDOMContentLoaded evento
  3. puede retrasar la ejecución (evaluación) si no inserta la etiqueta del script (solo precargue con link)

ahora hay alguna otra forma de hackey no recomendada : si envía su script completo stringo comment(porque el tiempo de evaluación del comentario o cadena es casi insignificante) y cuando necesita ejecutar eso, puede usarlo Function() constructoro evalno se recomiendan ambos


Otro trabajador de servicio de aproximación : (esto preservará su evento de caché después de recargar la página o el usuario se desconecta después de cargar la caché)

En el navegador moderno, puede usar service workerpara buscar y almacenar en caché un recurso (JavaScript, imagen, css cualquier cosa) y cuando la solicitud del hilo principal para ese recurso puede interceptar esa solicitud y devolver el recurso de la caché de esta manera, no está analizando y evaluando el script cuando lo está cargando en la memoria caché lea más sobre los trabajadores de servicios aquí

ejemplo

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

como puede ver, esto no depende del paquete web , está fuera del alcance del paquete web; sin embargo, con la ayuda del paquete web, puede dividir su paquete, lo que ayudará a utilizar mejor al trabajador del servicio

Tripurari Shankar
fuente
pero aún así mi problema es que no puedo obtener la URL del archivo fácilmente desde el paquete web, incluso si uso SW, todavía necesito que SW sepa qué archivos deben ser pre-caché ... un complemento de manifiesto del paquete web puede generar información de manifiesto en SW, pero está todo en funcionamiento, significa que SW no tiene otra opción que pre-cachear todos los archivos enumerados en el manifiesto ...
migcoder
Idealmente, espero que webpack pueda agregar otro comentario mágico como / * webpackOnlyPrefetch: true * /, por lo que puedo llamar a la declaración de importación dos veces por cada fragmento cargable perezoso, uno para prefetch, uno para evaluación de código, y todo sucede bajo demanda.
migcoder
1
@migcoder que es un punto válido (no se puede obtener el nombre de archivo porque se genera dinámicamente en tiempo de ejecución) buscará cualquier solución si puedo encontrar alguna
Tripurari Shankar
@migcoder He actualizado la respuesta, por favor, vea que resuelve su problema
Tripurari Shankar
resuelve parte del problema, puede filtrar los fragmentos asíncronos, eso es bueno, pero mi objetivo final es solo buscar previamente los fragmentos asincrónicos demandados. Actualmente estoy viendo este plugin github.com/sebastian-software/babel-plugin-smart-webpack-import , me muestra cómo reunir todas las declaraciones de importación y hacer algunas modificaciones en la base del código en los comentarios mágicos, tal vez pueda crear un complemento similar para insertar el código de captación previa en las declaraciones de importación con el comentario mágico 'webpackOnlyPrefetch: true'.
migcoder
1

Actualizaciones: ¡Incluyo todas las cosas en un paquete npm, échale un vistazo! https://www.npmjs.com/package/webpack-prefetcher


Después de unos días de investigación, termino escribiendo un complemento Babel personalizado ...

En resumen, el complemento funciona así:

  • Reúna todas las declaraciones de importación (args) en el código
  • Si la importación (args) contiene / * prefetch: true * / comment
  • Encuentre el chunkId de la instrucción import ()
  • Reemplácelo con Prefetcher.fetch (chunkId)

Prefetcher es una clase auxiliar que contiene el manifiesto de salida del paquete web y puede ayudarnos a insertar el enlace de captación previa:

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 ejemplo de uso:

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

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

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

El detalle de este complemento se puede encontrar aquí:

Prefetch: toma el control del paquete

Una vez más, esta es una forma realmente hacky y no me gusta, si quieres que el equipo webpack implemente esto, vota aquí:

Característica: captura previa importación dinámica bajo demanda

migcoder
fuente
0

Suponiendo que entendí lo que está tratando de lograr, desea analizar y ejecutar un módulo después de un evento determinado (por ejemplo, haga clic en un botón). Simplemente podría poner la declaración de importación dentro de ese evento:

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