# Optimize performance for Web

⚡️ Tags: 📍Performance

Câu hỏi muôn thuở đi phỏng vấn chắc gặp hoài lun :v kkkk nhưng không đơn thuần là đối phó, mà là để ghi nhớ rồi sau này áp dụng hoy

# 💎 KIM CHỈ NAM

overview

👍 Cái mục đích, mục tiêu khi mà tối ưu website đó là nhanh hơn, mượt hơn, xì mút hơn... chủ yếu là tạo thêm phần sung sướng tột đỉnh cho người dùng 🌟

Để mà có thể tối ưu được website hiệu quả, các bạn nên biết được khi người dùng gõ mywebsite.com trên trình duyệt rồi enter sẽ xảy ra chuyện gì? Chuyện gì xảy ra trên Browser, chuyện gì trên server, chuyện gì xảy ra trên internet :v ahihi

✌️ Mình có thể ví dụ ra như sau:

  • Browser sẽ tìm tới DNS Server -> coi địa chỉ IP của domain
  • Browser sẽ tạo request tới server đích (để tải HTML)
  • Trên server khi nhận request của Browser sẽ xử lý đưa ra HTML response... rồi gửi lại Browser
  • Browser nhận HTML ... parse HTML từ trên xuống dưới, và nếu gặp phải resource (CSS, JS, Fonts, Images,...) nào đó để hiển thị thì tạo request để tải các resource theo một độ ưu tiên... nào đó =))

🤘 Một vài phương thức để hiện thực hóa chuyện đó như là

  • giảm số lượng, số lần request tới server của browser
  • giảm khối lượng & thời gian response
  • giảm sự ngăn cản render trên browser
  • xử lý trước một số thao tác khi CPU rãnh...

# ⚡️ Gzip

  • Cách này server sẽ trả về những file đã được nén lại. Cách hoạt động của Gzip thì được hỗ trợ bởi các Browser 4.0 hết roài, khỏi lo.
  • Để kích hoạt cái này cũng tùy vào cái platform bạn sử dụng :v có thể cấu hình trong .htaccess, apache, nginx, ...
  • Thấy response trả về có: Accept-Encoding: gzip, ... là hiểu được nén rồi. Lưu ý: đừng để sót các đuôi extension file từ image tới fonts, tới html, css, js, cứ j mà trả về server là nén hết đi =))
  • Tool kiểm tra: Link (opens new window) thấy còn Uncompressed page size là có nghĩa có phần còn chưa nén =))

# ⚡️ HTTP Cache Header

  • Cách này là thêm vào vài thuộc tính lên header của response js, css, fonts, images,... để kích hoạt công năng đặc biệt của browser... Link tham khảo (opens new window)
  • Cách làm thì tùy vào cái công nghệ bạn làm luôn. Có thể là .htaccess, apache, nginx, hoặc là trong code ...

# ⚡️ Resource hints (opens new window)

  • preload: đặt độ ưu tiên cao khi browser load tài nguyên, ko chặn render. Thường sử dụng cho css, font, image.
  • prefetch: sau khi load xong những resouce cần thiết, browser sẽ nạp ngầm những preconnect chuẩn bị sử dụng trong tương lai.
  • prefetch-dns: DNS requests thì rất nhỏ, nhưng độ trễ đôi khi là rất lớn. Đây là giải pháp cho vấn đề đó.
  • preconnect: cái này giống như trên nhưng gồm 3 bước DNS lookups, TLS negotiations, TCP handshakes. Làm nhiều hơn cái trên 2 bước cuối.
  • prerender: cái này thì hơi dữ dằng :v cho ngầm load trước cái trang luôn, bao gồm js, css, ...

# ⚡️ Script defer / async

  • Chúng ta thường đem hết các thẻ <script> về cuối footer để tránh blocking sự render của browser.
  • Nay HTML5 cho chúng ta có nhiều cách hơn
    • <script async > load đồng bộ, và browser làm việc browser, script làm việc script, script load xong tự execute =))) thường sử dụng cho: Google Analytics, Sentry (opens new window), User Log, ...
    • <script defer > khi nào document parsed xong thì mới execute script
    • <script> chạy mặc định, khi nào script chạy xong thì parse DOM tiếp

