JavaScript phân tách mã

Việc tải các tài nguyên JavaScript lớn sẽ ảnh hưởng đáng kể đến tốc độ trang. Tách nền JavaScript của bạn thành các phần nhỏ hơn và chỉ tải xuống những phần cần thiết cho một trang hoạt động trong quá trình khởi động có thể cải thiện đáng kể tải khả năng thích ứng, nhờ đó có thể cải thiện Lượt tương tác với trang tiếp theo Tạo nội dung (INP).

Khi trang tải xuống, phân tích cú pháp và biên dịch các tệp JavaScript lớn, trang có thể trở thành không phản hồi trong một khoảng thời gian. Các phần tử trang được hiển thị, vì chúng là là một phần HTML ban đầu của một trang và được tạo kiểu theo CSS. Tuy nhiên, vì JavaScript cần thiết để hỗ trợ các phần tử tương tác đó – cũng như các tập lệnh khác được tải bởi trang—có thể đang phân tích cú pháp và thực thi JavaScript để chúng hoạt động. Chiến lược phát hành đĩa đơn kết quả là người dùng có thể cảm thấy như thể tương tác bị trì hoãn hoặc thậm chí bị hỏng hoàn toàn.

Điều này thường xảy ra khi luồng chính bị chặn, vì JavaScript được phân tích cú pháp và được biên dịch trên luồng chính. Nếu quá trình này mất quá nhiều thời gian, quy trình tương tác các phần tử trang có thể không phản hồi đủ nhanh với hoạt động đầu vào của người dùng. Một giải pháp cho vấn đề này là chỉ tải JavaScript bạn cần để trang hoạt động, trong khi trì hoãn việc tải JavaScript khác sau này thông qua một kỹ thuật được gọi là mã chia tách. Mô-đun này tập trung vào phần sau của hai kỹ thuật này.

Giảm phân tích cú pháp và thực thi JavaScript trong quá trình khởi động thông qua tính năng phân tách mã

Lighthouse đưa ra cảnh báo khi quá trình thực thi JavaScript mất hơn 2 giây và không thành công khi mất hơn 3, 5 giây. JavaScript quá mức phân tích cú pháp và thực thi là một vấn đề có thể xảy ra tại bất kỳ điểm nào trên trang vòng đời, vì tính năng này có thể làm tăng độ trễ đầu vào của lượt tương tác nếu thời điểm mà người dùng tương tác với trang trùng với thời điểm các tác vụ luồng chính chịu trách nhiệm xử lý và thực thi JavaScript là đang chạy.

Hơn thế nữa, việc thực thi và phân tích cú pháp JavaScript quá mức đặc biệt gặp vấn đề trong lần tải trang ban đầu, vì đây là thời điểm trên trang mà người dùng rất có khả năng sẽ tương tác với trang. Trên thực tế, Tổng thời gian chặn (TBT) (một chỉ số về khả năng phản hồi tải) có mối tương quan cao với INP, cho thấy rằng người dùng thường thử tương tác trong lần tải trang đầu tiên.

Bài kiểm tra Lighthouse báo cáo thời gian thực thi từng tệp JavaScript rằng các yêu cầu trên trang của bạn rất hữu ích vì có thể giúp bạn xác định chính xác có thể là đề xuất phân tách mã. Sau đó, bạn có thể tìm hiểu thêm bằng cách bằng cách sử dụng công cụ bao phủ trong Công cụ của Chrome cho nhà phát triển để xác định chính xác phần nào của JavaScript của trang không được sử dụng trong khi tải trang.

Chia tách mã là một kỹ thuật hữu ích có thể giảm JavaScript ban đầu của một trang tải trọng. Tính năng này cho phép bạn chia gói JavaScript thành hai phần:

  • JavaScript cần thiết khi tải trang và do đó không thể tải được ở bất kỳ thời điểm nào khác bất cứ lúc nào.
  • JavaScript còn lại có thể được tải sau đó, thường xuyên nhất là ở thời điểm người dùng tương tác với một phần tử tương tác nhất định trang.

