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

Trong mô-đun trước, chúng ta đã tìm hiểu một số lý thuyết đằng sau đường dẫn hiển thị quan trọng và cách 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ể làm chậm quá trình hiển thị ban đầu của trang. Giờ đây, khi đã hiểu một số lý thuyết cơ bản về vấn đề 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 kết xuất quan trọng.

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

Chặn hiển thị

Như đã thảo luận trong mô-đun trước, CSS là một tài nguyên chặn hiển thị, vì tài nguyên này chặn trình duyệt hiển thị mọi nội dung cho đến khi Mô hình đối tượng CSS (CSSOM) được tạo. Trình duyệt chặn quá trình hiển thị để ngăn chặn Flash of Unstyled Content (FOUC) (Nội dung chưa được tạo kiểu xuất hiện chớp nhoáng), điều này không mong muốn theo quan điểm trải nghiệm người dùng.

Trong video trước, có một FOUC ngắn, trong đó bạn có thể thấy trang mà không có bất kỳ kiểu nào. Sau đó, tất cả các kiểu sẽ được áp dụng sau khi CSS của trang hoàn tất quá trình tải từ mạng và phiên bản chưa được tạo kiểu của trang sẽ được thay thế ngay bằng phiên bản đã được tạo kiểu.

Nói chung, bạn thường không thấy FOUC, nhưng bạn cần hiểu rõ khái niệm này để biết tại sao trình duyệt chặn quá trình kết xuất 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 nhất thiết là không mong muốn, nhưng bạn nên giảm thiểu thời gian chặn 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> mà không có thuộc tính async hoặc defer. Khi trình phân tích cú pháp gặp một 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. Đây là một thiết kế có chủ ý, vì các tập lệnh có thể sửa đổi hoặc truy cập vào DOM trong thời gian mà DOM vẫn đang được tạo.

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

Khi 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ệp được phát hiện 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 nội tuyến, trình phân tích cú pháp cũng bị chặn cho đến khi tập lệnh nội tuyến đượ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 phương pháp tối ưu hoá trình duyệt dưới dạng trình phân tích cú pháp HTML phụ. Trình phân tích cú pháp này quét phản hồi HTML thô để tìm và tìm nạp tài nguyên một 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 chúng. Ví dụ: trình quét tải trước sẽ cho phép trình duyệt bắt đầu tải một tài nguyên được chỉ định trong phần tử <img> xuống, 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 và không thể được trình quét tải trước phát hiện.
  • Các tập lệnh được tải động ở dạng mã đánh dấu phần tử <script> được chèn vào DOM bằng JavaScript hoặc các mô-đun được tải bằng import() động.
  • HTML được kết xuất trên máy khách bằng JavaScript. Cú pháp đánh dấu như vậy nằm trong các chuỗi trong tài nguyên JavaScript và không được trình quét tải trước phát hiện.
  • Khai báo @import 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, do đó không được hưởng lợi từ trình quét tải trước. Tránh sử dụng các từ này bất cứ khi nào có thể. Tuy nhiên, nếu không thể tránh những mẫu như vậy, bạn có thể sử dụng gợi ý preload để tránh tình trạng chậm trễ khi khám phá tài nguyên.

CSS

CSS xác định cách trình bày và bố cục của một trang. Như đã mô tả trước đó, 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ể có tác động đáng kể đến tổng thời gian tải trang.

Giảm kích thước

Việc giảm thiểu kích thước tệp CSS sẽ làm giảm kích thước tệp của một tài nguyên CSS, giúp tải xuống nhanh hơn. Việc 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ư khoảng trắng và các ký tự không nhìn thấy khác, đồng thời xuất kết quả vào 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, việc giảm kích thước CSS là một phương pháp tối ưu hoá hiệu quả có thể cải thiện FCP của trang web và thậm chí cả LCP trong một số trường hợp. Các công cụ như bundler có thể tự động thực hiện quy trình tối ưu hoá này cho bạn trong các bản dựng sản xuất.

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 tất cả các biểu định kiểu. Thời gian cần thiết để hoàn tất việc phân tích cú pháp cũng bao gồm các kiểu không được dùng trên trang hiện tại. Nếu đang sử dụng một trình kết hợp để kết hợp tất cả các tài nguyên CSS thành một tệp duy nhất, thì người dùng của bạn có thể đang tải xuống nhiều CSS 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ụ mức độ sử dụng trong Công cụ cho nhà phát triển của Chrome.

Ảnh chụp màn hình công cụ mức sử dụng 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, cho thấy một lượng lớn CSS không được bố cục trang hiện tại sử dụng.
Công cụ mức sử dụng trong Chrome DevTools rất hữu ích để phát hiện CSS (và JavaScript) mà trang hiện tại không 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 mà các trang khác nhau sẽ tải, thay vì gửi một gói CSS lớn hơn nhiều có thể làm chậm quá trình kết xuất trang.

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

Tránh khai báo @import CSS

Mặc dù 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, khai báo @import trong CSS cho phép bạn nhập một tài nguyên CSS bên ngoài từ trong một biểu định kiểu. Điểm khác biệt chính giữa hai phương pháp này là phần tử HTML <link> nằm trong phản hồi HTML, do đó được phát hiện sớm hơn nhiều so với tệp CSS do một khai báo @import tải xuống.

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

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

Trong hầu hết các trường hợp, bạn có thể thay thế @import bằng cách sử dụng phần tử <link rel="stylesheet">. Các 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ể, không giống như các khai báo @import, tải biểu định kiểu xuống liên tiếp.

CSS quan trọng trong dòng

