Chiến Lược Quản Lý Bộ Nhớ Đệm Với Service Worker Trong PWA

Tổng quan về cơ chế lưu trữ

Khả năng hoạt động ngoại tuyến của Progressive Web Apps (PWA) phụ thuộc chủ yếu vào Service Worker. Thành phần này đóng vai trò như một lớp proxy mạng, cho phép chặn các yêu cầu HTTP và phục hồi dữ liệu từ bộ nhớ đệm thay vì mạng lưới internet. Điều này giúp nhà phát triển kiểm soát chính xác cách thức lưu trữ và truy xuất tài nguyên.

Dưới đây là cấu trúc cơ bản của một trang HTML đăng ký Service Worker:

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo PWA Cache</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <h1>Kiểm tra bộ nhớ đệm</h1>
        <img src="1.jpg" alt="Sample Image" />
    </div>
    <script>
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('./sw.js')
            .then(registration => {
                console.log('SW registered:', registration.scope);
            })
            .catch(error => {
                console.log('SW registration failed:', error);
            });
        }
    </script>
</body>
</html>

Lưu trữ tĩnh trong giai đoạn Cài đặt

Đối với các tài nguyên cố định như hình ảnh, CSS hoặc JS core, chúng ta có thể thực hiện lưu trữ ngay khi Service Worker được cài đặt. Phương pháp này đảm bảo các tài nguyên quan trọng đã sẵn sàng trước khi người dùng truy cập.

const STATIC_CACHE_ID = 'static-v1';
const ASSETS_TO_CACHE = ['1.jpg', 'index.html'];

self.addEventListener('install', (event) => {
    event.waitUntil((async () => {
        const cache = await caches.open(STATIC_CACHE_ID);
        await cache.addAll(ASSETS_TO_CACHE);
        // Bỏ qua giai đoạn waiting để kích hoạt ngay
        self.skipWaiting();
    })());
});

self.addEventListener('fetch', (event) => {
    event.respondWith((async () => {
        const cachedResponse = await caches.match(event.request);
        // Trả về từ cache nếu có, ngược lại gọi mạng
        return cachedResponse || fetch(event.request);
    })());
});

Lưu trữ động khi truy cập (Runtime Caching)

Trong trường hợp không thể xác định trước danh sách tài nguyên (ví dụ: dữ liệu API động), chúng ta cần chặn yêu cầu mạng trong sự kiện fetch, sau đó lưu trữ phản hồi thành công vào bộ nhớ đệm để sử dụng cho các lần sau.

Lưu ý cần clone request và response vì các đối tượng này chỉ có thể sử dụng một lần (stream).

const DYNAMIC_CACHE_ID = 'dynamic-v1';

self.addEventListener('fetch', (event) => {
    event.respondWith((async () => {
        try {
            const cache = await caches.open(DYNAMIC_CACHE_ID);
            const cached = await cache.match(event.request);
            
            if (cached) {
                return cached;
            }

            const networkRequest = event.request.clone();
            const response = await fetch(networkRequest);

            // Chỉ lưu cache nếu phản hồi thành công
            if (response && response.status === 200) {
                const responseClone = response.clone();
                await cache.put(event.request, responseClone);
            }

            return response;
        } catch (error) {
            // Xử lý lỗi mạng nếu cần
            throw error;
        }
    })());
});

Quản lý phiên bản và Công cụ hỗ trợ

Để đảm bảo người dùng luôn nhận được tài nguyên mới nhất, cần thực hiện quản lý phiên bản bộ nhớ đệm (Cache Versioning). Khi Service Worker mới được cài đặt, sự kiện activate nên được sử dụng để xóa các cache cũ dựa trên tên phiên bản hoặc hash của file.

Ngoài ra, thay vì viết thủ công các logic phức tạp trên, có thể sử dụng thư viện Workbox. Workbox cung cấp các chiến lược caching có sẵn (như Cache First, Network First, Stale While Revalidate) giúp tối ưu hóa quy trình phát triển PWA và giảm thiểu lỗi logic khi xử lý bộ nhớ đệm.

Thẻ: pwa service-worker cache-api offline-storage workbox

Đăng vào ngày 10 tháng 8 lúc 18:03