Bạn có thể phân tách mã bằng cách sử dụng cú pháp import() động. Chiến dịch này cú pháp – không giống như các phần tử <script> yêu cầu một tài nguyên JavaScript nhất định trong quá trình khởi động—đưa ra yêu cầu tài nguyên JavaScript sau đó trong khoảng thời gian vòng đời của trang.

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

Trong đoạn mã JavaScript trước đó, mô-đun validate-form.mjs là chỉ được tải xuống, phân tích cú pháp và thực thi khi người dùng làm mờ bất kỳ phần nào của biểu mẫu <input> trường. Trong trường hợp này, tài nguyên JavaScript chịu trách nhiệm về việc điều khiển logic xác thực của biểu mẫu chỉ liên quan đến trang khi có nhiều khả năng được sử dụng nhất.

Các trình gói JavaScript như webpack, Parcel, Rollupesbuild có thể được định cấu hình để chia các gói JavaScript thành các phần nhỏ hơn bất cứ khi nào chúng gặp phải lệnh gọi import() động trong mã nguồn của bạn. Hầu hết các công cụ này quy trình này một cách tự động, nhưng nếu muốn xây dựng, bạn phải chọn tối ưu hoá.

Ghi chú hữu ích khi chia tách mã

Mặc dù phân tách mã là phương thức hiệu quả để giảm tranh chấp luồng chính trong lần tải trang đầu tiên, cần lưu ý một vài điều nếu bạn quyết định để kiểm tra mã nguồn JavaScript của bạn nhằm tìm ra cơ hội phân tách mã.

Sử dụng trình gói nếu có thể

Nhà phát triển thường sử dụng các mô-đun JavaScript trong quá trình phát triển ứng dụng. Đây là một điểm cải tiến xuất sắc về trải nghiệm dành cho nhà phát triển cải thiện khả năng đọc và bảo trì mã. Tuy nhiên, có một số đặc điểm hiệu suất dưới mức tối ưu có thể dẫn đến khi vận chuyển JavaScript vào giai đoạn phát hành chính thức.

Quan trọng nhất, bạn nên sử dụng trình theo gói để xử lý và tối ưu hoá nguồn của mình bao gồm cả các mô-đun mà bạn dự định phân tách mã. Bundle rất hiệu quả trong việc không chỉ áp dụng tối ưu hoá cho mã nguồn JavaScript mà còn khá hiệu quả trong việc cân bằng các cân nhắc về hiệu suất, chẳng hạn như kích thước gói so với tỷ số nén. Hiệu quả nén tăng theo kích thước gói, nhưng các trình gói dịch vụ cũng cố gắng đảm bảo rằng các gói không lớn đến mức phải nhận các công việc đòi hỏi nhiều thời gian do việc đánh giá tập lệnh.

Trình gói dịch vụ cũng giúp bạn tránh vấn đề vận chuyển một số lượng lớn các mô-đun chưa được gói qua mạng. Kiến trúc sử dụng mô-đun JavaScript thường có kích thước lớn, cây mô-đun phức tạp. Khi cây mô-đun không được nhóm, mỗi mô-đun đại diện cho một yêu cầu HTTP riêng biệt và khả năng tương tác trong ứng dụng web của bạn có thể bị trì hoãn nếu bạn sẽ không gói các mô-đun. Mặc dù có thể sử dụng Gợi ý về tài nguyên <link rel="modulepreload"> để tải cây mô-đun lớn sớm nhất nhiều nhất có thể, các gói JavaScript vẫn được ưu tiên hơn từ hiệu suất tải quan điểm của chúng tôi.

Không vô tình tắt tính năng biên dịch truyền trực tuyến

Công cụ JavaScript V8 của Chromium cung cấp một số tính năng tối ưu hoá ngay từ đầu để đảm bảo rằng mã JavaScript sản xuất của bạn tải hiệu quả nhất có thể. Một trong những phương pháp tối ưu hoá này là biên dịch truyền trực tuyến, giống như phân tích cú pháp gia tăng của HTML được truyền trực tuyến đến trình duyệt—biên dịch các đoạn mã được truyền trực tuyến JavaScript khi chúng đến từ mạng.

