Tối ưu WordPress

Tác giả CCNACCNP, T.Tư 08, 2019, 12:17:25 CHIỀU

« Chủ đề trước - Chủ đề tiếp »

0 Thành viên và 1 Khách đang xem chủ đề.

Tối ưu WordPress


1. Giới thiệu.

Tốc độ của website ảnh hưởng trực tiếp đến trải nghiệm người dùng. Đây là một trong những vấn đề khá phức tạp, mà dường như ngày nay các dự án web không coi là việc ưu tiên hàng đầu. Chính vì thế, hôm nay mình muốn bàn sâu hơn về chủ đề này để mọi người thấy đây không phải là một việc đơn giản mà cần đầu tư nhiều thời gian công sức mới mang lại trải nghiệm tốt nhất cho người dùng.

Ở bài viết này, mình chỉ bàn về hiệu năng ở phía client-side, giả sử server-side đã đang trong điều kiện lý tưởng.

2. Các yếu tố ảnh hưởng đến Web Loading?

Trong thực tế, một website được đánh giá là nhanh hay chậm phụ thuộc vào rất nhiều yếu tố khách quan hoặc chủ quan như:
  • Network
  • Thermal Throttling
  • Parsing JavaScript
  • 3rd-party code
  • Device hardware
  • Caching
  • Images
Và còn rất nhiều yếu tố khác có thể ảnh hưởng đến tốc độ website của bạn. Từ những yếu tố trên, để có được một website nhanh phải kết hợp rất nhiều kỹ thuật/thủ thuật khác nhau, từ server-side cho đến client-side.

3. Những khuyến nghị của PageSpeed.

Đường truyền Internet cũng là một trong những nhân tố ảnh hưởng đến tốc độ tải trang của một website. Ngày nay, user sử dụng rất nhiều loại thiết bị từ laptop đến mobile, với nhiều đường truyền như 3G, 4G, ADSL... nên website cần được tối ưu nhất về dung lượng để có thể chạy tốt và nhanh trên nhiều thiết bị đầu cuối. Để giảm chi phí truyền tải tài nguyên qua internet, chúng ta nên áp dụng những quy tắc sau, các bạn cũng có thể sử dụng công cụ PageSpeed của Google tự test website của mình và nhận được những hướng dẫn để tối ưu:

Hình ảnh thể hiện cách mà resources của trang web được tải về trình duyệt
4. Chỉ tải những phần mà người dùng đang cần: Code-splitting.

Tối ưu hoặc nén những tài nguyên của website như: JS, CSS, Images.

Caching: Tìm hiểu sâu về HTTP Caching, và áp dụng thích hợp cho từng loại resouces cũng như tuỳ vào mục đích sử dụng. Các bạn tham khảo thêm tại đây: httpss://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/https-caching

5. Tốc độ khởi tạo trang web.

Tự suy luận, chúng ra cũng có thể dễ dàng biết rằng: cùng một kích thước file, nhưng chi phí cho việc chạy 1 file JavaScript so với hình ảnh hoặc Webfonts là cao hơn rất nhiều. Sau khi trình duyệt đã nhận được các file JavaScript từ server trả về, Javascript Engine bên trong trình duyệt sẽ hoạt động ngay lập tức để Parse, Compile, Execute. Nếu quá nhiều file js hoặc file quá lớn — hoặc bất cứ vấn đề nào đó mà Javascript Engine của bạn chạy chậm, thì việc này sẽ block UI và làm UX của website rất khó chịu cho người dùng.

Mình thử dùng Chrome Dev Tools để đo thử tốc độ trang.

Màu vàng là Scripting và chiếm hầu hết quá trình first-load của web app.


Thời gian cho việc Parse và Compile Javascript là khá cao
Như vậy, những script JS được trả về tại thời gian start-up của website càng nhỏ, thì tốc độ tải của website sẽ nhanh hơn. Ngoài chi phí Parse/Compile thì chi phí cho việc Execute các đoạn JavaScript cũng không kém, không những thế nó còn chạy ngay trên Main Thread của trình duyệt, vì vậy việc UI bị block chắc chắn sẽ xảy ra nếu việc thực thi này kéo dài hơn 50ms (theo Alex Russell — Tech Lead tại Google Chrome).