# ⚡️ CDN (Content Delivery Network)

Mình sẽ ko giải thích CDN là gì và làm gì đâu? Mình chỉ giải thích tại sao nó có thể giảm số lần request thôi.

  • Vd bạn có 2 website: webcuatui.comsub.webcuatui.com cả 2 website đều sử dụng JQuery giống nhau nhưng URI khác nhau http://webcuatui.com/assets/js/jquery.jshttp://sub.webcuatui.com/assets/js/jquery.js. Điều này khiến browser load 2 lần nếu truy cập vào 2 web.
  • Thế cho nên khi sử dụng CDN bạn sẽ sử dụng chung 1 URI thì browser chỉ load lần đầu thôi.
  • Cách nào cũng ưu nhược điểm riêng... ví dụ khi sử dụng CDN thì mất công phải mất công truy cập DNS Server, phụ thuộc vào 3rd party, tốn tiền =)) ...

# HTTP Version

Đổi version xịn sò 😨 cho chạy nhanh hơn này. Nghe giang hồ đồn đại do mấy anh to Google làm ra, có mấy cơ chế ngon nghẻ mà chưa có cơ hội xài =))

Why is HTTP/2 faster than HTTP/1.1? (opens new window)

# ⚡️ CSS Sprite

  • Cách này là gôm các hình ảnh thành 1 tấm, rồi sử dụng css background xử lý... mục đích để giảm bớt request tới server
  • Bài văn mẫu (opens new window)

# ⚡️ Internal CSS

  • Nghĩa là đem hết CSS vào vào trong HTML, không dùng external thẻ link
  • Vì browser xem đó là tài nguyên chặn sự render

Note:

  • Tất nhiên phải sửa lại các đường dẫn image, @font-face các kiểu.
  • Cách này luyện không cẩn thận sẽ bị tẩu hỏa nhập ma 😜 File css mà cả chục MB thì đây là sai sách.

# ⚡️ Fonts

  • Case 1: bạn xài fonts awesome bạn có muốn sử dụng có 10 icon mà phải load tới 300 cái icon khác không. Tất nhiên ai cũng bảo là *éo rồi nhưng làm như thế nào? Tool này sẽ giúp bạn chỉ chọn những icon mà bạn muốn. IcoMoon (opens new window)

  • Case 2: bạn xài google fonts mà lại muốn tải font đó zìa host của bạn để bạn có thể tự lo phần cache. Tìm mãi mới ra cái tool này (opens new window)

  • Case 3: bạn desiner đưa bạn 1 cái template khoảng 7,8 font custom gì đó, solution nhanh trong case này là bạn nên gửi 1 lời cảnh báo tới PM và ép thèng designer làm lại 1 template khác ít fonts hơn. Thế nhưng ... PM vẫn cứng đầu thì sao? Solution: hãy display font System trước, sau đó font custom load xong thì hiển thị nó lên với 1 dòng code CSS.

@font-face { 
    font-family: Helvetica; 
    font-display: swap; /* Mặt trời chân lý là đây */
}

À ... thèng bên Microsoft chưa hỗ trợ tính năng này nhé =))) Nhưng có hơn ko... cứ thêm vào đi =))

# ⚡️ Hình ảnh

Hình ảnh là thứ nặng nề nhất trong khi browser render á :v

# ⚡️ Không load dư thừa

ví dụ:

var _ = require('lodash'); // không nên load full như này 

var at = require('lodash/at'); /// cần gì load đó thôi :D 
  • Icon Fonts thì IcoMoon (opens new window) mình có nói ở trên rồi
  • Cách khổ dâm: Chrome có nhận biết được dòng code nào đã được chạy trên JS và CSS
    1. Trên Chrome mở devtools lên ( Command + Option + J )
    2. Mở search lên ( Command + Shift + P )
    3. Gõ vào "Show Coverage" > Click vào
    4. Nhìn dưới đáy có cái toolbar > Click vào tab "Coverage"
    5. Kéo cái thanh đó lên và nhìn xem dòng code JS & CSS nào được sử dụng =)) rãnh thì vào code xóa bớt mấy chỗ thừa đi... Lưu ý là nhiều khi nó đỏ nhưng mà nó chưa được kích hoạt lên thôi, như CSS thì có hover. JS thì có trigger on click

