Trên webpack làm thế nào tôi có thể nhập một tập lệnh mà không đánh giá nó?


9

Gần đây tôi đang làm việc trên một số công việc tối ưu hóa trang web và tôi bắt đầu sử dụng phân tách mã trong gói web bằng cách sử dụng câu lệnh nhập như thế này:

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

Mà tạo chính xác pageB-chunk.js , bây giờ hãy nói rằng tôi muốn tìm nạp trước đoạn này trong pageA, tôi có thể làm điều đó bằng cách thêm câu lệnh này vào pageA:

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

Điều này sẽ dẫn đến một

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

được thêm vào đầu HTML, sau đó trình duyệt sẽ tìm nạp trước, cho đến nay vẫn tốt.

Vấn đề là câu lệnh nhập mà tôi sử dụng ở đây không chỉ tìm nạp trước tệp js mà còn đánh giá tệp js, có nghĩa là mã của tệp js đó được phân tích cú pháp & biên dịch thành mã byte, mã cấp cao nhất của JS được thực thi.

Đây là một hoạt động rất tốn thời gian trên thiết bị di động và tôi muốn tối ưu hóa nó, tôi chỉ muốn phần tìm nạp trước , tôi không muốn phần đánh giá & thực thi , vì sau này khi một số tương tác người dùng xảy ra, tôi sẽ kích hoạt phân tích cú pháp & đánh giá bản thân

nhập mô tả hình ảnh ở đây

↑↑↑↑↑↑↑↑ Tôi chỉ muốn kích hoạt hai bước đầu tiên, hình ảnh đến từ https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

Chắc chắn tôi có thể làm điều này bằng cách tự thêm liên kết tìm nạp trước, nhưng điều này có nghĩa là tôi cần biết tôi nên đặt URL nào trong liên kết tìm nạp trước, webpack chắc chắn biết URL này, làm cách nào tôi có thể lấy nó từ webpack?

Webpack có cách nào dễ dàng để đạt được điều này không?


if (false) import(…)- Tôi nghi ngờ webpack không phân tích mã chết?
Bergi

Ở đâu / khi nào bạn thực sự muốn đánh giá mô-đun? Đó là nơi importmã động nên đi.
Bergi

Bây giờ tôi rất bối rối. Tại sao đánh giá là quan trọng? bởi vì cuối cùng, tệp JS phải được đánh giá bởi thiết bị trình duyệt máy khách. Hoặc tôi không hiểu câu hỏi chính xác.
AmerllicA

@AmerllicA cuối cùng cũng có js nên được đánh giá, nhưng hãy nghĩ trường hợp này: Trang web của tôi có A, B hai trang, khách truy cập trong trang A thường truy cập trang B sau khi họ "thực hiện một số công việc" trên trang A. Sau đó, việc tìm nạp trang B là hợp lý JS, nhưng nếu tôi có thể kiểm soát thời gian đánh giá JS của B này, tôi có thể chắc chắn 100% rằng tôi không chặn luồng chính tạo ra sự cố khi khách truy cập đang cố gắng "thực hiện công việc của họ" trên trang A. Tôi có thể đánh giá JS của B sau khi khách truy cập nhấp vào liên kết trỏ đến trang B, nhưng tại thời điểm đó, JS của B rất có thể được tải xuống, tôi chỉ cần dành một ít thời gian để đánh giá nó.
mã hóa

Chắc chắn theo blog của chrome v8: v8.dev/blog/cost-of-javascript-2019 , họ đã thực hiện rất nhiều tối ưu hóa để đạt được thời gian phân tích cú pháp JS nhanh, bằng cách sử dụng luồng Công nhân và nhiều công nghệ khác, chi tiết tại đây youtube.com / xem? v = D1UJgiG4_NI . Nhưng các trình duyệt khác chưa thực hiện tối ưu hóa như vậy.
mã hóa

Câu trả lời:


2

CẬP NHẬT

Bạn có thể sử dụng preload-webpack-plugin với html-webpack-plugin, nó sẽ cho phép bạn xác định những gì cần tải trước trong cấu hình và nó sẽ tự động chèn thẻ để tải trước đoạn của bạn

lưu ý nếu bây giờ bạn đang sử dụng webpack v4, bạn sẽ phải cài đặt plugin này bằng cách sử dụng preload-webpack-plugin@next

thí dụ

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

Đối với dự án tạo hai tập lệnh không đồng bộ với các tên được tạo động, chẳng hạn như chunk.31132ae6680e598f8879.jschunk.d15e7fdfc91b34bb78c4.js, các tải trước sau sẽ được đưa vào tài liệuhead

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

CẬP NHẬT 2

nếu bạn không muốn tải trước tất cả các đoạn không đồng bộ nhưng chỉ cụ thể một khi bạn cũng có thể làm điều đó

hoặc bạn có thể sử dụng plugin babel của người di cư hoặc với preload-webpack-pluginnhư sau

  1. đầu tiên bạn sẽ phải đặt tên cho đoạn async đó với sự trợ giúp của webpack magic commentví dụ

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. và sau đó trong cấu hình plugin sử dụng tên đó như

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

