Як можна імпортувати скрипт на веб-упаковці, не оцінюючи його?


9

Я нещодавно працюю над деякими оптимізаціями веб-сайтів, і я починаю використовувати розбиття коду в webpack, використовуючи оператор імпорту, як це:

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

Які правильно створюють pageB-chunk.js , тепер скажімо, що я хочу попередньо встановити цей фрагмент на сторінціA, я можу це зробити, додавши цей вислів у 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-адресу я повинен містити у посиланні попереднього вибору, webpack напевно знає цю URL-адресу, як я можу отримати її з webpack?

Чи є у веб-пакета якийсь простий спосіб досягти цього?


if (false) import(…)- Сумніваюсь, веб-пакет робить аналіз мертвого коду?
Бергі

Де / коли ж ви на справді хочете , щоб оцінити модуль? Ось куди importмає йти динамічний код.
Бергі

Зараз я так розгублений. Чому оцінка важлива? тому що нарешті, файл JS повинен оцінюватися пристроєм клієнтського браузера. Або я неправильно не розумію питання.
AmerllicA

@AmerllicA врешті-решт, так js слід оцінити, але подумайте про цей випадок: Мій веб-сайт отримав A, B дві сторінки, відвідувачі на сторінці Часто відвідують сторінку B після того, як вони "виконали деякі роботи" на сторінці A. Тоді розумно попередньо вилучити сторінку B JS, але якщо я можу контролювати час оцінки цього JS B, я можу на 100% впевнений, що я не блокую основну нитку, яка створює глюки, коли відвідувач намагається "виконати свої роботи" на сторінці А. Я можу оцінити JS J після відвідувача натискають на посилання, яке вказує на сторінку B, але в той час JS B, швидше за все, завантажується, мені просто потрібно витратити трохи часу, щоб його оцінити.
migcoder

Зрозуміло, згідно з блогом chrome v8: v8.dev/blog/cost-of-javascript-2019 , вони зробили багато оптимізацій для досягнення швидкого швидкого розбору JS за допомогою використання потоку Worker та багатьох інших технологій, детальніше тут у 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

якщо ви не хочете попередньо завантажити весь фрагмент асинхронізації, але лише конкретний раз, коли ви також можете це зробити

або ви можете використовувати плагін для мітки, або 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та deferтегів доDOMContentLoaded події
  3. ви можете затримати виконання (оцінку), якщо ви не вставите тег сценарію (лише попередньо завантажте його link)

Тепер є й інші не рекомендується hackey шляху ви відправляєте весь сценарій і 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
      });
    }
  }));
});

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


але все ж больовим моментом є те, що я не можу легко отримати URL-адресу файлу з webpack, навіть я переходжу з SW, мені все одно потрібно повідомити SW, які файли мають бути попередньо кешовані ... плагін веб-пакета маніфесту може генерувати інформацію про маніфест у SW, але це все в роботі, значить SW не має іншого вибору, крім попереднього кешування всіх файлів, перелічених у маніфесті ...
migcoder

В ідеалі, я сподіваюся, що webpack може додати ще один магічний коментар, як / * webpackOnlyPrefetch: true * /, тому я можу зателефонувати за заявою про імпорт двічі для кожного ледачого завантаженого фрагмента, один для попереднього вибору, один для оцінки коду, і все відбувається за запитом.
migcoder

1
@migcoder - це достовірний момент (ви не можете отримати ім'я файлу, оскільки воно динамічно генерується під час виконання) розгляне будь-яке рішення, якщо я можу знайти його
Tripurari Shankar

@migcoder Я оновив відповідь, будь ласка, подивіться, що вона вирішує вашу проблему
Tripurari Shankar

Це вирішує частину проблеми, вона може відфільтрувати шматки асинхроніму, що добре, але моя остаточна мета - лише попередній вибір, який вимагав асинхронних фрагментів. Я зараз переглядаю цей плагін github.com/sebastian-software/babel-plugin-smart-webpack-import , він показує мені, як зібрати всі заяви про імпорт та зробити деяку основу для зміни коду на основі магічних коментарів, можливо, я можу створити аналогічний плагін для вставки коду попереднього вибору у вивіски про імпорт із магічним коментарем 'webpackOnlyPrefetch: true'.
migcoder

1

Оновлення: Я включаю всі речі в пакет npm, перевіряйте це! https://www.npmjs.com/package/webpack-prefetcher


Після кількох днів досліджень я закінчую написання плагіну для налаштування бабел ...

Коротше кажучи, плагін працює так:

  • Зберіть всі коди імпорту (args) у коді
  • Якщо імпорт (аргументи) містить / * prefetch: true * / comment
  • Знайти chunkId від імпорту () заяву
  • Замініть його на Prefetcher.fetch (chunkId)

Prefetcher - це допоміжний клас, який містить маніфест виводу веб-пакету, і може допомогти нам вставити посилання попереднього вибору:

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 здійснила це, будь ласка, проголосуйте тут:

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


0

Припускаючи, що я зрозумів, чого ви намагаєтеся досягти, ви хочете розібратися та виконати модуль після заданої події (наприклад, натиснути кнопку). Ви можете просто покласти імпорт імпорту всередині цієї події:

element.addEventListener('click', async () => {
  const module = await import("...");
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.