Vì vậy, để giảm thời gian Parse/Compile/Execute tại thời điểm start-up của website thì website chỉ nên nạp những file Javascript thật sự cần thiết, thật sự có ý nghĩa cho website ngay lúc đó. Ví dụ: không nên chèn file js ckeditor của trang post bài trong khi người dùng đang truy cập vào trang xem bài viết; hoặc đối với các web app dạng SPA tại lần đầu tải trang chúng ta chỉ cần trả về các dependencies và chỉ code client của trang chủ, các trang/thành phần khác mà người dùng chưa thấy có thể load sau bằng cách dùng Code Splitting.

Hình ảnh từ Google Developers
5. Giảm thiểu sự phức tạp và kết hợp sử dụng Web Workers

Như mình đề cập ở trên, khi một dòng lệnh được thực thi lâu Main Thread, trình duyệt sẽ block UI dẫn đến hiện tượng giật trên trang web. Để tránh ảnh hưởng đến trải nghiệm người dùng, chúng ta nên tìm ra bottleneck nằm ở đoạn code nào bằng cách sử dụng các công cụ đo đạc như tab Performance của Chrome Dev Tools, từ đó cố gắng tối ưu đoạn code đấy nhanh nhất có thể. Trong trường hợp không thể làm chúng nhanh hơn hoặc đối với các thao tác nặng như xử lý file, hình ảnh,... thì buộc chúng ta phải đưa nó ra khỏi Main Thread bằng cách sử dụng Web Workers.

Mã nguồn [Chọn]
60fps (Frame Per Second)
Ngày nay, các thiết bị thường có FPS là 60, tức khung hình sẽ được refresh 60 lần trong 1 giây. Mỗi frame có hạn mức là 1000ms/60 = 16.66ms. Trong thực tế, trình duyệt thực thi 1 việc nào đó phải được hoàn tất trong 10ms. Một khi các script/callback vượt quá hạn mứcc này — nhất là khi cuộn trang hoặc chạy animation nào đó — frame rate giảm và nội dung trang web sẽ bị giật. Điều này ảnh hưởng tiêu cực đến UX của website.

Mã nguồn [Chọn]
requestAnimationFrame()
Trước đây, để làm chuyển động (animation) bằng Javascript, chúng ta sẽ nghĩ ngay đến setTimeout() hoặc setInterval(). Thử xem đoạn code sau dùng để di chuyển một thẻ div trên website:

Mã nguồn [Chọn]
var adiv = document.getElementById('animatedDiv')
var leftpos = 0
setInterval(function(){
    leftpos += 5
    adiv.style.left = leftpos + 'px'
}, 50) // chạy liên tục mỗi 50 milliseconds

Xem sơ qua thì đoạn code trên có vẻ hoạt động tốt. Tuy nhiên, có 2 vấn đề:
  • Hàm setInterval() đang chạy mỗi lần cách nhau 50ms, nhưng thời gian thực thi không ổn định vì có thể bị ảnh hưởng với resouces máy tính của người dùng. Có thể trong quá trình animation diễn ra, user làm một việc gì khác trên máy tính nặng, dẫn đến sự không đồng đều giữa các frame: lúc thì chạy vài ms, lúc thì cả trăm ms...
  • Đoạn code trên thay đổi left của div mỗi 50ms, tức là mỗi 50ms trang web bị bắt buộc phải vẽ lại, điều này là cực kì không tốt vì setInterval có thể được chạy ngay giữa frame hình, buộc trình duyệt phải vẽ lại ngay lập tức trong khi chỉ còn khoảng vài ms nữa là trình duyệt sẽ vẽ lại. Để dễ hình dung hơn, bạn có thể tham khảo hình bên dưới.

Hình ảnh từ trang Google Developers.
Đó là lý do mà requestAnimationFrame() được sinh ra để giải quyết 2 vấn đề trên. Ở đây mình sẽ không giải thích rõ cách sử dụng. Các bạn có thể tham khảo thêm tại: httpss://www.paulirish.com/2011/requestanimationframe-for-smart-animating/

6. Sử dụng Local Caching.

