ใน webpack ฉันจะนำเข้าสคริปต์โดยไม่ประเมินได้อย่างไร


9

ฉันเพิ่งทำงานกับการเพิ่มประสิทธิภาพเว็บไซต์บางอย่างและฉันเริ่มใช้การแยกรหัสใน webpack โดยใช้คำสั่งนำเข้าเช่นนี้:

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

ซึ่งสร้างpageB-chunk.jsอย่างถูกต้องตอนนี้สมมติว่าฉันต้องการดึงข้อมูลอันนี้ใน pageA ฉันสามารถทำได้โดยเพิ่มคำสั่งนี้ในหน้า A:

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

ซึ่งจะส่งผลให้

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

ถูกผนวกเข้ากับส่วนหัวของ HTML จากนั้นเบราว์เซอร์จะดึงข้อมูลล่วงหน้าจนดีมาก

ปัญหาคือคำแถลงการนำเข้าที่ฉันใช้ที่นี่ไม่เพียง แต่ดึงไฟล์ js ล่วงหน้า แต่ยังประเมินไฟล์ js หมายความว่ารหัสของไฟล์ js นั้นถูกวิเคราะห์ & คอมไพล์เป็นไบต์รหัสระดับบนสุดของ JS นั้นจะถูกดำเนินการ

นี่เป็นการดำเนินการที่ใช้เวลานานมากบนอุปกรณ์พกพาและฉันต้องการปรับให้เหมาะสมฉันต้องการเฉพาะส่วนprefetchเท่านั้นฉันไม่ต้องการชิ้นส่วนประเมิน & ดำเนินการเพราะต่อมาเมื่อมีการโต้ตอบของผู้ใช้เกิดขึ้นฉันจะเริ่มการแยกวิเคราะห์ & ประเมินตนเอง

ป้อนคำอธิบายรูปภาพที่นี่

↑↑↑↑↑↑↑↑ฉันต้องการทริกเกอร์สองขั้นตอนแรกเท่านั้นภาพมาจากhttps://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

แน่ใจว่าฉันสามารถทำได้โดยการเพิ่มลิงค์ prefetch ด้วยตัวเอง แต่นั่นหมายความว่าฉันต้องรู้ว่าฉันควรใส่ URL ใดไว้ในลิงค์ prefetch webpack รู้ URL นี้อย่างแน่นอนฉันจะได้รับจาก webpack ได้อย่างไร

webpack มีวิธีง่าย ๆ ในการบรรลุเป้าหมายนี้หรือไม่?


if (false) import(…)- ฉันสงสัยว่า webpack ทำการวิเคราะห์รหัสที่ไม่ทำงานหรือไม่
Bergi

ที่ไหน / เมื่อไม่จริงคุณต้องการประเมินโมดูล? นั่นคือสิ่งที่importรหัสแบบไดนามิกควรไป
Bergi

ตอนนี้ฉันสับสนมาก ทำไมการประเมินเป็นสิ่งสำคัญ เพราะในที่สุดไฟล์ JS ควรได้รับการประเมินโดยอุปกรณ์เบราว์เซอร์ไคลเอ็นต์ หรือฉันไม่เข้าใจคำถามอย่างถูกต้อง
AmerllicA

@AmerllicA ในที่สุดใช่ js ควรได้รับการประเมิน แต่คิดว่ากรณีนี้: เว็บไซต์ของฉันมี A, B สองหน้าผู้เข้าชมในหน้า A มักจะไปที่หน้า B หลังจากที่พวกเขา "ทำงานบางอย่าง" ในหน้า A. จากนั้นก็มีเหตุผลที่จะดึงหน้า B ของ JS แต่ถ้าฉันสามารถควบคุมเวลาที่ประเมิน JS ของ B นี้ได้ฉันมั่นใจได้ 100% ว่าฉันจะไม่ปิดกั้นเธรดหลักที่สร้างความบกพร่องเมื่อผู้เยี่ยมชมพยายาม "ทำงานของพวกเขา" ในหน้า A. ฉันสามารถประเมินได้ B's JS หลังจากผู้เยี่ยมชมคลิกลิงก์ที่ชี้ไปที่หน้า B แต่ในขณะนั้น JS ของ B นั้นถูกดาวน์โหลดมากที่สุดฉันแค่ใช้เวลาเล็กน้อยในการประเมิน
migcoder

แน่นอนตามบล็อกของ chrome v8: v8.dev/blog/cost-of-javascript-2019พวกเขาทำการปรับแต่งมากมายเพื่อให้ได้ JS การแจงเวลาอย่างรวดเร็วโดยการใช้เธรดผู้ทำงานและเทคโนโลยีอื่น ๆ รายละเอียดที่นี่youtube.com / แต่เบราว์เซอร์อื่นยังไม่ใช้การเพิ่มประสิทธิภาพดังกล่าว
migcoder

คำตอบ:


2

UPDATE

คุณสามารถใช้preload-webpack-pluginกับhtml-webpack-pluginมันจะช่วยให้คุณกำหนดสิ่งที่จะพรีโหลดในการกำหนดค่าและมันจะแทรกแท็กโดยอัตโนมัติเพื่อโหลดชิ้นของคุณ

หมายเหตุหากคุณใช้ webpack v4 ณ ตอนนี้คุณจะต้องติดตั้งปลั๊กอินนี้โดยใช้ preload-webpack-plugin@next

ตัวอย่าง

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