Bạn có một số cách để đảm bảo rằng quá trình biên dịch phát trực tuyến diễn ra cho ứng dụng web trong Chromium:

  • Chuyển đổi mã phát hành chính thức để tránh sử dụng các mô-đun JavaScript. Kệ đựng đồ có thể biến đổi mã nguồn JavaScript của mình dựa trên mục tiêu biên dịch và mục tiêu thường dành riêng cho một môi trường nhất định. V8 sẽ áp dụng tính năng truyền trực tuyến biên dịch sang bất kỳ mã JavaScript nào không sử dụng mô-đun và bạn có thể định cấu hình bộ gói để biến đổi mã mô-đun JavaScript thành cú pháp không sử dụng các mô-đun JavaScript và các tính năng của chúng.
  • Nếu bạn muốn đưa các mô-đun JavaScript vào kênh phát hành công khai, hãy sử dụng .mjs . Dù JavaScript sản xuất của bạn có sử dụng các mô-đun hay không, vẫn có không có loại nội dung đặc biệt nào cho JavaScript sử dụng các mô-đun so với JavaScript thì không. Trong trường hợp có liên quan đến V8, bạn có thể chọn không phát trực tiếp một cách hiệu quả khi bạn gửi các mô-đun JavaScript trong phiên bản chính thức bằng .js tiện ích. Nếu bạn sử dụng tiện ích .mjs cho các mô-đun JavaScript, V8 có thể đảm bảo rằng quá trình biên dịch truyền trực tuyến cho mã JavaScript dựa trên mô-đun bị hỏng.

Đừng để những cân nhắc này ngăn bạn sử dụng tính năng phân tách mã. Mã phân tách là một cách hiệu quả để giảm tải trọng JavaScript ban đầu cho người dùng, nhưng bằng cách sử dụng trình gói và biết cách bạn có thể duy trì khả năng truyền trực tuyến của V8 hành vi biên dịch, bạn có thể đảm bảo rằng mã JavaScript sản xuất của bạn nhanh chóng nhất có thể cho người dùng.

Bản minh hoạ tính năng nhập linh động

gói web

webpack đi kèm với một trình bổ trợ có tên SplitChunksPlugin, giúp bạn định cấu hình cách trình gói phân tách tệp JavaScript. webpack nhận dạng được cả câu lệnh import() linh động và câu lệnh import tĩnh. Hành vi của Có thể sửa đổi SplitChunksPlugin bằng cách chỉ định tuỳ chọn chunks trong cấu hình:

  • chunks: async là giá trị mặc định và tham chiếu đến các lệnh gọi import() động.
  • chunks: initial đề cập đến các lệnh gọi import tĩnh.
  • chunks: all bao gồm cả import() động và nhập tĩnh, cho phép bạn để chia sẻ các phân đoạn giữa lệnh nhập asyncinitial.

Theo mặc định, bất cứ khi nào webpack gặp một câu lệnh import() động. nó sẽ tạo một phân đoạn riêng cho mô-đun đó:

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

Cấu hình gói web mặc định cho đoạn mã trước đó dẫn đến hai kết quả các phần riêng biệt:

  • Phân đoạn main.js (được webpack phân loại là phân đoạn initial) bao gồm mô-đun main.js./my-function.js.
  • Phân đoạn async, chỉ bao gồm form-validation.js (chứa một tệp băm trong tên tài nguyên nếu đã định cấu hình). Phân đoạn này chỉ được tải xuống nếu và khi conditionsự thật.

Cấu hình này cho phép bạn trì hoãn việc tải phân đoạn form-validation.js cho đến khi điều đó thực sự cần thiết. Điều này có thể cải thiện khả năng phản hồi của tải bằng cách giảm tập lệnh đánh giá trong lần tải trang đầu tiên. Tải xuống và đánh giá tập lệnh cho phân đoạn form-validation.js xảy ra khi đáp ứng một điều kiện đã chỉ định trong trong trường hợp đó, mô-đun được nhập động sẽ được tải xuống. Ví dụ: điều kiện trong đó một polyfill chỉ được tải xuống cho một trình duyệt cụ thể hoặc—như trong ví dụ trước đó—mô-đun được nhập là cần thiết cho tương tác của người dùng.

