No webpack, como posso importar um script sem avaliá-lo?

9

Recentemente, estou trabalhando em algumas obras de otimização de sites e começo a usar a divisão de código no webpack usando a instrução import como esta:

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

Que criar corretamente os PAGEB-chunk.js , agora vamos dizer que eu quero prefetch este pedaço em pageA, eu posso fazer isso por adicionar essa declaração em pageA:

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

O que resultará em uma

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

sendo anexado à cabeça do HTML, o navegador irá buscá-lo até o momento.

O problema é a instrução de importação que eu uso aqui, não apenas pré-busca o arquivo js, ​​mas também avalia o arquivo js, ​​significa que o código desse arquivo js é analisado e compilado com bytecodes, o código de nível superior desse JS é executado.

Esta é uma operação muito demorada em um dispositivo móvel e eu quero otimizá-la, quero apenas a parte de pré - busca , não quero a parte de avaliar e executar , porque mais tarde, quando ocorrerem algumas interações do usuário, acionarei a análise e me avaliar

insira a descrição da imagem aqui

↑↑↑↑↑↑↑↑ Só quero acionar os dois primeiros passos, as imagens são de https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

Claro que posso fazer isso adicionando o link de pré-busca, mas isso significa que eu preciso saber qual URL devo colocar no link de pré-busca. O webpack definitivamente conhece esse URL, como posso obtê-lo no webpack?

O webpack tem alguma maneira fácil de conseguir isso?

migcoder
fonte
if (false) import(…)- Duvido que o webpack faça análise de código morto?
Bergi 30/01
Onde / quando não você realmente quer avaliar o módulo? É para onde o importcódigo dinâmico deve ir.
Bergi 30/01
Estou tão confuso agora. Por que a avaliação é importante? porque, finalmente, o arquivo JS deve ser avaliado pelo dispositivo do navegador do cliente. Ou não entendi a pergunta corretamente.
AmerllicA
@AmerllicA eventualmente sim, os js devem ser avaliados, mas pense neste caso: Meu site recebeu A, B duas páginas, visitantes na página A frequentemente visitam a página B depois de "terem feito alguns trabalhos" na página A. Então é razoável buscar previamente as páginas B JS, mas se eu puder controlar o tempo que o JS desse B é avaliado, posso ter 100% de certeza de que não bloqueio o encadeamento principal que cria falhas quando o visitante está tentando "fazer seus trabalhos" na página A. Posso avaliar O JS de B após o visitante clicar em um link que aponta para a página B, mas naquele momento o JS de B é provavelmente baixado, só preciso gastar um pouco de tempo para avaliá-lo.
migcoder 4/02
Certamente, de acordo com o blog do chrome v8: v8.dev/blog/cost-of-javascript-2019 , eles fizeram muitas otimizações para obter o tempo de análise JS extremamente rápido, utilizando o thread Worker e muitos outros técnicos, detalhes aqui youtube.com / watch? v = D1UJgiG4_NI . Mas outros navegadores ainda não implementam essa otimização.
migcoder 4/02

Respostas:

2

ATUALIZAR

Você pode usar o preload-webpack-plugin com o html-webpack-plugin, ele permitirá definir o que pré-carregar na configuração e inserir tags automaticamente para pré-carregar seu bloco

note que se você estiver usando o webpack v4 a partir de agora, precisará instalar este plugin usando preload-webpack-plugin@next

exemplo

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

Para um projeto que gera dois scripts assíncronos com nomes gerados dinamicamente, como chunk.31132ae6680e598f8879.jse chunk.d15e7fdfc91b34bb78c4.js, as pré-cargas a seguir serão injetadas no documentohead

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

ATUALIZAÇÃO 2

se você não quiser pré-carregar todos os trechos assíncronos, mas apenas específico quando puder fazer isso também

você pode usar o plugin babel do migcoder ou com o preload-webpack-pluginseguinte

  1. primeiro você terá que nomear esse pedaço assíncrono com ajuda do webpack magic commentexemplo

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. e, em seguida, na configuração do plugin, use esse nome como

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