สำหรับโครงการที่สร้างสคริปต์ async สองตัวที่มีชื่อที่สร้างขึ้นแบบไดนามิกเช่นchunk.31132ae6680e598f8879.jsและ chunk.d15e7fdfc91b34bb78c4.jspreloads ต่อไปนี้จะถูกแทรกลงในเอกสารhead

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

อัพเดท 2

หากคุณไม่ต้องการโหลด async ทั้งหมดล่วงหน้า แต่เฉพาะเจาะจงเมื่อคุณสามารถทำได้เช่นกัน

คุณสามารถใช้ปลั๊กอิน babel ของ migcoderหรือด้วยสิ่งpreload-webpack-pluginต่อไปนี้

  1. ก่อนอื่นคุณจะต้องตั้งชื่อให้ async เป็นก้อนด้วยความช่วยเหลือของ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)

ตอนนี้มีวิธีอื่นที่ไม่แนะนำให้ใช้แฮ็คกี้คือคุณจัดส่งสคริปต์ทั้งหมดของคุณและ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สิ่งนี้อยู่นอกขอบเขตของ webpack แต่ด้วยความช่วยเหลือของ webpack คุณสามารถแยกบันเดิลของคุณซึ่งจะช่วยให้การใช้งานของพนักงานบริการดีขึ้น


แต่จุดปวดของฉันก็คือฉันไม่สามารถรับ url ของไฟล์ได้อย่างง่ายดายจาก webpack แม้ฉันจะไปกับ SW ฉันยังต้องแจ้งให้ SW ทราบว่าไฟล์ใดควรเป็น pre-cache ... ปลั๊กอินรายการ webpack สามารถสร้างข้อมูลรายการลงใน SW แต่เป็นการดำเนินการทั้งหมดหมายความว่า SW ไม่มีทางเลือกนอกจากแคชไฟล์ทั้งหมดที่แสดงรายการไว้ในรายการ ...
migcoder

ฉันหวังว่า webpack สามารถเพิ่มความคิดเห็นมายากลอื่นเช่น / * webpackOnlyPrefetch: จริง * / ดังนั้นฉันสามารถเรียกคำสั่งนำเข้าสองครั้งสำหรับทุก ๆ กลุ่มที่โหลดได้ขี้เกียจหนึ่งสำหรับ prefetch หนึ่งสำหรับการประเมินโค้ดและทุกอย่างเกิดขึ้นตามความต้องการ
migcoder

1
@migcoder นั้นเป็นจุดที่ถูกต้อง (คุณไม่สามารถรับชื่อไฟล์เพราะมันถูกสร้างขึ้นแบบไดนามิกบนรันไทม์) จะค้นหาวิธีการแก้ปัญหาใด ๆ ถ้าฉันสามารถหาได้
Tripurari Shankar

@migcoder ฉันได้อัปเดตคำตอบแล้วโปรดดูว่ามันแก้ปัญหาของคุณได้ไหม
Tripurari Shankar

มันแก้ปัญหาบางส่วนได้มันสามารถกรอง async chunks ได้ดี แต่เป้าหมายสุดท้ายของฉันคือ prefetch async ที่เรียกร้องเท่านั้น ขณะนี้ฉันกำลังดูปลั๊กอินนี้github.com/sebastian-software/babel-plugin-smart-webpack-importมันแสดงให้ฉันเห็นว่าจะรวบรวมคำสั่งการนำเข้าทั้งหมดอย่างไร ปลั๊กอินที่คล้ายกันเพื่อแทรกรหัส prefetch ในคำสั่งนำเข้าที่มีความคิดเห็นมายากล 'webpackOnlyPrefetch: true'
migcoder

1

อัปเดต: ฉันรวมทุกสิ่งไว้ในแพ็คเกจ npm ลองดูสิ! https://www.npmjs.com/package/webpack-prefetcher


หลังจากการวิจัยไม่กี่วันฉันก็ลงเอยด้วยการเขียนโปรแกรมเสริม babel ...

กล่าวโดยย่อปลั๊กอินทำงานดังนี้:

  • รวบรวมคำสั่งimport (args) ทั้งหมดในรหัส
  • หากการนำเข้า (args)มี / * prefetch: true * / comment
  • ค้นหาchunkIdจากคำสั่งimport ()
  • แทนที่ด้วยPrefetcher.fetch (chunkId)

Prefetcher เป็นคลาสผู้ช่วยที่มีรายการของ webpack output และสามารถช่วยเราในการแทรกลิงค์ 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(...)

รายละเอียดของปลั๊กอินนี้สามารถพบได้ที่นี่:

โหลดล่วงหน้า - ควบคุมจาก webpack

อีกครั้งนี่เป็นวิธีที่แฮ็กจริง ๆ และฉันไม่ชอบถ้าคุณต้องการให้ทีม webpack ใช้งานได้กรุณาลงคะแนนได้ที่นี่:

คุณสมบัติ: prefetch การนำเข้าแบบไดนามิกตามความต้องการ


0

สมมติว่าฉันเข้าใจสิ่งที่คุณพยายามจะบรรลุคุณต้องการแยกวิเคราะห์และรันโมดูลหลังจากเหตุการณ์ที่กำหนด (เช่นคลิกที่ปุ่ม) คุณสามารถใส่คำสั่งการนำเข้าไว้ในเหตุการณ์นั้น:

element.addEventListener('click', async () => {
  const module = await import("...");
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.