Giả sử trang chủ của bạn có những dữ liệu không thay đổi thường xuyên: thay vì mỗi khi người dùng xem website, client phải request lên server để lấy dữ liệu về thì bạn có thể cache những dữ liệu đó trong localStorage hoặc indexedDB tại trình duyệt, và đánh version hoặc key gì đó để client có thể phân biệt được dữ liệu này so với server là cũ hay mới, từ đó quyết định được nên lấy dữ liệu từ cache hay gọi lên server. Điều này sẽ giảm đáng kể lượng request đến server đồng thời tối ưu thời gian tải trang cho website của bạn.

Lưu ý: Vì localStorage khi đọc/ghi sẽ sử dụng tài nguyên của Main Thread, việc đọc/ghi lượng dữ liệu nhiều cũng có thể sẽ gây ra hiện tượng block UI. Để tránh được việc này, các bạn có thể kết hợp indexedDB và web workers. Việc đọc/ghi dữ liệu sẽ được xử lý ở thread khác mà không ảnh hưởng đến UX của website.

7. Ứng dụng thực thế.

Tại phần này mình sẽ dùng trang httpss://developers.zalo.me làm ví dụ, để các bạn có thể hiểu hơn những vấn đề mình nói ở trên. Project này được build theo dạng Single Page App, sử dụng React + Redux + ... Giờ mình sẽ sử dụng Chrome Dev Tools để test thử website hiện đang tải như thế nào nhé.

Theo hình, file js được download hoàn tất lúc 2500ms, user có thể thấy được website sau 5500ms.




Các file js, css, image chưa được áp dụng HTTP Caching.


Có 1 chỗ xử lý khá lâu bên trong handler...

Từ những thông tin này, mình lần lượt đưa ra các giải pháp và hiện thực nó:
  • Sử dụng HTTP caching cho các resources JS, Images, CSS
  • Tối ưu kích thước file bundle
  • Xử lý bottleneck trong handler
8. HTTP Caching.

Sử dụng cơ chế ETag để quản lý version cho các resources.
9. Optimize bundle size.

Dùng tool Webpack bundle analyzer để test thử bundle của website. Hình bên dưới cho thấy hiện website đang sử dụng rất nhiều thư viện bên ngoài với dung lượng khá lớn, mặc dù chỉ dùng 1 thành phần nhỏ của thư viện đó. Mình sẽ tìm cách loại bỏ các thư viện không cần thiết, hoặc có dùng nhưng không nhiều ra khỏi project.

Kích thước client lúc chưa optimize
Một số tip để tối ưu kích thước khi sử dụng các lib thông dụng:
Mã nguồn [Chọn]
var times = require('lodash').times; // 72KB (min)
var times = require('lodash/times'); // 3.4KB (min)
React -> Preact
jQuery -> Zepto
Underscore -> Lodash


Kết quả sau khi hiện thực các giải pháp trên, đây là kết quả nhận được.


Như vậy, client đã được tối ưu đáng kể so với ban đầu:
  • First render giảm từ ~5s xuống còn ~1s
  • Bundle size giảm từ 1,97mb -> 836kb
  • Các resources như js, image, css được đã cache tại trình duyệt
  • Bottleneck bên trong handler đã được xử lý
10. Kết luận.

Như vậy, để tối ưu hoá vấn đề loading của website chúng ta có thể áp dụng rất rất nhiều cách khác nhau, tuỳ thuộc vào website của bạn. Trước tiên, hãy sử dụng các công cụ Chrome Dev Tools để test thử, sau đó tìm ra bottleneck đang nằm ở đâu và tìm giải pháp phù hợp nhất cho website của mình.

Dù vậy, vẫn có trường hợp website không kiểm soát hết được, dù đã dùng các biện pháp tối ưu tốt nhất có thể. Điều này còn tuỳ thuộc rất nhiều vào client và để có cái nhìn tổng quát hơn về việc website mình đang được user sử dụng thế nào, có mượt không, có nhanh không... lại là một bài toán khác. Vậy nên ở bài sau, mình sẽ nói kĩ hơn về vấn đề này, đó là làm thế nào để xây dựng hệ thống Profiling cho front-end.