Tối ưu hoá hoạt động tải tài nguyên

Trong mô-đun trước, chúng tôi đã khám phá một số lý thuyết đằng sau đường dẫn hiển thị quan trọng cũng như lý do các tài nguyên chặn hiển thị và chặn trình phân tích cú pháp có thể trì hoãn quá trình kết xuất ban đầu của trang. Giờ đây, khi đã hiểu một số lý thuyết đằng sau điều này, bạn đã sẵn sàng tìm hiểu một số kỹ thuật để tối ưu hoá đường dẫn hiển thị quan trọng.

Khi một trang tải, nhiều tài nguyên sẽ được tham chiếu trong HTML của trang đó, cung cấp cho một trang giao diện và bố cục thông qua CSS, cũng như khả năng tương tác của trang đó thông qua JavaScript. Trong mô-đun này, chúng tôi sẽ đề cập đến một số khái niệm quan trọng liên quan đến những tài nguyên này và tác động của chúng đối với thời gian tải của trang.

Chặn quá trình kết xuất

Như đã thảo luận trong mô-đun trước, CSS là một tài nguyên chặn hiển thị, vì nó chặn trình duyệt hiển thị bất kỳ nội dung nào cho đến khi tạo xong Mô hình đối tượng CSS (CSSOM). Trình duyệt chặn hoạt động kết xuất để ngăn hiện tượng Flash của nội dung chưa định kiểu (FOUC) – đây là điều không mong muốn từ quan điểm trải nghiệm người dùng.

Trong video trước, có một FOUC ngắn để bạn có thể xem trang mà không cần định kiểu. Sau đó, tất cả các kiểu sẽ được áp dụng sau khi CSS của trang tải xong từ mạng và phiên bản chưa định kiểu của trang sẽ được thay thế ngay lập tức bằng phiên bản đã được tạo kiểu.

Nói chung, FOUC là thứ mà bạn thường không thấy, nhưng bạn phải hiểu rõ khái niệm này để biết lý do trình duyệt chặn hiển thị trang cho đến khi CSS được tải xuống và áp dụng cho trang. Việc chặn hiển thị không hẳn là điều không mong muốn, nhưng bạn nên giảm thiểu thời gian tồn tại bằng cách tối ưu hoá CSS.

Chặn trình phân tích cú pháp

Tài nguyên chặn trình phân tích cú pháp làm gián đoạn trình phân tích cú pháp HTML, chẳng hạn như phần tử <script> không có thuộc tính async hoặc defer. Khi trình phân tích cú pháp gặp phần tử <script>, trình duyệt cần đánh giá và thực thi tập lệnh trước khi tiếp tục phân tích cú pháp phần còn lại của HTML. Điều này là do thiết kế, vì các tập lệnh có thể sửa đổi hoặc truy cập vào DOM trong một khoảng thời gian trong khi nó vẫn đang được xây dựng.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

Khi bạn sử dụng các tệp JavaScript bên ngoài (không có async hoặc defer), trình phân tích cú pháp sẽ bị chặn từ khi tìm thấy tệp cho đến khi tệp được tải xuống, phân tích cú pháp và thực thi. Khi sử dụng JavaScript cùng dòng, trình phân tích cú pháp cũng sẽ bị chặn cho đến khi tập lệnh cùng dòng được phân tích cú pháp và thực thi.

Trình quét tải trước

Trình quét tải trước là một hoạt động tối ưu hoá trình duyệt dưới dạng một trình phân tích cú pháp HTML phụ để quét phản hồi HTML thô nhằm tìm và tìm nạp tài nguyên theo cách suy đoán trước khi trình phân tích cú pháp HTML chính phát hiện ra những tài nguyên đó. Ví dụ: trình quét tải trước sẽ cho phép trình duyệt bắt đầu tải xuống tài nguyên được chỉ định trong phần tử <img>, ngay cả khi trình phân tích cú pháp HTML bị chặn trong khi tìm nạp và xử lý các tài nguyên như CSS và JavaScript.

Để tận dụng trình quét tải trước, bạn nên đưa các tài nguyên quan trọng vào mã đánh dấu HTML do máy chủ gửi. Trình quét tải trước không thể phát hiện các mẫu tải tài nguyên sau đây:

  • Hình ảnh do CSS tải bằng thuộc tính background-image. Các tham chiếu hình ảnh này nằm trong CSS mà trình quét tải trước không phát hiện được.
  • Các tập lệnh được tải động dưới dạng mã đánh dấu phần tử <script> được chèn vào DOM bằng cách sử dụng JavaScript hoặc các mô-đun tải bằng import() động.
  • HTML hiển thị trên ứng dụng bằng JavaScript. Mã đánh dấu như vậy nằm trong các chuỗi trên tài nguyên JavaScript và trình quét tải trước không phát hiện được.
  • Nội dung khai báo @import của CSS.

