Di webpack, bagaimana saya bisa mengimpor skrip tanpa mengevaluasinya?

9

Saya baru-baru ini mengerjakan beberapa pekerjaan pengoptimalan situs web, dan saya mulai menggunakan pemecahan kode di webpack dengan menggunakan pernyataan impor seperti ini:

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

Yang benar-benar membuat pageB-chunk.js , sekarang katakanlah saya ingin membuat prefetch bagian ini di pageA, saya bisa melakukannya dengan menambahkan pernyataan ini di pageA:

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

Yang akan menghasilkan a

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

menjadi menambahkan ke kepala HTML, maka browser akan mengambilnya, sejauh ini bagus.

Masalahnya adalah pernyataan impor yang saya gunakan di sini tidak hanya mengambil file js, tetapi juga mengevaluasi file js, berarti kode file js tersebut diuraikan & dikompilasi ke bytecode, kode tingkat atas dari JS yang dieksekusi.

Ini adalah operasi yang sangat memakan waktu pada perangkat seluler dan saya ingin mengoptimalkannya, saya hanya ingin bagian prefetch , saya tidak ingin bagian evaluasi & eksekusi , karena nanti ketika beberapa interaksi pengguna terjadi, saya akan memicu penguraian & evaluasi diri saya sendiri

masukkan deskripsi gambar di sini

↑↑↑↑↑↑↑↑ Saya hanya ingin memicu dua langkah pertama, gambar berasal dari https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

Tentu saya bisa melakukan ini dengan menambahkan tautan prefetch sendiri, tetapi ini berarti saya perlu tahu URL mana yang harus saya masukkan di tautan prefetch, webpack pasti tahu URL ini, bagaimana saya bisa mendapatkannya dari webpack?

Apakah webpack memiliki cara mudah untuk mencapai ini?

migcoder
sumber
if (false) import(…)- Saya ragu webpack melakukan analisis kode mati?
Bergi
Di mana / kapan yang Anda benar-benar ingin mengevaluasi modul? Di situlah importkode dinamis harus pergi.
Bergi
Saya sangat bingung sekarang. Mengapa evaluasi itu penting? karena pada akhirnya, file JS harus dievaluasi oleh perangkat browser klien. Atau saya tidak mengerti pertanyaan dengan benar.
AmerllicA
@AmerllicA akhirnya ya js harus dievaluasi, tetapi pikirkan kasus ini: Situs web saya mendapat A, B dua halaman, pengunjung di halaman A sering mengunjungi halaman B setelah mereka "melakukan beberapa pekerjaan" di halaman A. Maka masuk akal untuk memilih halaman B JS, tetapi jika saya dapat mengontrol waktu di mana B's JS ini dievaluasi, saya dapat 100% yakin bahwa saya tidak memblokir utas yang membuat gangguan ketika pengunjung mencoba untuk "melakukan pekerjaan mereka" di halaman A. Saya dapat mengevaluasi B's JS setelah pengunjung mengeklik tautan yang mengarah ke halaman B, tetapi pada saat itu B's JS kemungkinan besar diunduh, saya hanya perlu meluangkan sedikit waktu untuk mengevaluasinya.
migcoder
Tentu menurut blog chrome v8: v8.dev/blog/cost-of-javascript-2019 , mereka melakukan banyak optimasi untuk mencapai waktu penguraian JS yang sangat cepat, dengan memanfaatkan thread Pekerja dan banyak teknologi lainnya, detailnya di sini youtube.com / tonton? v = D1UJgiG4_NI . Tetapi browser lain belum mengimplementasikan optimasi seperti itu.
migcoder

Jawaban:

2

MEMPERBARUI

Anda dapat menggunakan preload-webpack-plugin dengan html-webpack-plugin itu akan memungkinkan Anda menentukan apa yang harus dimuat di konfigurasi dan secara otomatis akan memasukkan tag untuk preload potongan Anda

perhatikan jika Anda menggunakan webpack v4 sekarang Anda harus menginstal plugin ini menggunakan preload-webpack-plugin@next

contoh

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

Untuk proyek yang menghasilkan dua skrip async dengan nama yang dihasilkan secara dinamis, seperti chunk.31132ae6680e598f8879.jsdan chunk.d15e7fdfc91b34bb78c4.js, preload berikut akan disuntikkan ke dalam dokumenhead

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

PEMBARUAN 2

jika Anda tidak ingin memuat semua potongan async tetapi hanya spesifik sekali Anda dapat melakukannya juga

Anda dapat menggunakan plugin babel migcoder atau dengan preload-webpack-pluginseperti berikut

  1. pertama Anda harus memberi nama potongan async itu dengan bantuan webpack magic commentcontoh

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. dan kemudian dalam konfigurasi plugin gunakan nama seperti itu

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

