Categories Tối ưu JavaScript

Tải JavaScript của bên thứ ba hiệu quả hơn

hàng xuất khẩu được ví như JS của bên thứ ba

Hàng ngoại thì thường chất lượng cao rồi, nhưng đôi khi thuế má vào cũng không ít.

Bài viết này sẽ giúp bạn tránh được các cạm bẫy khi sử dụng JavaScript của bên thứ ba (third-party) để cải thiện thời gian tải (load time) và trải nghiệm người dùng (user experience).

Nếu JavaScript từ bên thứ ba làm chậm tốc độ tải trang của bạn, bạn có hai lựa chọn để cải thiện tốc độ, hiệu suất:

  • Loại bỏ nó, nếu nó không giúp tạo ra giá trị rõ ràng cho trang.
  • hoặc Tối ưu hóa quá trình tải.

Bài viết này giải thích cách tối ưu hóa quá trình tải JavaScript của bên thứ ba bằng các kỹ thuật sau:

  1. Sử dụng các thuộc tính asyncdefer trong thẻ <script> để tác động vào phương thức tải về;
  2. Thành lập các kết nối sớm (preconnect) đến máy chủ gốc cần được thực hiện để ưu tiên cho các tài nguyên quan trọng;
  3. Lazy-loading (tải lười) để tài nguyên không quan trọng không làm ảnh hưởng đến quá trình hiển thị ban đầu;
  4. Tối ưu hóa cách bạn phục vụ các JS của bên thứ ba.

Sử dụng async hoặc defer

Do các đoạn mã tải đồng bộ (synchronous scripts) trì hoãn cấu trúc DOMkết xuất (render), bạn luôn luôn cần phải tải các đoạn mã của bên thứ ba theo cách không đồng bộ (asynchronously) trừ khi đoạn mã phải chạy trước khi trang được kết xuất.

Các thuộc tính asyncdefer nói cho trình duyệt biết rằng nó có thể phân tích cú pháp (parsing) HTML trong khi tải JavaScript dưới dạng nền, và rồi thực thi JavaScript sau khi nó tải xong. Bằng cách này, đoạn mã được tải về không chặn cấu trúc DOM và quá trình kết xuất trang. Kết quả là người dùng có thể thấy trang tải trước khi tất cả đoạn mã (JS) được tải xong.

<script async src="script.js">
<script defer src="script.js">

Sự khác biệt giữa asyncdeferthời điểm chúng bắt đầu thực thi đoạn mã.

async

Đoạn mã JavaScript với thuộc tính async sẽ được thực thi ngay khi có cơ hội đầu tiên sau khi chúng hoàn tất tải xuống và trước sự kiện load của window. Điều này có nghĩa là đoạn mã JavaScript với thuộc tính async sẽ có thể (và gần như) không được thực thi theo thứ tự mà nó xuất hiện trong HTML. Nó cũng có nghĩa là chúng có thể làm gián đoạn quá trình xây dựng DOM nếu chúng hoàn thành quá trình tải trong khi quá trình phân tích cú pháp HTML thì vẫn đang tiếp diễn.

async JavaScript
Trong ví dụ minh họa ở hình trên, với thuộc tính async, đoạn mã được tìm nạp (fetch) song song với quá trình phân tích cú pháp HTML và không làm gián đoạn quá trình đó. Tuy nhiên khi JS tìm nạp xong, mã JS được thực thi (execute), nó sẽ làm gián đoạn (chặn / block) trình phân tích cú pháp (parsing) HTML

Chú thích:

  • parsing: phân tích cú pháp;
  • blocked: chặn;
  • fetch: tìm nạp;
  • execute: thực thi.

defer

Các đoạn mã JavaScript với thuộc tính defer được thực thi sau khi quá trình phân tích cú pháp HTML làm xong hoàn toàn, nhưng trước sự kiện DOMContentLoaded. defer đảm bảo các đoạn mã JavaScript sẽ được thực thi theo thứ tự mà chúng xuất hiện trong HTML và sẽ không làm chặn trình phân tích cú pháp.

defer JavaScript
Với defer thì JS cũng được tìm nạp song song với quá trình phân tích cú pháp HTML, nhưng kể cả khi tìm nạp xong, JS không được thực thi ngay mà phải đợi trình phân tích cú pháp HTML hoàn thành (done!) rồi thì JS mới thực thi
  • Sử dụng async nếu điều quan trọng là phải chạy (thực thi) đoạn mã sớm trong quá trình tải.
  • Sử dụng defer cho các tài nguyên ít quan trọng hơn. Ví dụ, một trình bật video nằm dưới màn hình đầu tiên.

Sử dụng các thuộc tính này có thể ảnh hưởng đáng kể đến việc tăng tốc độ website. Lấy ví dụ, Telegraph gần đây đã trì hoãn tất cả các đoạn mã của họ, bao gồm cả quảng cáo và mã phân tích để cải thiện thời gian tải quảng cáo tính trên trung bình là 4 giây.