Tất cả các mẫu tải tài nguyên này đều là tài nguyên được phát hiện muộn nên không được hưởng lợi từ trình quét tải trước. Hãy tránh dùng chúng bất cứ khi nào có thể. Tuy nhiên, nếu không thể tránh được các mẫu như vậy, bạn có thể sử dụng gợi ý preload để tránh sự chậm trễ trong việc khám phá tài nguyên.

CSS

CSS xác định cách trình bày và bố cục của trang. Như đã mô tả ở trên, CSS là một tài nguyên chặn hiển thị, vì vậy, việc tối ưu hoá CSS có thể tác động đáng kể đến tổng thời gian tải trang.

Giảm thiểu

Rút gọn tệp CSS giúp giảm kích thước tệp của tài nguyên CSS, giúp bạn tải xuống nhanh hơn. Điều này chủ yếu được thực hiện bằng cách xoá nội dung khỏi tệp CSS nguồn, chẳng hạn như dấu cách và các ký tự ẩn khác, rồi xuất kết quả sang một tệp mới được tối ưu hoá:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

Ở dạng cơ bản nhất, rút gọn CSS là một cách tối ưu hoá hiệu quả, giúp cải thiện FCP của trang web và thậm chí là LCP trong một số trường hợp. Các công cụ như trình tạo gói có thể tự động thực hiện việc tối ưu hoá này cho bạn trong các bản dựng chính thức.

Xoá CSS không dùng đến

Trước khi hiển thị bất kỳ nội dung nào, trình duyệt cần tải xuống và phân tích cú pháp tất cả biểu định kiểu. Thời gian cần thiết để hoàn tất quá trình phân tích cú pháp cũng bao gồm cả các kiểu chưa dùng trên trang hiện tại. Nếu bạn đang sử dụng một trình gói kết hợp tất cả tài nguyên CSS vào một tệp duy nhất, thì người dùng có thể tải nhiều CSS xuống hơn mức cần thiết để hiển thị trang hiện tại.

Để khám phá CSS không dùng đến cho trang hiện tại, hãy sử dụng công cụ Phạm vi lập chỉ mục trong Công cụ cho nhà phát triển của Chrome.

Ảnh chụp màn hình về công cụ đo lường mức độ phù hợp trong Công cụ của Chrome cho nhà phát triển. Một tệp CSS được chọn trong ngăn dưới cùng, hiển thị một lượng CSS đáng kể chưa được sử dụng trong bố cục trang hiện tại.
Công cụ về mức độ phù hợp trong Công cụ của Chrome cho nhà phát triển rất hữu ích trong việc phát hiện CSS (và JavaScript) mà trang hiện tại chưa sử dụng. Bạn có thể dùng tính năng này để chia các tệp CSS thành nhiều tài nguyên để tải bởi các trang khác nhau, thay vì gửi một gói CSS lớn hơn nhiều vì việc này có thể làm chậm trễ quá trình hiển thị của trang.

Việc xoá CSS không được sử dụng có tác động gấp đôi: ngoài việc giảm thời gian tải xuống, bạn còn đang tối ưu hoá quá trình xây dựng cây hiển thị vì trình duyệt cần xử lý ít quy tắc CSS hơn.

Tránh khai báo @import của CSS

Mặc dù cách này có vẻ thuận tiện, nhưng bạn nên tránh khai báo @import trong CSS:

/* Don't do this: */
@import url('style.css');

Tương tự như cách hoạt động của phần tử <link> trong HTML, phần khai báo @import trong CSS cho phép bạn nhập tài nguyên CSS bên ngoài từ trong một biểu định kiểu. Điểm khác biệt lớn giữa 2 phương pháp này là phần tử HTML <link> là một phần của phản hồi HTML, do đó được phát hiện sớm hơn nhiều so với tệp CSS được tải xuống bằng nội dung khai báo @import.

Lý do là để khai báo @import, bạn phải tải tệp CSS chứa tệp này trước xuống. Điều này dẫn đến việc gọi là chuỗi yêu cầu (trong trường hợp của CSS) là trì hoãn khoảng thời gian cần thiết để trang hiển thị lần đầu. Một hạn chế khác là các biểu định kiểu được tải bằng nội dung khai báo @import không thể phát hiện được bằng trình quét tải trước, do đó sẽ trở thành tài nguyên chặn hiển thị phát hiện muộn.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