Thời gian tải tệp CSS xuống có thể làm tăng FCP của một trang. Việc chèn các kiểu quan trọng vào tài liệu <head> sẽ loại bỏ yêu cầu mạng đối với tài nguyên CSS và – khi được thực hiện đúng cách – có thể cải thiện thời gian tải ban đầu khi bộ nhớ đệm của trình duyệt người dùng chưa được chuẩn bị. CSS còn lại có thể được tải không đồng bộ hoặc được thêm 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>

Nhược điểm là việc nội tuyến một lượng lớn CSS sẽ thêm nhiều byte hơn vào phản hồi HTML ban đầu. Vì tài nguyên HTML thường không thể được lưu vào bộ nhớ đệm trong thời gian dài (hoặc hoàn toàn không thể lưu vào bộ nhớ đệm), điều này có nghĩa là CSS nội tuyến 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 biểu định kiểu bên ngoài. Kiểm tra và đo lường hiệu suất của trang để đảm bảo rằng những điểm đánh đổi đó xứng đáng với công sức bạn bỏ ra.

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 điều này phải trả giá. Việc vận chuyển quá nhiều JavaScript có thể khiến trang web của bạn phản hồi chậm trong quá trình tải trang, thậm chí có thể gây ra các vấn đề về khả năng phản hồi làm chậm các hoạt động tương tác – cả hai điều này đều có thể khiến người dùng cảm thấy khó chịu.

JavaScript chặn hiển thị

Khi tải các phần tử <script> mà 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 nội tuyến 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 so 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 nội tuyến) có type="module" sẽ tự động bị hoãn lại. Tuy nhiên, asyncdefer có một số điểm khác biệt mà bạn cần nắm rõ.

Hình ảnh minh hoạ nhiều cơ chế tải tập lệnh, tất cả đều mô tả chi tiết các 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 nhiều thuộc tính được dùng, chẳng hạn như async, defer, type=&#39;module&#39; và tổ hợp của cả ba.
Nguồn: https://html.spec.whatwg.org/multipage/scripting.html

Các tập lệnh được tải bằng async sẽ được phân tích cú pháp và thực thi ngay lập tức sau khi tải xuống, trong khi các tập lệnh được tải bằng defer sẽ được thực thi khi quá trình phân tích cú pháp tài liệu HTML hoàn tất – điều này xảy ra đồng thời 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ự, trong khi các tập lệnh defer được thực thi theo thứ tự xuất hiện trong mã đánh dấu.

Kết xuất phía máy khách

Thông thường, 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 một 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).

Phần đánh dấu do JavaScript kết xuất sẽ bỏ qua trình quét tải trước, vì trình quét này không thể phát hiện các tài nguyên có trong phần đánh dấu do máy khách kết xuất. Điều này có thể làm chậm quá trình tải xuống các tài nguyên quan trọ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. Đến lượt, tập lệnh chỉ có thể được thực thi sau khi được phát hiện, tải xuống và phân tích cú pháp. Đây được gọi là chuỗi yêu cầu quan trọng và bạn nên tránh sử dụng.

Ngoài ra, việc hiển thị mã đánh dấu bằng JavaScript có nhiều khả năng tạo ra các tác vụ dài hơn là 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 rộng rãi phương thức kết xuất phía máy khách của HTML 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 có kích thước rất lớn, điều này sẽ kích hoạt quá trình kết xuất đáng kể khi JavaScript sửa đổi DOM.

Giảm kích thước

Tương tự như CSS, việc giảm thiểu JavaScript sẽ 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 quá trình tải xuống diễn ra 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 kích thước các tài sản khác, chẳng hạn như CSS. Khi được rút gọn, JavaScript không chỉ bị loại bỏ những thành phần như khoảng trắng, thẻ tab và nhận xét mà các biểu tượng trong JavaScript nguồn cũng được rút ngắn. Quá trình này đôi khi được gọi là uglification (làm xấu mã). Để xem 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 mã nguồn JavaScript ở trên được rút gọn, kết quả có thể trông 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 mà con người có thể đọc được scriptElement trong nguồn được rút ngắn thành t. Khi được áp dụng cho một tập hợp lớn các tập lệnh, mức tiết kiệm có thể khá đáng kể mà không ảnh hưởng đến các tính năng mà JavaScript sản xuất của một 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ì quá trình rút gọn thường được thực hiện tự động cho các bản dựng phát hành công khai. Các công cụ làm rối mã nguồn (chẳng hạn như Terser) cũng có khả năng định cấu hình cao, cho phép bạn điều chỉnh mức độ mạnh mẽ của thuật toán làm rối mã nguồn để đạt được mức tiết kiệm tối đa. Tuy nhiên, các giá trị mặc định cho mọi công cụ làm rối mã thường đủ để đạt được sự cân bằng phù hợp giữa kích thước đầu ra và khả năng bảo toàn.

Bản minh hoạ JavaScript

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

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

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

Trình quét tải trước của trình duyệ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ô để phát hiện 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 các 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 theo mặc định, trình duyệt tạm thời chặn việc phân tích cú pháp HTML khi tải tài nguyên JavaScript xuống?

Để ngăn chặn hiện tượng Flash of Unstyled Content (FOUC).
Hãy thử lại.
Vì việc đánh giá JavaScript là một tác vụ rất tốn CPU và việc tạm dừng phân tích cú pháp HTML sẽ giúp CPU có thêm băng thông để 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 bằng gợi ý về tài nguyên

Giờ đây, khi 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 quá trình tải trang ban đầu và nhiều chỉ số, đã đến lúc chuyển sang bước tiếp theo. Trong mô-đun tiếp theo, chúng ta sẽ khám phá gợi ý về tài nguyên và cách các gợi ý này có thể cung cấp những gợi ý có giá trị cho trình duyệt để bắt đầu tải tài nguyên và mở các kết nối đến các máy chủ trên 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.