В веб-пакете, как я могу импортировать скрипт без его оценки?

9

Недавно я работал над некоторыми работами по оптимизации веб-сайта, и я начал использовать разбиение кода в веб-пакете, используя следующую инструкцию импорта:

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

Что правильно создает pageB-chunk.js , теперь, скажем, я хочу предварительно извлечь этот чанк в pageA, я могу сделать это, добавив этот оператор в pageA:

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

Что приведет к

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

будучи добавленным к заголовку HTML, браузер будет предварительно выбирать его, пока все хорошо.

Проблема в том, что оператор импорта, который я здесь использую, не только предварительно выбирает файл js, но и оценивает файл js, означает, что код этого файла js анализируется и компилируется в байт-коды, выполняется код верхнего уровня этого JS.

Это очень трудоемкая операция на мобильном устройстве, и я хочу ее оптимизировать, я хочу только часть предварительной выборки , я не хочу часть оценки и выполнения , потому что позже, когда произойдет какое-то взаимодействие с пользователем, я инициирую синтаксический анализ и оценить себя

введите описание изображения здесь

↑↑↑↑↑↑↑↑ Я хочу только запустить первые два шага, фотографии приходят с https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

Конечно, я могу сделать это, добавив ссылку на предварительную выборку самостоятельно, но это означает, что мне нужно знать, какой URL-адрес я должен добавить в ссылку предварительной выборки, веб-пакет определенно знает этот URL-адрес, как я могу получить его из веб-пакета?

У webpack есть какой-нибудь легкий способ достигнуть этого?

migcoder
источник
if (false) import(…)Я сомневаюсь, что веб-пакет делает анализ мертвого кода?
Берги
Где / когда же вы на самом деле хотите , чтобы оценить модуль? Вот куда importдолжен идти динамический код.
Берги
Я так растерялся сейчас. Почему оценка важна? потому что, наконец, файл JS должен быть оценен клиентским браузерным устройством. Или я не правильно понял вопрос.
AmerllicA
@AmerllicA в конечном итоге да, js следует оценить, но подумайте об этом: мой сайт получил A, B две страницы, посетители на странице A часто посещают страницу B после того, как они «сделали какие-то работы» на странице A. Тогда разумно предварительно выбрать страницу B JS, но если я могу контролировать время оценки JS этого B, я могу на 100% быть уверенным, что я не блокирую основной поток, который создает глюки, когда посетитель пытается «сделать свою работу» на странице A. Я могу оценить JS B после того, как посетитель щелкнет по ссылке, указывающей на страницу B, но в это время JS B, скорее всего, загружается, мне просто нужно потратить немного времени, чтобы оценить его.
Migcoder
Конечно, согласно блогу chrome v8: v8.dev/blog/cost-of-javascript-2019 , они сделали много оптимизаций, чтобы добиться невероятно быстрого времени анализа JS, используя рабочий поток и многие другие технологии, подробности здесь youtube.com / смотреть? V = D1UJgiG4_NI . Но другие браузеры пока не реализуют такую ​​оптимизацию.
Migcoder

Ответы:

2

ОБНОВИТЬ

Вы можете использовать preload-webpack-plugin с html-webpack-plugin, он позволит вам определить, что предварительно загружать в конфигурацию, и автоматически вставит теги для предварительной загрузки вашего чанка.

обратите внимание, что если вы используете webpack v4, вам нужно установить этот плагин, используя preload-webpack-plugin@next

пример

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

Для проекта, генерирующего два асинхронных сценария с динамически генерируемыми именами, такими как chunk.31132ae6680e598f8879.jsи chunk.d15e7fdfc91b34bb78c4.js, следующие предварительные загрузки будут внедрены в документhead

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

ОБНОВЛЕНИЕ 2

если вы не хотите предварительно загружать весь асинхронный блок, но только один раз, вы можете сделать это тоже

либо вы можете использовать babel плагин migcoder или preload-webpack-pluginкак

  1. Сначала вы должны назвать этот асинхронный блок с помощью webpack magic commentпримера

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. а затем в конфигурации плагина использовать это имя как

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