Trong hầu hết trường hợp, bạn có thể thay thế @import bằng phần tử <link rel="stylesheet">. Phần tử <link> cho phép tải biểu định kiểu xuống đồng thời và giảm thời gian tải tổng thể, trái ngược với phần khai báo @import giúp tải biểu định kiểu xuống liên tiếp.

CSS quan trọng cùng dòng

Thời gian để tải tệp CSS xuống có thể làm tăng FCP của trang. Việc chèn các kiểu quan trọng trong tài liệu <head> sẽ loại bỏ yêu cầu mạng cho tài nguyên CSS và nếu thực hiện đúng cách, bạn có thể cải thiện thời gian tải ban đầu khi bộ nhớ đệm của trình duyệt của người dùng chưa được tạo sẵn. Bạn có thể tải không đồng bộ hoặc nối thêm CSS còn lại vào cuối phần tử <body>.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

Tuy nhiên, việc chèn một lượng lớn CSS sẽ làm tăng thêm nhiều byte vào phản hồi HTML ban đầu. Vì các tài nguyên HTML thường không thể được lưu vào bộ nhớ đệm trong thời gian rất lâu hoặc tất cả. Điều này có nghĩa là CSS cùng dòng không được lưu vào bộ nhớ đệm cho các trang tiếp theo có thể sử dụng cùng một CSS trong các biểu định kiểu bên ngoài. Hãy kiểm tra và đo lường hiệu suất trang để đảm bảo sự đánh đổi là xứng đáng.

Bản minh hoạ CSS

JavaScript

JavaScript thúc đẩy hầu hết hoạt động tương tác trên web, nhưng cũng có chi phí. Việc vận chuyển quá nhiều JavaScript có thể làm cho trang web của bạn phản hồi chậm trong quá trình tải trang và thậm chí có thể gây ra các vấn đề về khả năng phản hồi làm chậm tương tác – cả hai vấn đề này đều có thể gây khó chịu cho người dùng.

JavaScript chặn hiển thị

Khi tải các phần tử <script> không có thuộc tính defer hoặc async, trình duyệt sẽ chặn việc phân tích cú pháp và kết xuất cho đến khi tập lệnh được tải xuống, phân tích cú pháp và thực thi. Tương tự, các tập lệnh cùng dòng sẽ chặn trình phân tích cú pháp cho đến khi tập lệnh được phân tích cú pháp và thực thi.

async đấu với defer

asyncdefer cho phép các tập lệnh bên ngoài tải mà không chặn trình phân tích cú pháp HTML, trong khi các tập lệnh (bao gồm cả tập lệnh cùng dòng) có type="module" sẽ tự động bị trì hoãn. Tuy nhiên, asyncdefer có một số điểm khác biệt quan trọng bạn cần hiểu rõ.

Mô tả nhiều cơ chế tải tập lệnh, tất cả đều nêu chi tiết vai trò của trình phân tích cú pháp, tìm nạp và thực thi dựa trên các thuộc tính được dùng như không đồng bộ, trì hoãn, type=&#39;module&#39; và sự kết hợp của cả ba thuộc tính.
Lấy từ https://html.spec.whatwg.org/multipage/scripting.html

Các tập lệnh tải bằng async được phân tích cú pháp và thực thi ngay sau khi tải xuống, trong khi các tập lệnh tải bằng defer được thực thi khi quá trình phân tích cú pháp tài liệu HTML hoàn tất. Quá trình này xảy ra cùng lúc với sự kiện DOMContentLoaded của trình duyệt. Ngoài ra, các tập lệnh async có thể thực thi không theo thứ tự, còn tập lệnh defer được thực thi theo thứ tự xuất hiện trong mã đánh dấu.

Hiển thị phía máy khách

Nhìn chung, bạn nên tránh sử dụng JavaScript để hiển thị mọi nội dung quan trọng hoặc phần tử LCP của trang. Đây được gọi là kết xuất phía máy khách và là một kỹ thuật được sử dụng rộng rãi trong Ứng dụng trang đơn (SPA).

Mã đánh dấu do JavaScript kết xuất sẽ bước qua bước tải trước trình quét, vì tài nguyên có trong mã đánh dấu do ứng dụng kết xuất không thể phát hiện. Điều này có thể trì hoãn quá trình tải các tài nguyên quan trọng xuống, chẳng hạn như hình ảnh LCP. Trình duyệt chỉ bắt đầu tải hình ảnh LCP xuống sau khi tập lệnh đã thực thi và thêm phần tử vào DOM. Đổi lại, tập lệnh chỉ có thể được thực thi sau khi tập lệnh được khám phá, tải xuống và phân tích cú pháp. Đây được gọi là một chuỗi yêu cầu quan trọng và bạn nên tránh dùng chuỗi này.

