"Hãy nén gọn (minify) JavaScript" là một trong những mục đầu tiên của mọi danh sách tối ưu hiệu năng, thường kèm một con số phần trăm ấn tượng. Con số đó có thật nhưng được đo trên sai đại lượng. Trình duyệt hiếm khi tải tệp thô: máy chủ gửi tệp đã nén bằng gzip hoặc Brotli, và việc nén đã loại bỏ phần lớn những gì minify loại bỏ. Bài này đo cả hai trên tệp thật, giải thích vì sao có khoảng chênh, nói về những phần của minify thật sự đáng giá, và về source map giúp gỡ lỗi mã đã nén. Các con số lấy từ công cụ nén gọn HTML, CSS và JS và thư viện zlib của Node.js 22.
Số đo
Ba tệp thật, được nén gọn chỉ bằng cách bỏ khoảng trắng và chú thích (đúng việc công cụ làm), rồi so với bản minify đầy đủ của esbuild 0.28, vốn còn đổi tên biến và viết lại cú pháp:
| Tệp | Thô | Bỏ khoảng trắng | Minify đầy đủ |
|---|---|---|---|
React 19.2 react.development.js | 47.219 B | 32.393 B (−31%) | 20.727 B (−56%) |
| như trên, sau gzip | 10.755 B | 9.174 B (−15%) | 7.340 B (−32%) |
js-yaml 4.3 js-yaml.js | 131.628 B | 79.318 B (−40%) | 44.797 B (−66%) |
| như trên, sau gzip | 22.424 B | 19.168 B (−15%) | 15.143 B (−32%) |
sonner 2.0 styles.css | 17.681 B | 15.143 B (−14%) | |
| như trên, sau gzip | 3.297 B | 3.052 B (−7%) |
Có hai điều nổi bật. Thứ nhất, bỏ khoảng trắng giảm 30 đến 40% một tệp JavaScript viết tay, nhưng chỉ khoảng 15% lượng dữ liệu thật sự truyền đi. Thứ hai, tệp CSS vốn đã khá gọn nên mức giảm nhỏ hơn ở mọi cột.
Vì sao gzip "ăn" gần hết phần tiết kiệm
Gzip (thuật toán DEFLATE) làm hai việc: thay các chuỗi byte lặp lại bằng tham chiếu ngắn về phía trước, và mã hóa ký hiệu hay gặp bằng ít bit hơn. Thụt lề là thứ lặp lại nhiều nhất trong mã nguồn: một dòng bắt đầu bằng tám dấu cách chỉ tốn vài bit khi tám dấu cách đó đã từng xuất hiện. Chú thích là văn xuôi và cũng nén tốt. Vì vậy xóa chúng là xóa những byte mà gzip vốn đang lưu rất rẻ.
Thứ gzip không xóa được là thông tin. Tên dài như formattedPrice lặp lại nên nén được, nhưng mỗi lần xuất hiện vẫn tốn một tham chiếu. Đó là lý do bộ minify đầy đủ, vốn đổi tên biến như thế thành một chữ cái, vẫn tiết kiệm thêm 17 điểm phần trăm sau gzip: nó bỏ đi thông tin thật chứ không chỉ sự lặp lại.
Khi nào những byte còn lại đáng kể
Chênh lệch 1,6 KB sau gzip là vô hình trên mạng nhanh và nhỏ trên mạng chậm. Minify quan trọng hơn ở:
- Thời gian phân tích và biên dịch. Trình duyệt phân tích tệp đã giải nén, nên 32 KB phân tích nhanh hơn 47 KB. Trên điện thoại cấu hình thấp, chi phí này thường lớn hơn chi phí tải.
- Mã nội tuyến.
<script>hay<style>trong HTML là một phần của phản hồi HTML, và nếu HTML không được lưu đệm thì nó bị tải lại mỗi lần xem trang. - Nơi không có nén. HTML trong email, widget nhúng, tệp lưu trong cơ sở dữ liệu hoặc gửi qua API không nén.
- Giới hạn kích thước. Một số nền tảng giới hạn số ký tự của đoạn mã, bookmarklet hay ô mã tùy biến.
Một bộ minify an toàn không được thay đổi gì
Bỏ khoảng trắng nghe thì đơn giản, nhưng JavaScript có bẫy:
a + +b(a cộng số b) không được thànha++b, vốn là lỗi cú pháp, vàa - -bkhông được thànha--b.returntheo sau là dấu xuống dòng sẽ trả vềundefined; nối hai dòng lại sẽ trả về giá trị ở dòng sau. Tương tự với dấu xuống dòng trước++, và sauthrow,break,continue.x = a / b / ccó hai phép chia, cònx = /b/glà biểu thức chính quy. Bộ minify đoán sai sẽ gộp khoảng trắng bên trong regex.- Template literal giữ nguyên khoảng trắng: `
a blà chuỗi khác vớia b`. 1 .toString()cần dấu cách của nó, vì1.sẽ bị đọc là một số.
Trong CSS, calc(100% - 10px) cần dấu cách quanh dấu trừ, và a :hover (mọi phần tử đang được trỏ chuột bên trong a) khác với a:hover. Trong HTML, dấu cách giữa </b> <i> là chữ hiển thị, còn dấu cách giữa </li> <li> thì không.
Các bài kiểm tra của công cụ kiểm chứng điều này một cách nghiêm ngặt: nó nén các thư viện thật như bản development của React rồi dùng một bộ phân tích JavaScript độc lập để xác nhận kết quả có đúng cùng cây cú pháp với bản gốc.
Source map
Một bundle đã minify rất khó gỡ lỗi: mọi thứ nằm trên vài dòng, và nếu có đổi tên thì mỗi tên chỉ còn một chữ cái. Source map là một tệp JSON riêng, ánh xạ mỗi vị trí trong tệp đã nén về dòng và cột trong mã gốc, cùng tên gốc. Tệp đã nén trỏ tới nó bằng một chú thích ở cuối:
//# sourceMappingURL=app.min.js.mapCông cụ dành cho nhà phát triển của trình duyệt chỉ tải map khi đang mở, nên người dùng bình thường không tốn gì. Hai lưu ý thực tế: map thường chứa toàn bộ mã nguồn của bạn, nên hãy quyết định có muốn công khai hay không; và mọi công cụ viết lại tệp đã nén sau đó (kể cả công cụ này, vốn bỏ chú thích) đều làm mất hoặc làm sai ánh xạ. Hãy tạo source map bằng công cụ build, đừng làm tay.
Những lỗi hay gặp
- So kích thước thô. Luôn so kích thước sau gzip hoặc Brotli, thứ người dùng thật sự tải. Công cụ hiển thị cả hai.
- Minify mã đã minify. Gần như không giảm thêm gì mà còn làm source map sai.
- Xóa chú thích giấy phép. Nhiều giấy phép mã nguồn mở yêu cầu giữ thông báo đi cùng mã. Hãy giữ các chú thích
/*! … */. - Mong một bộ bỏ khoảng trắng đổi tên biến. Phần tiết kiệm lớn đến từ đổi tên và loại bỏ mã chết, những việc cần bundler.
- Quên bật nén trên máy chủ. Bật gzip hoặc Brotli tiết kiệm nhiều hơn bất kỳ bộ minify nào; hãy kiểm tra header phản hồi
Content-Encoding.
Thử ngay
Dán một tệp vào công cụ nén gọn HTML, CSS và JS và so sánh số liệu thô với số sau gzip. Nếu phần giảm sau gzip chỉ vài phần trăm, tệp vốn đã ổn; nếu máy chủ của bạn hoàn toàn không nén, hãy sửa điều đó trước.