Mặt khác, việc thay đổi cấu hình SplitChunksPlugin để chỉ định chunks: initial đảm bảo rằng mã chỉ được phân tách trên các đoạn ban đầu. Đây là chẳng hạn như các phân đoạn được nhập tĩnh hoặc được liệt kê trong entry của gói webpack thuộc tính. Nhìn vào ví dụ trước, đoạn thu được sẽ là kết hợp form-validation.js main.js trong một tệp tập lệnh duy nhất, dẫn đến hiệu suất tải trang ban đầu có thể kém hơn.

Bạn cũng có thể định cấu hình các tuỳ chọn cho SplitChunksPlugin để phân tách các giá trị lớn hơn thành nhiều tập lệnh nhỏ hơn – ví dụ: bằng cách sử dụng tuỳ chọn maxSize hướng dẫn webpack chia các phần thành các tệp riêng biệt nếu chúng vượt quá do maxSize chỉ định. Việc chia các tệp tập lệnh lớn thành các tệp nhỏ hơn có thể cải thiện khả năng phản hồi của quá trình tải, như trong một số trường hợp, việc đánh giá tập lệnh cần nhiều CPU công việc được chia thành các công việc nhỏ hơn, nên ít có khả năng chặn trong khoảng thời gian dài hơn.

Ngoài ra, việc tạo các tệp JavaScript lớn hơn cũng có nghĩa là các tập lệnh có nhiều khả năng bị vô hiệu hoá bộ nhớ đệm. Ví dụ: nếu bạn gửi rất nhiều tập lệnh lớn có cả mã khung và mã xử lý ứng dụng của bên thứ nhất, toàn bộ gói có thể bị vô hiệu hoá nếu chỉ khung được cập nhật, chứ không có gì khác trong tài nguyên đi kèm.

Mặt khác, các tệp tập lệnh càng nhỏ thì khả năng trả về càng lớn khách truy cập truy xuất tài nguyên từ bộ nhớ đệm, giúp trang tải nhanh hơn trên số lượt truy cập lặp lại. Tuy nhiên, các tệp nhỏ hơn sẽ được hưởng ít lợi ích hơn từ khả năng nén so với kích thước lớn đồng thời có thể tăng thời gian trọn vòng của mạng khi tải trang mà không cần bộ nhớ đệm của trình duyệt. Bạn phải cẩn thận để cân bằng giữa việc lưu vào bộ nhớ đệm hiệu quả, hiệu quả nén và thời gian đánh giá tập lệnh.

bản minh hoạ gói web

bản minh hoạ SplitChunksPlugin của gói webpack.

Kiểm tra kiến thức của bạn

Loại câu lệnh import nào được dùng khi thực hiện mã chia tách?

import() động.
Chính xác!
import tĩnh.
Hãy thử lại.

Loại câu lệnh import nào phải ở trên cùng của một mô-đun JavaScript, và không ở vị trí nào khác?

import() động.
Hãy thử lại.
import tĩnh.
Chính xác!

Khi sử dụng SplitChunksPlugin trong webpack, sự khác biệt giữa phân đoạn async và phân đoạn initial phân đoạn?

async phần được tải bằng import() động và initial phần được tải bằng cách sử dụng phương thức tĩnh import.
Chính xác!
async phân đoạn được tải bằng import tĩnh và initial phần được tải bằng cách sử dụng import().
Hãy thử lại.

Tiếp theo: Tải từng phần hình ảnh và các phần tử <iframe>

Mặc dù đây thường là một loại tài nguyên khá đắt đỏ, nhưng JavaScript không phải là chỉ loại tài nguyên bạn có thể trì hoãn việc tải. Phần tử hình ảnh và <iframe> tiềm ẩn những tài nguyên tốn kém. Tương tự như JavaScript, bạn có thể trì hoãn việc tải hình ảnh và phần tử <iframe> bằng cách tải từng phần chúng, được giải thích trong mô-đun tiếp theo của khóa học này.