+2

Tạo một project đơn giản với Spring Boot và PWA(Progressive Web Apps) - Service worker kết hợp Lighthouse extension

Trong bài viết này, tôi sẽ hướng dẫn các bạn làm thế nào để cài đặt PWA cũng như Service Worker, tôi chia làm các phần như bên dưới:

  • Tạo project với Spring Boot 3 với Thymeleaf
  • Tạo Controller - Model
  • Tạo 1 config file web manifest online
  • Tạo 1 script file service worker đơn giản

Trong bài viết này, tôi sẽ không nói lại PWA là gì, tôi chỉ tóm tắt lại một vài đặc điểm cơ bản của PWA:

  • Nếu 1 website đã applied PWA -> ví dụ khi người dùng sử dụng mobile device truy cập website từ browser -> sẽ có 1 icon PWA xuất hiện trên màn hình home(thanh địa chỉ phía bên phải) -> người dùng có thể click vào icon và chọn "Install", sau khi cài đặt thành công -> sẽ build lại thành 1 native app với icon xuất hiện bên ngoài màn hình điện thoại và cho những lần sau người dùng không cần mở browser để truy cập website nữa, thay vào đó người dùng chỉ cần mở app vừa được tạo là có thể truy cập nhanh đến website

  • Service worker, có thể hiểu đơn giản công việc của nó là để cache data, nghĩa là sau khi data được lưu cache thành công -> nếu người dùng ngoại tuyến(không cần connect network) -> vẫn có thể load dữ liệu thành công, bởi vì lúc đó hệ thống sẽ kiểm tra trong cache và thấy có data tương ứng và load lên. Nhưng điều kiện để có thể lưu cache thành công -> cho lần truy cập website đầu tiên người dùng vẫn cần phải connect network để service worker được đăng ký -> scan -> tiến hành lưu cache

  • Điều liện tiên quyết để có thể apply PWA là phải apply thành công web manifest với service worker

Công cụ và thư viện được sử dụng trong bài viết:

  • Spring tool suite

  • Spring boot 3

  • Thymeleaf

  • Maven

  • Java 17

  • Service worker script file

  • Web manifest file

  • Lighthouse extension

1. Tạo 1 project Spring boot với cấu trúc thư mục như bên dưới:

image.png

2. Nội dung pom file:

image.png

3. Nội dung Application class:

image.png

4. Nội dung controller:

Main Controller:

image.png

User Controller:

5. Nội dung User model:

image.png

Tôi tiến hành start project lên:

image.png

Tôi kiểm tra Cache Storage:

image.png

=> Hiện tại không có bất kì data nào được lưu cache

Tiếp theo, tôi sẽ test 1 kịch bản, tôi check chọn option "Offline" trên devtools và reload lại page:

image.png

=> Website không thể load vì đang ngoại tuyến

Trước khi apply web manifest và service worker, tôi tạo 1 api với method "POST" để load "name" và "message"

Tôi update lại MainController:

image.png

Tôi tạo 1 function để gửi request data trong index.html:

image.png

Tôi restart lại project và kiểm tra:

image.png

=> Call api success

6. Apply web manifest:

Trước khi apply web manifest, tôi giới thiệu một vài website hiện tại đang được apply PWA:

  • Youtube:

image.png

  • Trang chủ angular:

image.png

Để apply web manifest -> cần file config manifest, file config manifest sẽ có nội dung tương tự như bên dưới:

image.png

Trong bài viết này, tôi sẽ hướng dẫn tạo 1 file config web manifest online, truy cập website: https://manifest-gen.netlify.app/

image.png

Chúng ta chỉ cần nhập thông tin cho 1 số field bắt buộc như bên dưới:

  • App Name
  • Short Name
  • Background color
  • Start Url
  • Icon

Cho icon, các bạn có thể tự download bất kì icon nào hoặc tự design nếu muốn, sau khi có icon file -> upload icon này lên, ở đây tôi đã download sẵn 1 icon:

image.png

Sau khi upload icon thành công -> click "SUBMIT" để generate file config manifest cùng với những icons cần thiết:

image.png

Trong folder "app-image" có folder "images"(chứa icons với những kích thước tương ứng) cùng với file config web manifest:

image.png

Nội dung file web manifest:

image.png

Tiếp theo, tôi copy tất cả đến folder "static":

image.png

Tại file index.html, tôi cần khai bào đường dẫn đến file config web manifest và theme color:

image.png

Tôi restart lại Project và kiểm tra tab "Application -> Manifest" xem file config manifest được apply thành công hay chưa:

image.png

image.png

=> Web manifest config file được apply thành công

Tiếp theo, trước khi apply service worker tôi giới thiệu 1 công cụ cũng khá hay là Lighthouse, có thể cài đặt công cụ này tại đây:

https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk

Sau khi cài đặt thành công, kiểm tra tại giao diện devtools sẽ có 1 tab "Lighthouse" được thêm vào:

image.png

Tại đây, tôi chỉ muốn kiểm tra xem hiện tại tôi còn thiếu những gì để có thể apply PWA, nên tôi chỉ chọn option PWA -> click "Analyze page load":

image.png

Các bạn có thể thấy hiện tại website này còn thiếu 2 thứ nữa để có thể apply PWA thành công, tôi sẽ xem chi tiết những phần còn thiếu:

image.png

=> 2 phần còn thiếu liên quan đến service worker chưa được apply và 1 lỗi liên quan đến icon có vẻ như load chưa được, tôi sẽ kiểm tra xem phần này trước

Tôi kiểm tra tab "Network" thấy icon độ phân giải "144x144" được load thành công:

image.png

Tiếp theo tôi check tab "Application - Manifest" và thấy 1 vài cảnh báo liên quan đến độ phân giải chưa chính xác:

image.png

Tôi chỉ quan tâm đến icon với độ phân giải "144x144" và thấy chiều cao của icon chỉ có 95px -> tôi sẽ lấy về thử 1 icon khác với độ phân giải đúng "144x144", ở đây tôi lấy tạm icon từ trang "angular.io":

image.png

Tôi copy icon này vào thư mục "icons" và sửa lại thông tin icon trong config manifest:

image.png

Tôi restart lại project và kiểm tra lại xem lỗi icon được giải quyết chưa:

image.png

Các bạn để ý nếu ngay tại icon used by chrome -> icon mới được apply thành công

Tôi sẽ kiểm tra lại với Lighthouse:

image.png

=> Hiện tại, sau khi scan với Lighthouse -> chỉ còn thiếu điều kiện nữa là apply service worker -> issue icon đã được fix, ngoài ra thay vì kiếm 1 icon khác các bạn chỉ cần set size lại là được

7. Apply Service worker:

Tương tự như web manifest, chúng ta cũng cần 1 script file service worker để đăng ký, để generate 1 file service worker đơn giản, các bạn có thể làm tham khảo 1 số cách như bên dưới:

image.png

Các bạn thấy tại trang này cũng có hỗ trợ generate config file web manifest và service worker

Trong bài viết này tôi tạo 1 script file service worker với nội dung như bên dưới:

// Establish a cache name
const cacheName = 'LocalHostCache';

self.addEventListener('fetch', event => {
	  try {
	    var request = event.request;
	    
		event.respondWith(caches.open(cacheName).then((cache) => {
			  return cache.match(request).then((cachedResponse) => {
				const fetchedResponse = fetch(request).then((networkResponse) => {
				  cache.put(request, networkResponse.clone());

				  return networkResponse;
				});

				return cachedResponse || fetchedResponse;
			  });
			}));
	    
	  } catch (e) {
	    return event.respondWith(new Response('Error thrown ' + e.message));
	  }
	});	

Tiếp theo, tôi tiến hành đăng ký service worker từ script file ở trên, tại file index.html tôi thêm function:

   if ("serviceWorker" in navigator) {
      // Register a service worker hosted at the root of the
      // site using the default scope.
      navigator.serviceWorker.register("/ngsw-worker.js").then(
        (registration) => {
          console.log("Service worker registration succeeded:", registration);
        },
        (error) => {
          console.error(`Service worker registration failed: ${error}`);
        }
      );
    } else {
      console.error("Service workers are not supported.");
    }

image.png

Tham khảo thêm tại trang này: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register

Tôi restart lại project và kiểm tra:

image.png

image.png

=> Service worker được đăng ký thành công, tôi sẽ kiểm tra lại bằng Lighthouse:

image.png

Lighthouse thông báo PWA được apply thành công cho website này, tôi kiểm tra xem icon PWA có được tạo thành công hay không:

image.png

=> Icon được tạo thành công

Tiếp đến, tôi click icon và install để build 1 native app:

image.png

image.png

=> Build thành công 1 app -> mỗi lần nếu muốn truy cập website này, thay vì vào browser các bạn chỉ cần run app bên ngoài là được

Tiếp theo, tôi kiểm tra Cache Storage để thấy cache được tạo thành công hay chưa:

