Giảm JavaScript bundle và script của bên thứ ba
JavaScript là loại byte đắt đỏ nhất trên web. Một hình ảnh cùng dung lượng chỉ cần được giải mã, trong khi một script phải được tải xuống, parse, biên dịch và thực thi, và tất cả diễn ra trên main thread mà khách truy cập cần để chạm và cuộn. Vì vậy cắt giảm những gì bạn gửi đi là một trong những cách đáng tin cậy hơn để cải thiện độ phản hồi. Hướng dẫn này nói về cách xem bên trong bundle có gì, các cách chuẩn để thu nhỏ nó, và cách giữ cho script của bên thứ ba không phá hỏng công sức đó.
Vì sao dung lượng quan trọng ngoài thời gian tải
Bài viết của DebugBear về kích thước bundle liệt kê các hệ quả dây chuyền của tệp lớn: truyền chậm hơn trên kết nối kém, thời gian parse và thực thi dài hơn ngay cả trên kết nối nhanh, và hành vi cache tệ hơn, vì bundle lớn thay đổi thường xuyên hơn và bị vô hiệu hóa thường xuyên hơn. Trên mobile, chi phí xử lý thường là phần lớn hơn. Các long task từ 50 ms trở lên chặn cả việc xử lý input lẫn render, đó là lý do các script khởi động nặng xuất hiện trong Interaction to Next Paint.
Bước 1: xem trước khi cắt
- Kiểm tra dependency mới trước khi cài. Các công cụ như BundlePhobia và tiện ích editor Import Cost cho biết một package sẽ thêm bao nhiêu, kể cả các dependency của chính nó.
- Trực quan hóa bundle. Công cụ treemap như Webpack Bundle Analyzer cho thấy cả mã của bạn lẫn các package, và thường hé lộ những bất ngờ. Một ví dụ kinh điển là thư viện xử lý ngày tháng đóng gói mọi locale.
- Đo mã không dùng đến. Bảng Coverage trong Chrome DevTools đánh dấu những gì không được thực thi trong một phiên, và hoạt động với source map nên bạn có thể thấy tệp nguồn nào là thủ phạm.
Bước 2: tách những gì chưa cần ngay
Code splitting nghĩa là lần tải trang đầu tiên chỉ mang những gì trang đó dùng. import() động là viên gạch nền tảng: module được tải khi quá trình thực thi chạm tới dòng đó. Ví dụ của web.dev là một thư viện chỉ cần khi gửi form, nên không cần đi cùng bundle ban đầu.
const form = document.querySelector('#contact-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const { validateAndSend } = await import('./contact-submit.js');
await validateAndSend(new FormData(form));
});
Tách theo route dễ lập luận hơn tách từng thư viện: mỗi trang trở thành một chunk riêng, và nhiều công cụ framework làm việc này cho bạn. Mã vendor thường được đặt vào một chunk riêng, cache lâu vì nó ít thay đổi hơn mã ứng dụng. Nếu một chunk quan trọng bị tách ra, hãy preload nó để lợi ích không bị triệt tiêu bởi một lần tải muộn.
Bước 3: import ít hơn
Tree shaking loại bỏ các export không ai dùng, nhưng chỉ khi các import được viết theo cách bundler có thể phân tích. Kéo cả một thư viện tiện ích vào bằng một import gom tất cả có thể kéo theo mọi thứ, trong khi named import từ một bản build hỗ trợ module cho phép các phần không dùng rơi rụng.
// Heavier: may include the whole library
import _ from 'lodash';
const names = _.map(users, 'name');
// Lighter: plain JS where possible
const names = users.map((u) => u.name);
Có hai cải thiện nhỏ hơn đáng kiểm tra. Hãy bọc mã chỉ dùng khi phát triển trong các điều kiện môi trường để minifier có thể xóa nó, và xem lại các browser target của bạn, vì transpile cho trình duyệt rất cũ sẽ thêm mã trợ giúp. DebugBear ước tính việc thu hẹp danh sách trình duyệt được hỗ trợ có thể cắt bundle 5 đến 15 phần trăm.
Bước 4: thay script bằng tính năng của nền tảng
Bài viết về hiệu năng của MDN, viết cùng DebugBear, ánh xạ một số mẫu script thông dụng sang các tính năng native:
- Carousel thường có thể dựng bằng CSS scroll snapping.
- Phát hiện phần tử nằm trong viewport thuộc về Intersection Observer.
- Ảnh và iframe hoãn tải có thể dùng thuộc tính
loadingnative. - Popup có thể dùng Popover API.
- Layout phức tạp là việc của CSS Grid.
- Chuyển trang có thể dùng View Transitions API.
Mức hỗ trợ cho một số thành phần mới hơn, như anchor positioning và scroll-driven animation, vẫn chưa đồng đều, vì vậy hãy suy giảm êm (degrade gracefully) và kiểm thử trên các trình duyệt bạn không dùng hằng ngày.
Bước 5: kiểm soát script của bên thứ ba
Các tag cho analytics, chat, đánh giá và A/B testing thường là phần đóng góp lớn nhất, và bạn không kiểm soát mã của chúng. Hướng dẫn của web.dev về cách tải chúng quy về một vài thói quen:
- Tải chúng bằng
asynchoặcdeferđể không chặn parser.asyncchạy ngay khi tệp đến, không đảm bảo thứ tự;deferchờ đến khi parse xong và giữ nguyên thứ tự. - Chỉ dùng
preconnectcho các origin thực sự quan trọng, vì trình duyệt đóng các kết nối nhàn rỗi sau khoảng mười giây. Dùngdns-prefetchnhư một gợi ý nhẹ hơn cho phần còn lại. - Lazy-load các widget nằm dưới màn hình đầu như embed và khối đánh giá, bằng thuộc tính native hoặc Intersection Observer.
- Cân nhắc tự host một script mà bạn phụ thuộc, giúp bạn kiểm soát caching header, đổi lại bạn phải tự cập nhật nó.
<link rel="preconnect" href="https://widgets.example.com">
<link rel="dns-prefetch" href="https://widgets.example.com">
<script src="https://widgets.example.com/loader.js" defer></script>
Lưu ý rằng bài viết về bên thứ ba của web.dev có từ năm 2019, nên hãy coi các con số case study cụ thể của nó là dữ liệu lịch sử và các nguyên tắc của nó vẫn đúng.
Giữ cho nó không phình lên trở lại
Hãy theo dõi kích thước bundle trong continuous integration để một pull request thêm dependency lớn được nhìn thấy trước khi merge. Định kỳ audit tag manager và các app đã cài để tìm những script mà không ai nhớ đã thêm. Nếu bạn muốn một đánh giá từ bên ngoài về những gì storefront hoặc site của bạn đang tải, hãy xem dịch vụ của chúng tôi, hoặc duyệt các theme của chúng tôi để tìm những bản build khởi đầu gọn nhẹ.