Ngoài ra, việc hiển thị mã đánh dấu bằng JavaScript có nhiều khả năng tạo ra thao tác dài hơn so với mã đánh dấu được tải xuống từ máy chủ để phản hồi yêu cầu điều hướng. Việc sử dụng quá nhiều tính năng hiển thị HTML phía máy khách có thể ảnh hưởng tiêu cực đến độ trễ tương tác. Điều này đặc biệt đúng trong trường hợp DOM của một trang rất lớn, kích hoạt hoạt động hiển thị đáng kể khi JavaScript sửa đổi DOM.

Giảm thiểu

Tương tự như CSS, việc giảm thiểu JavaScript làm giảm kích thước tệp của tài nguyên tập lệnh. Điều này có thể giúp tải xuống nhanh hơn, cho phép trình duyệt chuyển sang quá trình phân tích cú pháp và biên dịch JavaScript nhanh hơn.

Ngoài ra, việc giảm kích thước JavaScript còn tiến thêm một bước so với việc giảm thiểu các tài sản khác, chẳng hạn như CSS. Khi được giảm kích thước, JavaScript không chỉ bị loại bỏ các nội dung như dấu cách, thẻ và nhận xét, mà các ký hiệu trong JavaScript nguồn cũng được rút ngắn. Quá trình này đôi khi được gọi là vi phạm. Để thấy sự khác biệt, hãy lấy mã nguồn JavaScript sau:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

Khi xác định nhanh mã nguồn JavaScript trước đó, kết quả có thể giống như đoạn mã sau:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

Trong đoạn mã trước, bạn có thể thấy biến scriptElement mà con người có thể đọc được trong nguồn đã được rút ngắn thành t. Khi áp dụng trên một tập hợp lớn các tập lệnh, mức tiết kiệm được có thể khá đáng kể mà không ảnh hưởng đến các tính năng mà JavaScript chính thức của trang web cung cấp.

Nếu bạn đang sử dụng một trình đóng gói để xử lý mã nguồn của trang web, thì quy trình xác minh thường được thực hiện tự động đối với các bản dựng chính thức. Các thành phần làm phiền (chẳng hạn như Terser) cũng có thể được định cấu hình cao, cho phép bạn điều chỉnh mức độ linh hoạt của thuật toán làm rõ để đạt được mức tiết kiệm tối đa. Tuy nhiên, giá trị mặc định cho mọi công cụ xác minh thường là đủ để tạo ra sự cân bằng phù hợp giữa kích thước đầu ra và việc duy trì các tính năng.

Bản minh hoạ JavaScript

Kiểm tra kiến thức

Cách tốt nhất để tải nhiều tệp CSS trong trình duyệt là gì?

Bản khai báo @import của CSS.
Hãy thử lại.
Nhiều phần tử <link>.
Chính xác!

Trình duyệt tải trước trình quét có chức năng gì?

Đây là một trình phân tích cú pháp HTML phụ kiểm tra mã đánh dấu thô nhằm tìm ra các tài nguyên trước khi trình phân tích cú pháp DOM có thể phát hiện ra những tài nguyên đó sớm hơn.
Chính xác!
Phát hiện các phần tử <link rel="preload"> trong một tài nguyên HTML.
Hãy thử lại.

Tại sao trình duyệt tạm thời chặn phân tích cú pháp HTML theo mặc định khi tải các tài nguyên JavaScript xuống?

Để ngăn chặn Flash của nội dung không định kiểu (FOUC).
Hãy thử lại.
Vì việc đánh giá JavaScript là một tác vụ tốn rất nhiều CPU và việc tạm dừng phân tích cú pháp HTML sẽ cung cấp thêm băng thông cho CPU để hoàn tất việc tải tập lệnh.
Hãy thử lại.
Vì tập lệnh có thể sửa đổi hoặc truy cập vào DOM.
Chính xác!

Tiếp theo: Hỗ trợ trình duyệt với các gợi ý về tài nguyên

Bây giờ, bạn đã nắm được cách các tài nguyên được tải trong phần tử <head> có thể ảnh hưởng đến lượt tải trang ban đầu và các chỉ số khác nhau, đã đến lúc chuyển sang phần tiếp theo. Trong mô-đun tiếp theo, bạn sẽ khám phá các gợi ý về tài nguyên và cách đưa ra gợi ý có giá trị cho trình duyệt để bắt đầu tải tài nguyên và mở kết nối tới máy chủ nhiều nguồn gốc sớm hơn so với khi trình duyệt không có các gợi ý này.