# ⚡️ Lazy load

Cách này sẽ không tạo ra request tới server khi mà chưa cần thiết, tránh tắc nghẽn giao thông =))

  • Lazy load hình ảnh khi nào scroll sắp đến mới load src của img lên.
  • Lazy load video :v đừng cho nó autoplay =))
  • Cách implement thì: Mình xài thư viện này, vừa có feature lazy load vừa có feature Responsive images lun lazysizes (opens new window)

# ⚡️ ADM (Asynchronous Module Definition)

# ⚡️ Bundle Assets

# ⚡️ Minimize Assets

  • Xóa hết các ký tự khoảng trắng, tab, xuống hàng dư thừa đối với các file html, css, js, json, ...
  • Đổi các tên biến dài thòn trong JS. Ví dụ:
var total = number1 + number2; 
/// rewrite
var t = n + m;

Trừ mấy bạn thất tình, đen bạc, thì mới làm thủ công bước này thôi. Chứ các build tool nó support tận răng mấy bước này roài. 😄

Ví dụ như mình sử dụng Laravel, mình sẽ sử dụng Middleware: Link tham khảo (opens new window)

<?php namespace App\Http\Middleware;

class AfterMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // implement code how to filter $response 

        return $response;
    }
}

Còn như khi làm với Codeigniter, mình cần định nghĩa lại cách display. Link tham khảo (opens new window)

<?php
// Compress output
$hook['display_override'][] = array(
	'class' => '',
	'function' => 'compress',
	'filename' => 'compress.php',
	'filepath' => 'hooks'
);

# Upload hiệu quả

# Presigned URL

Đại loại là tính năng upload hình ảnh mà hình ảnh đang chứa ở AWS S3. Backend ko cần nhận trực tiếp nguyên file từ Frontend, rồi tự mình dùng tấm thân mỏng manh của BE đẩy chiếc file nặng nề ấy lên S3. Chúng ta sẽ dùng công thức của AWS đưa ra đó là Presigned URL

  • FE hash md5 cái content file, rồi tên file, gửi lên BE xin cái Presigned URL
  • BE tạo ra Presigned URL
  • FE dùng Presigned URL upload file lên AWS S3

Full ko che - Generate a presigned URL in modular AWS SDK for JavaScript (opens new window)

# Upload multipart

Nếu bạn cần upload 1 cái file "biệt thự", thì cứ chiến thuật chia để trị nhé. Hoặc mạng tắt bật như công tắc thì bí kiếp AWS Docs nói răng

  • If you're uploading large objects over a stable high-bandwidth network, use multipart upload to maximize the use of your available bandwidth by uploading object parts in parallel for multi-threaded performance.
  • If you're uploading over a spotty network, use multipart upload to increase resiliency to network errors by avoiding upload restarts. When using multipart upload, you need to retry uploading only the parts that are interrupted during the upload. You don't need to restart uploading your object from the beginning.

Chi tiết tại đây: Uploading and copying objects using multipart upload (opens new window)

# ⚡️ Service worker

Thèng này có tên là "công nhân dịch vụ" hoạt động ngầm underground =)) Giao tiếp với overground qua postMessage, cái này cũng giống iframe.

Muốn sử dụng được tính năng này thì điều tiên quyết đó là tự cung... cấp https :v Vì thằng "công nhân dịch vụ" này có công năng đặc dị có thể thay đổi các response trên http nên cần https để giới hạn tài năng nó lại =)) Google bảo thế (opens new window)

Và nó cũng là xương sống trong việc làm 1 PWA (web có thể dùng offline ). Nhưng mà để nó hợp thức hóa, liên quan hóa với chủ đề tối ưu website thì chỉ nên chú trọng vào phần Backgroun sync & Cache request thôi nhé :v

# ⚡️ CSS Rendering Performance

Link tựa vào (opens new window)

Khi browser render thì cần trải qua 5 bước:

  • Javascript > Style calculations > Layout > Paint > Compositing