Các đoạn mã phân tích thường được tải sớm hơn vì thế bạn sẽ không làm mất bất kỳ thông tin phân tích giá trị nào. May mắn thay, có các mẫu khởi tạo mã phân tích theo kiểu lazy trong khi vẫn giữ lại dữ liệu tải trang sớm.

Thực hiện các kết nối sớm khi máy chủ gốc bên ngoài yêu cầu

Bạn có thể tiết kiệm được từ 100-500 ms bằng cách thực hiện các kết nối quan trọng sớm hơn đến máy chủ gốc của bên thứ ba.

Có hai kiểu <link> có thể hữu ích ở đây:

  • preconnect;
  • dns-prefetch.

preconnect

<link rel=’preconnect’> thông báo với trình duyệt rằng trang có ý định thành lập kết nối tới máy chủ gốc khác, và rằng bạn muốn quá trình này bắt đầu càng sớm càng tốt. Khi yêu cầu một nguồn từ một máy chủ đã được preconnect, quá trình tải xuống sẽ được bắt đầu ngay lập tức.

<link rel="preconnect" href="https://cdn.example.com">

Lưu ý: Chỉ preconnect tới tên miền quan trọng bạn sẽ sử dụng sớm, bởi vì trình duyệt đóng bất kỳ kết nối nào không được sử dụng trong vòng 10 giây. Các preconnect không cần thiết có thể làm trì hoãn các tài nguyên quan trọng khác, vì thế cần giới hạn số lượng của các tên miền được preconnect và kiểm tra ảnh hưởng mà preconnect tạo ra sau đó.

dns-prefetch

<link rel=’dns-prefetch’> xử lý một phần nhỏ hơn so với những gì <link rel=’preconnect’> thực hiện. Thành lập kết nối (preconnect) liên quan đến việc tìm kiếm DNS và TCP handshake, và bảo mật máy chủ gốc, đàm phán TLS. Còn dns-prefetch chỉ hướng dẫn trình duyệt phân giải DNS của một tên miền cụ thể trước khi nó được gọi một cách rõ ràng.

Gợi ý preconnect được sử dụng tốt nhất chỉ cho các kết nối quan trọng nhất; với các tên miền ít quan trọng hơn của bên thứ ba, hãy sử dụng <link rel=’dns-prefetch’>.

<link rel="dns-prefetch" href="http://example.com">

Trình duyệt hỗ trợ cho dns-prefetch hơi khác so với trình duyệt hỗ trợ preconnect, vì thế dns-prefetch có thể được phục vụ như là cách dự phòng cho các trình duyệt không hỗ trợ preconnect. Sử dụng các thẻ link tách biệt để triển khai điều này theo cách an toàn:

<link rel="preconnect" href="http://example.com">
<link rel="dns-prefetch" href="http://example.com">

Lazy-load các tài nguyên của bên thứ ba

Nhúng tài nguyên của bên thứ ba có thể là nguyên nhân đóng góp lớn làm chậm tốc độ trang khi cấu trúc của nó không ổn. Nếu chúng không quan trọng hoặc nằm dưới đường biên fold (nghĩa là, nếu người dùng phải cuộn chuột xuống để xem chúng), lazy-loading là cách tốt để cải thiện chỉ số tốc độ trang. Bằng cách này, người dùng sẽ nhận được nội dung chính của trang nhanh hơn và có trải nghiệm người dùng tốt hơn.

lazy load

Một cách tiếp cận hiệu quả là lazy-load nội dung của bên thứ ba sau khi nội dung chính được tải. Các quảng cáo là ứng cử viên tốt cho cách tiếp cận này.

Quảng cáo là nguồn thu quan trọng cho nhiều website, nhưng người dùng đến website của bạn là vì nội dung. Bằng cách lazy-loading quảng cáo và phân phối nội dung chính nhanh hơn, bạn có thể làm gia tăng tổng thể về tỷ lệ phần trăm khả năng xem của quảng cáo. Lấy ví dụ, MediaVine chuyển sang lazy-loading quảng cáo và nhận thấy cải thiện 200% tốc độ tải trang. DoubleClick có hướng dẫn về cách lazy-load quảng cáo trong tài liệu chính thức của họ.

Một cách tiếp cận thay thế là tải nội dung của bên thứ ba chỉ khi người dùng cuộn chuột xuống khu vực đó của trang.

Intersection Observer là một API trình duyệt có khả năng nhận biết hiệu quả khi nào một thành phần đi vào hoặc thoát ra khung nhìn trình duyệt (browser’s viewport) và nó có thể được sử dụng để triển khai kỹ thuật này. lazysizes là một thư viện JavaScript phổ biến để tải lười ảnh và iframe. Nó hỗ trợ mã nhúng YouTube và các widget. Nó cũng có tùy chọn hỗ trợ cho IntersectionObserver.

Lưu ý: Cần cẩn thận khi lazy-load các tài nguyên bằng JavaScript. Nếu JavaScript gặp lỗi khi tải, có thể do điều kiện mạng không ổn định, các nguồn tài nguyên của bạn sẽ không được tải một chút nào.

Sử dụng thuộc tính loading cho việc lazy-loading ảnh và iframe là cách thay thế tốt cho các kỹ thuật JavaScript, và nó gần đây đã được đưa vào trong Chrome 76!

Cập nhật: hiện rất nhiều trình duyệt lớn đã hỗ trợ thuộc tính loading, ngoài ra WordPress cũng tự động thêm nó vào các ảnh.

Tối ưu hóa cách bạn phục vụ các đoạn mã của bên thứ ba

CDN hosting của bên thứ ba

Điều này phổ biến với các nhà cung cấp dịch vụ của bên thứ ba đưa ra (các) URL của file JavaScript mà họ host, thường là trên mạng phân tán nội dung (Content Delivery Network / CDN). Lợi ích của cách tiếp cận này là bạn có thể bắt đầu nhanh chóng-chỉ cần copy và paste URL- và không cần lo lắng đến chi phí bảo trì. Nhà cung cấp của bên thứ ba tự xử lý cấu hình máy chủ của họ và cập nhật mã.

Nhưng vì họ không có cùng gốc với phần còn lại của các tài nguyên trên trang của bạn, việc tải file từ CDN công cộng đi kèm với tổn thất mạng kết nối (network). Trình duyệt cần thực hiện tìm kiếm DNS, thành lập kết nối HTTP mới, và trên máy chủ gốc bảo mật, nó cần thực thi cả SSL handshake với máy chủ của nhà cung cấp.

Khi bạn sử dụng các file từ máy chủ của bên thứ ba, bạn hiếm khi có khả năng tự chủ caching. Dựa vào người khác về chiến lược caching có thể là nguyên nhân khiến đoạn mã không cần thiết phải tìm nạp lại quá thường xuyên.

Tự host các đoạn mã của bên thứ ba

Tự host các đoạn mã của bên thứ ba là một tùy chọn giúp bạn có nhiều quyền hơn với quá trình tải JavaScript. Bằng cách tự host, bạn có thể:

  • Giảm thời gian tìm kiếm DNS và vòng khứ hồi;
  • Cải thiện header HTTP caching;
  • Tận dụng lợi thế của máy chủ HTTP/2.

Lấy ví dụ, Casper đã giảm được 1,7 giây thời gian tải trang bằng cách tự host các đoạn mã kiểm tra A/B.

Tuy nhiên self-hosting (tự host) có thể có một nhược điểm lớn: các đoạn mã có thể lỗi thời và không được cập nhật tự động khi API thay đổi hoặc một vấn đề bảo mật nào đó được sửa chữa.

Lưu ý: Cập nhật các đoạn mã theo cách thủ công có thể bổ sung thêm nhiều chi phí ngầm vào quá trình phát triển của bạn và bạn có thể bỏ lỡ các cập nhật quan trọng. Nếu bạn không sử dụng CDN hosting để phục vụ tất cả các tài nguyên, bạn cũng có thể bỏ lỡ kiểu caching máy chủ biên (edge caching) và phải tối ưu hóa nén máy chủ của bạn.

P/S: Một ví dụ về tự host JS trong WordPress đó là các đoạn mã phân tích website của Google Analytics. Bạn có thể tham khảo cách plugin CAOS làm điều này.

Sử dụng dịch vụ workers để cache đoạn mã từ máy chủ của bên thứ ba

Một cách khác để tự host cho phép bạn kiểm soát bộ nhớ đệm tốt hơn trong khi vẫn nhận được lợi ích CDN của bên thứ ba đó là sử dụng dịch vụ worker để cache đoạn mã từ máy chủ của bên thứ ba. Điều này cho phép bạn làm chủ tần suất đoạn mã tìm nạp lại (re-fetch) từ mạng kết nối và làm nó có khả năng tạo ra một chiến lược tải để điều chỉnh các yêu cầu từ tài nguyên của bên thứ ba không quan trọng cho đến khi trang đạt đến thời điểm ‘key user / quan trọng với người dụng’. Sử dụng preconnect để thiết lập các kết nối sớm trong trường hợp này cũng có thể giúp giảm thiểu ảnh hưởng mạng ở một mức độ nào đó.

PS: Cloudflare mới đây triển khi dịch vụ Automatic Platform Optimization có khả cache font của bên thứ ba như Google Fonts.

(Dịch từ bài viết: Efficiently load third-party JavaScript, tác giả: Milica Mihajlija, trang web[.]dev)

Back to Top