Pertama-tama mari kita lihat perilaku browser ketika kita menentukan scripttag atau linktag untuk memuat skrip

  1. setiap kali browser menemukan scripttag, tag akan memuatnya untuk menguraikannya dan menjalankannya dengan segera
  2. Anda hanya dapat menunda penguraian dan evaluasi hanya dengan bantuan asyncdan defertag hinggaDOMContentLoaded acara
  3. Anda dapat menunda eksekusi (evaluasi) jika Anda tidak memasukkan tag skrip (hanya dengan preload dengan link)

sekarang ada beberapa cara hackey lain yang tidak disarankan adalah Anda mengirimkan seluruh skrip Anda dan stringatau comment(karena waktu evaluasi komentar atau string hampir dapat diabaikan) dan ketika Anda perlu mengeksekusi yang dapat Anda gunakan Function() constructoratau evalkeduanya tidak direkomendasikan


Pekerja Layanan Pendekatan Lainnya : (ini akan membuat Anda menyimpan aktivitas cache setelah pemuatan ulang halaman atau pengguna offline setelah cache dimuat)

Di peramban modern, Anda dapat menggunakan service workeruntuk mengambil dan men-cache jalan lain (JavaScript, gambar, css apa saja) dan ketika permintaan utas utama untuk jalan itu Anda dapat mencegat permintaan itu dan mengembalikan jalan lain dari cache dengan cara ini Anda tidak mengurai dan mengevaluasi skrip saat Anda memuatnya ke dalam cache, baca lebih lanjut tentang pekerja layanan di sini

contoh

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

karena Anda dapat melihat ini bukan hal yang tergantung webpack ini berada di luar lingkup webpack namun dengan bantuan webpack Anda dapat membagi bundel Anda yang akan membantu memanfaatkan pekerja layanan dengan lebih baik

Tripurari Shankar
sumber
tapi tetap saja titik sakit saya adalah saya tidak bisa mendapatkan url file dengan mudah dari webpack, bahkan saya pergi dengan SW, saya masih perlu memberi tahu SW file apa yang harus pre-cache ... plugin manifes webpack dapat menghasilkan info manifes menjadi SW, tetapi ini semua dalam operasi, berarti SW tidak punya pilihan selain melakukan pra-cache semua file yang tercantum dalam manifes ...
migcoder
Idealnya, saya berharap webpack dapat menambahkan komentar ajaib lain seperti / * webpackOnlyPrefetch: true * /, jadi saya dapat memanggil pernyataan impor dua kali untuk setiap potongan malas yang dapat dimuat, satu untuk pengambilan awal, satu untuk evaluasi kode, dan semuanya terjadi sesuai permintaan.
migcoder
1
@migcoder itu adalah poin yang valid (Anda tidak bisa mendapatkan nama file karena itu dihasilkan secara dinamis saat runtime) akan mencari solusi apa pun jika saya dapat menemukannya
Tripurari Shankar
@migcoder Saya telah memperbarui jawabannya, silakan lihat yang memecahkan masalah Anda
Tripurari Shankar
itu memecahkan sebagian dari masalah, itu dapat menyaring potongan async yang bagus, tetapi tujuan akhir saya hanya prefetch potongan async yang diminta. Saat ini saya sedang melihat plugin ini github.com/sebastian-software/babel-plugin-smart-webpack-import , ini menunjukkan kepada saya cara mengumpulkan semua pernyataan impor dan melakukan beberapa modifikasi kode berdasarkan komentar ajaib, mungkin saya bisa membuat plugin serupa untuk menyisipkan kode prefetch pada pernyataan impor dengan komentar ajaib 'webpackOnlyPrefetch: true'.
migcoder
1

Pembaruan: Saya memasukkan semua hal ke dalam paket npm, check it out! https://www.npmjs.com/package/webpack-prefetcher


Setelah beberapa hari penelitian, saya berakhir dengan menulis plugin babel yang dapat dikustomisasi ...

Singkatnya, plugin berfungsi seperti ini:

  • Kumpulkan semua pernyataan impor (argumen) dalam kode
  • Jika impor (args) berisi / * prefetch: true * / comment
  • Menemukan chunkId dari impor () pernyataan
  • Ganti dengan Prefetcher.fetch (chunkId)

Prefetcher adalah kelas pembantu yang berisi manifes output webpack, dan dapat membantu kami dalam memasukkan tautan 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)
  }
}

Contoh penggunaan:

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

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

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

Detail dari plugin ini dapat ditemukan di sini:

Prefetch - Kendalikan dari webpack

Sekali lagi, ini adalah cara yang benar-benar gila dan saya tidak suka, jika Anda ingin tim webpack menerapkan ini, silakan pilih di sini:

Fitur: mengambil impor dinamis berdasarkan permintaan

migcoder
sumber
0

Dengan asumsi saya mengerti apa yang Anda coba capai, Anda ingin mem-parsing dan mengeksekusi modul setelah peristiwa tertentu (misalnya klik pada tombol). Anda bisa memasukkan pernyataan impor ke dalam acara itu:

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