Trước hết, hãy xem hành vi của trình duyệt khi chúng tôi chỉ định scriptthẻ hoặc linkthẻ để tải tập lệnh

  1. Bất cứ khi nào một trình duyệt gặp phải một scriptthẻ, nó sẽ tải nó và phân tích nó ngay lập tức
  2. bạn chỉ có thể trì hoãn việc phân tích cú pháp và đánh giá với sự trợ giúp của asyncchỉdefer gắn thẻ cho đến khiDOMContentLoaded sự kiện
  3. bạn có thể trì hoãn việc thực thi (đánh giá) nếu bạn không chèn thẻ script (chỉ tải trước nó với link)

bây giờ có một số cách hackey không được đề xuất khác là bạn gửi toàn bộ tập lệnh của mình và stringhoặc comment(vì thời gian đánh giá nhận xét hoặc chuỗi gần như không đáng kể) và khi bạn cần thực thi mà bạn có thể sử dụng Function() constructorhoặc evalcả hai không được khuyến nghị


Một nhân viên dịch vụ tiếp cận khác : (điều này sẽ bảo vệ bạn sự kiện bộ đệm sau khi tải lại trang hoặc người dùng ngoại tuyến sau khi bộ đệm được tải)

Trong trình duyệt hiện đại, bạn có thể sử dụng service workerđể tìm nạp và lưu trữ bộ đệm (JavaScript, hình ảnh, css bất cứ thứ gì) và khi yêu cầu chủ đề chính cho yêu cầu đó, bạn có thể chặn yêu cầu đó và trả lại truy vấn từ bộ đệm theo cách này mà bạn không phân tích cú pháp và đánh giá tập lệnh khi bạn đang tải nó vào bộ đệm đọc thêm về nhân viên dịch vụ tại đây

thí dụ

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

như bạn có thể thấy đây không phải là một thứ phụ thuộc vào gói web, đây là thứ nằm ngoài phạm vi của gói web, tuy nhiên với sự trợ giúp của gói web, bạn có thể chia gói của mình, điều này sẽ giúp sử dụng nhân viên dịch vụ tốt hơn


nhưng điểm đau của tôi là tôi không thể lấy url của tệp một cách dễ dàng từ webpack, ngay cả khi tôi đi với SW, tôi vẫn cần cho SW biết những tệp nào nên được lưu trước bộ đệm ... một plugin tệp kê khai webpack có thể tạo thông tin tệp kê khai vào SW, nhưng đó là tất cả trong hoạt động, có nghĩa là SW không có lựa chọn nào ngoài bộ đệm trước tất cả các tệp được liệt kê trong tệp kê khai ...
mã hóa

Lý tưởng nhất, tôi hy vọng webpack có thể thêm một nhận xét ma thuật khác như / * webpackOnlyPrefetch: true * /, vì vậy tôi có thể gọi câu lệnh nhập hai lần cho mỗi đoạn có thể tải lười biếng, một để tìm nạp trước, một để đánh giá mã và mọi thứ xảy ra theo yêu cầu.
mã hóa

1
@migcoder đó là một điểm hợp lệ (bạn không thể lấy tên tệp vì được tạo động khi chạy) sẽ xem xét mọi giải pháp nếu tôi có thể tìm thấy bất kỳ
Tripurari Shankar

@migcoder Tôi đã cập nhật câu trả lời xin vui lòng xem nó giải quyết vấn đề của bạn
Tripurari Shankar

nó giải quyết được một phần của vấn đề, nó có thể lọc ra các khối async đó là tốt, nhưng mục tiêu cuối cùng của tôi chỉ là các khối async được yêu cầu trước. Tôi hiện đang xem plugin này github.com/sebastian-software/babel-plugin-smart-webpack-import , nó chỉ cho tôi cách thu thập tất cả các báo cáo nhập khẩu và thực hiện một số sửa đổi mã dựa trên các nhận xét ma thuật, có thể tôi có thể tạo một plugin tương tự để chèn mã prefetch vào câu lệnh nhập với nhận xét ma thuật 'webpackOnlyPrefetch: true'.
mã hóa

1

Cập nhật: Tôi bao gồm tất cả những thứ vào một gói npm, hãy kiểm tra nó! https://www.npmjs.com/package/webpack-prefetcher


Sau vài ngày nghiên cứu, tôi kết thúc bằng việc viết một plugin babel tùy chỉnh ...

Nói tóm lại, plugin hoạt động như thế này:

  • Tập hợp tất cả các câu lệnh nhập (args) trong mã
  • Nếu quá trình nhập (args) chứa / * prefetch: true * / comment
  • Tìm chunkId từ câu lệnh import ()
  • Thay thế nó bằng Prefetcher.fetch (chunkId)

Prefetcher là lớp trình trợ giúp chứa tệp kê khai của gói webpack và có thể giúp chúng tôi chèn liên kết 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)
  }
}

Một ví dụ sử dụng:

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

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

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

Chi tiết về plugin này có thể tìm thấy ở đây:

Tìm nạp trước - Kiểm soát từ gói webpack

Một lần nữa, đây là một cách thực sự khó khăn và tôi không thích nó, nếu bạn muốn nhóm webpack thực hiện điều này, vui lòng bỏ phiếu tại đây:

Tính năng: nhập trước động nhập theo yêu cầu


0

Giả sử tôi hiểu những gì bạn đang cố gắng đạt được, bạn muốn phân tích và thực thi một mô-đun sau một sự kiện nhất định (ví dụ: nhấp vào nút). Bạn chỉ có thể đặt câu lệnh nhập bên trong sự kiện đó:

element.addEventListener('click', async () => {
  const module = await import("...");
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.