Прежде всего давайте посмотрим поведение браузера, когда мы указываем scriptтег или linkтег для загрузки скрипта

  1. всякий раз, когда браузер сталкивается с scriptтегом, он загружает его, анализирует и немедленно выполняет его
  2. Вы можете только отложить анализ и оценку только с помощью тега asyncи до события.deferDOMContentLoaded
  3. вы можете отложить выполнение (оценку), если не вставите тег сценария (только предварительно загрузите его link)

теперь есть и другой, не рекомендуемый хаккейный способ: вы отправляете весь свой сценарий и stringили comment(потому что время оценки комментария или строки практически ничтожно), и когда вам нужно выполнить то, что вы можете использовать, Function() constructorили evalоба не рекомендуется


Работники сервиса другого подхода : (это сохранит ваше кеш-событие после перезагрузки страницы или пользователь перейдет в автономный режим после загрузки кеша)

В современном браузере вы можете использовать service workerдля извлечения и кэширования ресурсов (JavaScript, изображения, CSS, что угодно), и когда основной поток запрашивает этот ресурс, вы можете перехватить этот запрос и вернуть ресурс из кэша таким образом, что вы не анализируете и не анализируете скрипт, когда вы загружаете его в кеш, подробнее о сервисных работниках здесь

пример

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

как вы можете видеть, это не зависит от веб-пакета , это выходит за рамки веб-пакета, однако с помощью веб-пакета вы можете разделить свой пакет, что поможет лучше использовать работника сервиса

Трипурари Шанкар
источник
но все же моя главная проблема в том, что я не могу легко получить URL файла из веб-пакета, даже если я использую SW, мне все равно нужно сообщить SW, какие файлы должны быть предварительно кэшированы ... плагин манифеста веб-пакета может генерировать информацию манифеста в SW, но это все в работе, значит у SW нет выбора, кроме как предварительно кэшировать все файлы, перечисленные в манифесте ...
migcoder
В идеале, я надеюсь, что webpack может добавить еще один магический комментарий, например / * webpackOnlyPrefetch: true * /, поэтому я могу вызывать оператор import дважды для каждого ленивого загружаемого чанка, один для предварительной выборки, один для оценки кода, и все происходит по требованию.
Migcoder
1
@migcoder - это правильная точка (вы не можете получить имя файла, потому что оно динамически генерируется во время выполнения), если я смогу найти какое-либо решение, оно будет искать любое решение
Tripurari Shankar
@migcoder Я обновил ответ, пожалуйста, посмотрите, что решает вашу проблему
Трипурари Шанкар
это решает часть проблемы, оно может отфильтровывать асинхронные блоки, это хорошо, но моя конечная цель - только предварительная выборка требуемых асинхронных блоков. В настоящее время я смотрю на этот плагин github.com/sebastian-software/babel-plugin-smart-webpack-import , он показывает мне, как собрать все операторы импорта и выполнить некоторые модификации кода на основе магических комментариев, может быть, я смогу создать аналогичный плагин для вставки кода предварительной выборки в операторы импорта с волшебным комментарием «webpackOnlyPrefetch: true».
Migcoder
1

Обновления: я включаю все вещи в пакет npm, зацените! https://www.npmjs.com/package/webpack-prefetcher


После нескольких дней исследований я в итоге написал плагин для настройки babel ...

Короче, плагин работает так:

  • Соберите все операторы import (args) в коде
  • Если импорт (args) содержит / * prefetch: true * / comment
  • Найти chunkId от импорта () заявление
  • Замените его на Prefetcher.fetch (chunkId)

Prefetcher - это вспомогательный класс, который содержит манифест вывода webpack и может помочь нам вставить ссылку prefetch:

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

Пример использования:

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

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

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

Подробности этого плагина можно найти здесь:

Prefetch - взять управление из веб-пакета

Опять же, это действительно хакерский способ, и мне не нравится, если вы хотите, чтобы команда webpack реализовала это, пожалуйста, проголосуйте здесь:

Особенность: предварительный выбор динамического импорта по требованию

migcoder
источник
0

Предполагая, что я понял, чего вы пытаетесь достичь, вы хотите проанализировать и выполнить модуль после определенного события (например, нажмите на кнопку). Вы можете просто поместить оператор импорта в это событие:

element.addEventListener('click', async () => {
  const module = await import("...");
});
Элия ​​Коэн
источник