Primeiro de tudo, vamos ver o comportamento do navegador quando especificamos scripttag ou linktag para carregar o script

  1. sempre que um navegador encontrar uma scripttag, ele será carregado, analisado e executado imediatamente
  2. você só pode atrasar a análise e avaliação com a ajuda de asynce defermarcar somente até oDOMContentLoaded evento
  3. você pode atrasar a execução (avaliação) se não inserir a tag de script (apenas pré-carregá-la com link)

Agora, existem outras maneiras não recomendadas de hackey: você envia todo o script e stringou comment(porque o tempo de avaliação do comentário ou da string é quase insignificante) e quando você precisa executar, pode usar Function() constructorou evalambos não são recomendados


Outros trabalhadores do serviço de abordagem : (isso preservará seu evento de cache após o recarregamento da página ou o usuário ficará offline após o carregamento do cache)

No navegador moderno, você pode usar service workerpara buscar e armazenar em cache um recurso (JavaScript, imagem, css, qualquer coisa) e, quando o thread principal solicita esse recurso, você pode interceptar esse pedido e retornar o recurso do cache dessa maneira, para não analisar e avaliar o script quando você está carregando no cache, leia mais sobre profissionais de serviço aqui

exemplo

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 você pode ver, isso não é uma coisa dependente do webpack , isso está fora do escopo do webpack; no entanto, com a ajuda do webpack, você pode dividir seu pacote, o que ajudará a utilizar melhor o trabalhador do serviço

Tripurari Shankar
fonte
mas ainda assim meu ponto negativo é que não consigo obter o URL do arquivo facilmente do webpack, mesmo que eu vá com o SW, ainda preciso informar ao SW quais arquivos devem ser pré-cache ... um plug-in de manifesto do webpack pode gerar informações manifestas no SW, mas é tudo em operação, significa que SW não tem escolha, mas pré-armazena em cache todos os arquivos listados no manifesto ...
migcoder 03/02
Idealmente, espero que o webpack possa adicionar outro comentário mágico como / * webpackOnlyPrefetch: true * /, para que eu possa chamar a declaração de importação duas vezes para cada bloco preguiçoso carregável, um para pré-busca, um para avaliação de código e tudo acontecer sob demanda.
migcoder 03/02
11
@migcoder que é um ponto válido (você não pode obter o nome do arquivo porque é gerado dinamicamente em tempo de execução) procurará qualquer solução, se eu encontrar alguma
Tripurari Shankar
@migcoder Eu atualizei a resposta, veja-a que resolve seu problema
Tripurari Shankar 03/02
ele resolve parte do problema, pode filtrar os pedaços assíncronos que são bons, mas meu objetivo final é apenas pré-buscar os pedaços assíncronos exigidos. Atualmente, estou olhando para este plugin github.com/sebastian-software/babel-plugin-smart-webpack-import , ele me mostra como reunir todas as instruções de importação e fazer alguma modificação de código nos comentários mágicos, talvez eu possa criar um plug-in semelhante para inserir o código de pré-busca nas instruções de importação com o comentário mágico 'webpackOnlyPrefetch: true'.
migcoder 3/02
1

Atualizações: eu incluo todas as coisas em um pacote npm, confira! https://www.npmjs.com/package/webpack-prefetcher


Após alguns dias de pesquisa, acabei escrevendo um plug-in babel customizado ...

Em suma, o plugin funciona assim:

  • Reúna todas as instruções import (args) no código
  • Se a importação (args) contiver / * prefetch: true * / comment
  • Encontre o chunkId da importação () declaração
  • Substitua-o por Prefetcher.fetch (chunkId)

Prefetcher é uma classe auxiliar que contém o manifesto da saída do webpack e pode nos ajudar a inserir o link de pré-busca:

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

Um exemplo 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(...)

Os detalhes deste plugin podem ser encontrados aqui:

Pré-busca - Assuma o controle do webpack

Novamente, essa é uma maneira realmente hacky e eu não gosto, se você quiser que a equipe do webpack implemente isso, vote aqui:

Funcionalidade: pré-buscar importação dinâmica sob demanda

migcoder
fonte
0

Supondo que eu entenda o que você está tentando alcançar, você deseja analisar e executar um módulo após um determinado evento (por exemplo, clique em um botão). Você pode simplesmente colocar a declaração de importação dentro desse evento:

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