Cách tối ưu là sử dụng CSS property hợp lý để có thể skip được bước nào hay bước đó =)) Trong 5 bước này thì 2 bước đầu là fixed. Còn bước Layout nặng nhất vì kiểu gì cũng phải Paint lại, Compositing lại.

Đây là danh sách các thuộc tính CSS (opens new window)

Túm nhẹ một số CSS chi phí rẻ mạt:

/* Position */
selector {
    transform: translate(n px, n px);
}
/* Scale */
selector {
    transform: scale(n);
}
/* Rotation */
selector {
    transform: rotate(n deg);
}
/* Opacity */
selector {
   opacity: 0 -> 1;
}

# ⚡️ CSS BEM (opens new window)

Đây là cách viết code hướng module trong CSS, hướng tới việc giảm độ sâu, độ dài của selector bằng cách đặt cái tên dài hơn, chi tiết hơn =))) Tất nhiên là giảm được cái độ phức tạp của selector thì CPU rãnh hơn, rãnh hơn thì web nhanh hơn =))

# ⚡️ Short polling, Long polling, WebSocket

Xem xét nên đổi một số công nghệ implement hay không? Vd: feature nhận thông báo giống facebook, thay vì bạn làm Short polling, cứ mỗi 5s request lên server 1 lần để kiểm tra dữ liệu mới nhất... thì xem xét có nên đổi sang long polling hay websocket. Tất nhiên là làm cái gì cũng có cái giá của nó =))

# ⚡️ Sử dụng Tools

Cách này thì kiểu làm bài tập, đưa website cho tools chấm. Làm tới khi nào điểm cao thì thôi

Tool mình hay xài:

# ⚡️ Cache tại Backend

Viết gì nhỉ =)) Có thể là bạn đang sử dụng 1 framework backend nào đó và có đủ thứ docs về Cache. Nào là memcache, redis ... đọc rất là easy nhưng mà implement nó là cả 1 vùng trời kiến thức, kiến trúc, sáng kiến, cao kiến, phong kiến lun á =)))

Nói vậy thôi để implement Cache thì chỉ cần biết vài chỗ này thôi =))

  • Nơi xử lý Cache miss
  • Nơi xử lý Cache hit
  • Nơi xử lý Cache remove
  • Nơi xử lý Cache refresh

Tuy nhiên, khi sử cache thì đây cũng sẽ là thứ bạn cần phải quan tâm. Cache (Redis / Memcache /...) trở thành 1 storage kế tiếp cạnh chiếc Database thân yêu. Đồng nghĩa tại đây chiếc app của bạn đã bước lên con đường trở thành Distributed System. Bạn sẽ đối diện với nhiều thứ hơn như là Consistency hay Availability các kiểu.

Ví dụ Availability cho case Cache Miss Storm (opens new window)

Hoặc Consistency cho case cache invalidation

There are only two hard things in Computer Science: cache invalidation and naming things.

― Phil Karlton

# 🔗 Tối ưu Database

📕 Xem bài này

# 🔗 Tối ưu backend sâu hơn

Optimize Performance at Backend


Những cách lỗi thời =))

# ☔️ BasketJS

BasketJS (opens new window) Cái thư viện này sẽ load file JS qua AJAX sau đó cất vào trong localStorage tại browser và lần sau không request lên server nữa...

# ☔️ Deferred CSS

<noscript id="deferred-styles">
    <link href="http://yourwebsite/assets/css/main.min.css?v=7.9" rel="stylesheet">
</noscript> 
 
 <script>
  var loadDeferredStyles = function() {
    var addStylesNode = document.getElementById("deferred-styles");
    var replacement = document.createElement("div");
    replacement.innerHTML = addStylesNode.textContent;
    document.body.appendChild(replacement)
    addStylesNode.parentElement.removeChild(addStylesNode);
  };
  var raf = requestAnimationFrame || mozRequestAnimationFrame ||
      webkitRequestAnimationFrame || msRequestAnimationFrame;
  if (raf) raf(function() { window.setTimeout(loadDeferredStyles, 0); });
  else window.addEventListener('load', loadDeferredStyles);
</script>

Bỏ qua mấy vụ phân tán, load balancing, microservice gì đi nhé =))

To be continued... Khi phát hiện gì mới thì update tiếp :v