!image.png

image.png

Tôi sẽ kiểm tra với kịch bản ngoại tuyến(offline mode), tôi check option offline trên browser và reload lại page:

image.png

=> Các bạn thấy trong trường hợp ngoại tuyến -> data sẽ được load từ cache storage nếu tìm thấy và thời gian load rất nhanh

Trong bài viết này tôi sẽ hướng dẫn thêm làm thế nào để có thể caching data với POST method, mặc định service worker chỉ có thể cache data với GET method, có thể tham khảo điều này tại điểm 4 trang này:

https://w3c.github.io/ServiceWorker/#cache-put

image.png

Tôi sẽ thử call api với POST method và kiểm tra trong Cache Storage:

image.png

image.png

=> Service worker có scan api này nhưng không thể save cache

Giải pháp ở đây là ta sẽ convert trở về GET method và save cache, tôi cập nhật lại script file như sau:

 // Establish a cache name
const cacheName = 'LocalHostCache';

self.addEventListener('fetch', event => {
	  try {
	    var request = event.request;
	    if (event.request.method == 'POST') {
	    	  const body = event.request.clone().text();

	    	  // Create new request
	    	  const cacheUrl = new URL(event.request.url);

	    	  // Store the URL in cache 
	    	  cacheUrl.pathname = '/POST' + cacheUrl.pathname;

	    	  // Convert to a GET to be able to cache
	    	  const cacheKey = new Request(cacheUrl.toString(), {
	    	    headers: request.headers,
	    	    method: 'GET',
	    	  });
			  
			  request = cacheKey;

	    }
		event.respondWith(caches.open(cacheName).then((cache) => {
			  return cache.match(request).then((cachedResponse) => {
				const fetchedResponse = fetch(event.request).then((networkResponse) => {
				  cache.put(request, networkResponse.clone());

				  return networkResponse;
				});

				return cachedResponse || fetchedResponse;
			  });
			}));
	    
	  } catch (e) {
	    return event.respondWith(new Response('Error thrown ' + e.message));
	  }
	});	

Tôi restart lại project và kiểm tra Cache Storage:

image.png

image.png

=> Method POST được cache thành công

Tôi sẽ kiểm tra lại với kịch bản ngoại tuyến:

image.png

image.png

image.png

Dành cho những ai muốn apply PWA trong angular js, các bạn có thể sử dụng lệnh:

ng add @angular/pwa

Khi sử dụng câu lệnh này, mặc định sẽ lấy về tất cả những thứ cần thiết để apply PWA như web manifest - service worker config và icons file, cũng như sẽ tự động đăng ký service worker trong app module và add đường dẫn web manifest trong angular.json. Để test trực tiếp lại local với angular các bạn có thể cài thêm "http-server"

npm install --global http-server

Sẽ có 1 vài trường hợp các bạn khi add PWA trong angular -> chỉ có angular package được thêm vào, còn những thức khác như web manifest - icons - service worker config đều không được apply thành công -> có thể do version angular hiện tại trong dự án của các bạn -> nên cài đặt PWA tương ứng với version angular hiện tại, ví dụ trong dự án các bạn đang sử dụng angular 10 -> khi add PWA nên thêm version vào như:

 ng add @angular/pwa@0.1002.4

Một điều cần lưu ý khi apply PWA, các bạn cần đảm bảo đường dẫn khai báo trong "start_url" của web manifest phải the same như scope của config file service worker, nghĩa là ví dụ hiện tại file config service worker của tôi đang nằm trong "static" folder -> đường dẫn sẽ là: "http://localhost:8080/manifest.json" -> start_url tôi để mặc định là "/" -> nó sẽ tự hiểu là lấy theo root directory là "http://localhost:8080/" và với service worker cũng nên tương tự như vậy:
"http://localhost:8080/ngsw-worker.js" -> scope default sẽ là "http://localhost:8080/"

Nếu project của các bạn là single page application -> không cần quan tâm nhiều về điều này nhưng nếu project sử dụng kiến trúc microservices -> sẽ có 1 vài thử thách để apply service worker, một vài người sẽ call remote file config service worker từ service khác để đăng ký và sẽ gặp 1 số issue liên quan đến cors và nếu domain khác nhau -> sẽ failed, một số trường hợp có thể giải quyết bằng cách apply reverse proxy trên server

Trong thời gian tới, nếu có thời gian tôi sẽ viết 1 bài apply PWA trên project sử dụng kiến trúc microservices.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí