The Cost Of JavaScript
Khi các website chúng ta xây dựng ngày càng phụ thuộc vào JavaScript, thỉnh thoảng chúng ta cũng phải trả giá cho những gì được gửi về phía người dùng, theo những cách không dễ nhìn thấy . Trong bài viết này, tôi sẽ nói về lý do tại sao một chút kỷ luật có thể giúp nếu bạn muốn website của mình có ...
Khi các website chúng ta xây dựng ngày càng phụ thuộc vào JavaScript, thỉnh thoảng chúng ta cũng phải trả giá cho những gì được gửi về phía người dùng, theo những cách không dễ nhìn thấy . Trong bài viết này, tôi sẽ nói về lý do tại sao một chút kỷ luật có thể giúp nếu bạn muốn website của mình có thể tải và phản ứng một cách nhanh chóng trên các thiết bị di động.
tl;dr: less code = less parse/compile + less transfer + less to decompress Dài quá ngại đọc: ít mã lệnh = ít thời gian phân tách/biên dịch + ít dung lượng trao đổi + ít phải giải nén
Khi hầu hết lập trình viên nghĩ về chi phí cho JavaScript, họ nghĩ về mặt chi phí tải và thực thi mã lệnh. Kết nối Internet của người dùng càng chậm thì gửi nhiều bytes JavaScript về phía họ càng lâu. Điều này cũng có thể là một vấn đề với cả những nước đã phát triển, vì kết nối mạng đang sử dụng của một người dùng có thể không thật sự là 3G, 4G hay WiFi. Bạn có thể đang vào mạng WiFi của một quán cà phê, nhưng đang kết nối với một hotspot di động với tốc độ 2G.
Bạn có thể giảm chi phí truyền tải JavaScript bằng cách:
- Chỉ chuyển đến người dùng phần mã lệnh cần thiết. Kỹ thuật chia mã (code-splitting) có thể hữu ích ở đây.
- Tối giản hóa mã lệnh (sử dụng Uglify cho ES5, babel-minify hay uglify-es cho ES2015)
- Nén mã lệnh tới mức tối đa, bằng cách dùng Brotli ~ q11, Zopfli hay gzip. Brotli hoàn toàn qua mặt gzip khi xét về tỉ lệ nén. Giải thuật này đã giúp cho CertSimple giảm 17% dung lượng nén tập tin JS, và LinkedIn tiết kiệm 4% thời gian tải.
- Xóa mã lệnh không dùng tới. Với DevTools code coverage, bạn có thể nhận dạng phần mã nào không được thực thi. Để loại bỏ mã nguồn không cần thiết, bạn có thể sử dụng kỹ thuật “rung cây” (tree-shaking) của Webpack, các kỹ thuật tối ưu hóa nâng cao của Closure Compiler, và các plugin hỗ trợ tỉa tót mã lệnh như lodash-babel-plugin hay ContextReplacementPlugin của Webpack dành cho các thư viện như moment.js. Sử dụng babel-preset-env và browserlist để tránh tình trạng chuyển đổi những tính năng ES2015 đã được hỗ trợ mặc định trong các trình duyệt. Những lập trình viên nhiều kinh nghiệm có thể phân tích các bản đóng gói (bundles) của Webpack và tìm cách bỏ đi những thư viện phụ thuộc không cần thiết.
- Lưu bộ đệm để giảm tải các yêu cầu mạng. Xác định thời gian sống tối ưu cho các tập tin JS (max-age) và cung cấp các token thẩm định (ETag) để tránh phải truyền tải những bytes không cần thiết. Lưu bộ đệm bằng Service Worker có thể giúp ứng dụng của bạn trở nên chủ động hơn trong trường hợp mất kết nối, đồng thời cho phép bạn truy xuất đến những tính năng đặc biệt, chẳng hạn như bộ đệm lưu trữ mã trong V8. Hãy tìm hiểu về lưu đệm dài hạn với kỹ thuật băm tên tập tin (filename hashing).
Sau khi đã tải, một trong những chi phí JavaScript nặng nề nhất là thời gian để một trình xử lý JS tiến hành phân tách/biên dịch mã nguồn. Trong Chrome DevTools, phân tách và biên dịch là những phần trong thời gian “Scripting” màu vàng, có thể thấy trong bảng Performance. Phần Bottom-Up/Call Tree cho phép xem chính xác thời gian phân tách và biên dịch mã: Trong bản Performance của Chrome DevTools, tìm đến phần Bottom-Up. Khi Runtime Call Stats trong V8 được kích hoạt, chúng ta có thể thấy thời gian cần thiết của những tiến trình như Phân Tách và Biên Dịch
Nhưng mà, tại sao điều này lại quan trọng? Mất nhiều thời gian để phân tách/biên dịch mã nguồn có thể làm chậm đi đáng kể thời gian người dùng có thể tương tác với website. Bạn càng gửi xuống nhiều tập tin JavaScript, trình duyệt càng tốn thời gian để phân tách và biên dịch trước khi website của bạn có thể tương tác được.
Ăn byte nào trả byte đó, JavaScript ngày càng trở nên đắt đỏ cho trình duyệt để xử lý hơn là một bức hình hay web font có cùng dung lượng tương ứng – Tom Dale - tác giả của Ember.js
So với JavaScript, cũng có nhiều chi phí tham gia vào quá trình xử lý một bức ảnh có dung lượng tương tự (chúng vẫn cần phải được giải mã!) nhưng đối với phần cứng của thiết bị di động trung bình, có vẻ như tác động của JS có phần tiêu cực hơn đến khả năng tương tác của website. Các byte của JavaScript và hình ảnh cần đến những chi phí rất khác nhau. Hình ảnh thường không chặn luồng chính (main thread) hay ngăn cản tương tác với các giao diện trong quá trình giải mã và hiển thị lên màn hình (rasterization). Ngược lại JS có thể làm chậm quá trình tương tác vì các chi phí phân tách, biên dịch và thực thi.
Khi chúng ta nói về phân tách và biên dịch bị chậm, ngữ cảnh rất quan trọng – vì ở đây chúng ta đang nói về những chiếc điện thoại ở phân khúc trung bình. Người dùng bình dân có thể dùng những thiết bị với CPUs và GPUs chậm chạp, hoàn toàn không có bộ đệm L2/L3 và thậm chí còn bị giới hạn bộ nhớ.
Năng lực mạng và năng lực của thiết bị thường không đi chung với nhau. Một người dùng sử dụng kết nối Fiber siêu tốc không nhất thiết phải có CPU tốt nhất để phân tách và thực thi JavaScript được gửi đến. Điều ngược lại cũng chính xác…kết nối cùi mía, nhưng CPU lại nhanh như điện. – Kristofer Baxter, LinkedIn.
Trong bài JavaScript Start-up Performance, tôi có lưu ý về chi phí phân tách một tập tin JavaScript (đơn giản) đã được giải nén có dung lượng khoảng 1MB trên phần cứng bình dân và cao cấp. Thời gian phân tách/biên tịch mã lệnh giữa chiếc điện thoại nhanh nhất với chiếc điện thoại trung bình khác nhau từ 2 đến 5 lần. Thời gian phân tách một bản đóng gói JavaScript có dung lượng 1MB (~250KB gzipped) giữa các thiết bị máy tính cá nhân và di động thuộc nhiều dòng khác nhau. Khi nhìn vào chi phí cho việc phân tách, chúng ta phải xem xét khi tập tin đã được giải nén, chẳng hạn như ~250KB gzipped khi giải nén thì khoảng 1MB.
Đối với những trang trong thực tế, như CNN.com thì sao?
Trên một chiếc iPhone 8 cao cấp thì mất khoảng 4 giây để phân tách/biên dịch JS trên CNN.com, so với khoảng 13 giây cho một chiếc điện thoại bình dân (Moto G4). Điều này có thể tác động rõ ràng đến khả năng tương tác của người dùng với website. So sánh thời gian phân tách mã nguồn trên chip A11 Bionic của Apple với Snapdragon 617 trên các thiết bị Android bình dân
Điều này nêu bật tầm quan trọng của việc kiểm thử ứng dụng trên các phần cứng trung bình (như chiếc Moto G4) thay vì chiếc điện thoại trong túi của bạn. Nói gì thì nói, ngữ cảnh cũng quan trọng: tối ưu hóa cho thiết bị và điều kiện kết nối mà người dùng của bạn có. Các ứng dụng phân tích thống kê (analytics) có thể đưa ra một cái nhìn về dòng thiết bị di động mà người dùng thực tế của bạn đang sử dụng. Thông tin này đem đến cơ hội để hiểu hơn về các điều kiện giới hạn của CPU/GPU trên các thiết bị đó.
Mà có thật là chúng ta đang gửi xuống người dùng quá nhiều JavaScript không? Hên xui