Phát triển ứng dụng web tiến bộ 02.0: Bắt đầu nhanh khi không có mạng

Lớp học lập trình này nằm trong khoá học Phát triển ứng dụng web tiến bộ do Nhóm đào tạo nhà phát triển của Google xây dựng. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự.

Để biết đầy đủ thông tin chi tiết về khoá học, hãy xem bài viết Tổng quan về việc phát triển ứng dụng web tiến bộ.

Giới thiệu

Trong phòng thí nghiệm này, bạn sẽ sử dụng Lighthouse để kiểm tra một trang web theo các tiêu chuẩn của Ứng dụng web tiến bộ (PWA). Bạn cũng sẽ thêm chức năng ngoại tuyến bằng API trình chạy dịch vụ.

Kiến thức bạn sẽ học được

  • Cách kiểm tra trang web bằng Lighthouse
  • Cách thêm các chức năng ngoại tuyến vào một ứng dụng

Những điều bạn cần biết

  • HTML, CSS và JavaScript cơ bản
  • Làm quen với Promise ES2015

Bạn cần có

  • Máy tính có quyền truy cập vào thiết bị đầu cuối/vỏ
  • Kết nối Internet
  • Trình duyệt Chrome (để sử dụng Lighthouse)
  • Trình chỉnh sửa văn bản
  • Không bắt buộc: Chrome trên thiết bị Android

Tải xuống hoặc sao chép kho lưu trữ pwa-training-labs trên github và cài đặt phiên bản LTS của Node.js (nếu cần).

Chuyển đến thư mục offline-quickstart-lab/app/ rồi khởi động một máy chủ phát triển cục bộ:

cd offline-quickstart-lab/app
npm install
node server.js

Bạn có thể chấm dứt máy chủ bất cứ lúc nào bằng cách nhấn Ctrl-c.

Mở trình duyệt rồi chuyển đến localhost:8081/. Bạn sẽ thấy rằng trang web này là một trang web tĩnh và đơn giản.

Lưu ý: Huỷ đăng ký mọi trình chạy dịch vụ và xoá tất cả bộ nhớ đệm của trình chạy dịch vụ cho localhost để chúng không gây trở ngại cho phòng thí nghiệm. Trong Công cụ của Chrome cho nhà phát triển, bạn có thể thực hiện việc này bằng cách nhấp vào Xoá dữ liệu trang web trong mục Xoá bộ nhớ của thẻ Ứng dụng.

Mở thư mục offline-quickstart-lab/app/ trong trình chỉnh sửa văn bản mà bạn muốn. Thư mục app/ là nơi bạn sẽ xây dựng phòng thí nghiệm.

Thư mục này chứa:

  • Thư mục images/ chứa hình ảnh mẫu
  • styles/main.css là biểu định kiểu chính
  • index.html là trang HTML chính cho trang web mẫu của chúng tôi
  • package-lock.jsonpackage.json theo dõi các phần phụ thuộc của ứng dụng (trong trường hợp này, phần phụ thuộc duy nhất là cho máy chủ phát triển cục bộ)
  • server.js là một máy chủ phát triển cục bộ để kiểm thử
  • service-worker.js là tệp worker dịch vụ (hiện đang trống)

Trước khi bắt đầu thay đổi trang web, hãy kiểm tra bằng Lighthouse để xem những điểm cần cải thiện.

Quay lại ứng dụng (trong Chrome) rồi mở thẻ Audits (Kiểm tra) của Công cụ cho nhà phát triển. Bạn sẽ thấy biểu tượng Lighthouse và các lựa chọn cấu hình. Chọn "Thiết bị di động" cho Thiết bị, chọn tất cả Kiểm tra, chọn một trong hai lựa chọn Điều tiết và chọn Xoá bộ nhớ:

Nhấp vào Run audits (Chạy quy trình kiểm tra). Quá trình kiểm tra mất vài phút để hoàn tất.

Giải thích

Bạn sẽ thấy một báo cáo có điểm số trong Công cụ cho nhà phát triển sau khi quá trình kiểm tra hoàn tất. Kết quả sẽ hiển thị điểm số, chẳng hạn như sau (điểm số có thể không giống hệt):

Lưu ý: Điểm số Lighthouse chỉ là số liệu ước chừng và có thể bị ảnh hưởng bởi môi trường của bạn (ví dụ: nếu bạn mở quá nhiều cửa sổ trình duyệt). Điểm số của bạn có thể không hoàn toàn giống với điểm số được hiển thị ở đây.

Phần Ứng dụng web tiến bộ sẽ có dạng như sau:

Báo cáo này có điểm số và chỉ số trong 5 danh mục:

  • Ứng dụng web tiến bộ
  • Hiệu suất
  • Hỗ trợ tiếp cận
  • Các phương pháp hay nhất
  • SEO

Như bạn có thể thấy, ứng dụng của chúng tôi có điểm số thấp trong danh mục Ứng dụng web tiến bộ (PWA). Hãy cải thiện điểm số của chúng ta!

Hãy dành chút thời gian xem qua phần PWA của báo cáo và xem những gì còn thiếu.

Đăng ký trình chạy dịch vụ

Một trong những lỗi được liệt kê trong báo cáo là không có trình chạy dịch vụ nào được đăng ký. Chúng ta hiện có một tệp trình chạy dịch vụ trống tại app/service-worker.js.

Thêm tập lệnh sau vào cuối index.html, ngay trước thẻ đóng </body>:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js')
      .then(reg => {
        console.log('Service worker registered! 😎', reg);
      })
      .catch(err => {
        console.log('😥 Service worker registration failed: ', err);
      });
  });
}
</script>

Giải thích

Mã này đăng ký tệp trình chạy dịch vụ service-worker.js trống sau khi trang đã tải. Tuy nhiên, tệp trình chạy dịch vụ hiện tại đang trống và sẽ không làm gì cả. Hãy thêm mã dịch vụ trong bước tiếp theo.

Tài nguyên lưu vào bộ nhớ đệm trước

Một lỗi khác được liệt kê trong báo cáo là ứng dụng không trả về mã trạng thái 200 khi không có mạng. Chúng ta cần cập nhật worker dịch vụ để giải quyết vấn đề này.

Thêm mã sau vào tệp worker dịch vụ (service-worker.js):

const cacheName = 'cache-v1';
const precacheResources = [
  '/',
  'index.html',
  'styles/main.css',
  'images/space1.jpg',
  'images/space2.jpg',
  'images/space3.jpg'
];

self.addEventListener('install', event => {
  console.log('Service worker install event!');
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => {
        return cache.addAll(precacheResources);
      })
  );
});

self.addEventListener('activate', event => {
  console.log('Service worker activate event!');
});

self.addEventListener('fetch', event => {
  console.log('Fetch intercepted for:', event.request.url);
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});

Bây giờ, hãy quay lại trình duyệt và làm mới trang web. Kiểm tra bảng điều khiển để xem service worker:

  • đã đăng ký
  • đã cài đặt
  • Đã kích hoạt

Lưu ý: Nếu bạn đã đăng ký trình chạy dịch vụ trước đó hoặc gặp vấn đề khi nhận tất cả các sự kiện, hãy huỷ đăng ký mọi trình chạy dịch vụ rồi làm mới trang. Nếu cách này không hiệu quả, hãy đóng tất cả các phiên bản của ứng dụng rồi mở lại.

Tiếp theo, hãy kết thúc máy chủ phát triển cục bộ trong dòng lệnh bằng cách chạy Ctrl + c. Làm mới trang web một lần nữa và quan sát xem trang web có tải được hay không ngay cả khi máy chủ đang ở trạng thái ngoại tuyến!

Lưu ý: Bạn có thể thấy một lỗi trên bảng điều khiển cho biết không thể tìm nạp trình chạy dịch vụ: An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED. Lỗi này xuất hiện do trình duyệt không thể tìm nạp tập lệnh của worker dịch vụ (vì trang web đang ở chế độ ngoại tuyến), nhưng điều này là bình thường vì chúng ta không thể dùng worker dịch vụ để lưu chính nó vào bộ nhớ đệm. Nếu không, trình duyệt của người dùng sẽ bị kẹt với cùng một worker dịch vụ mãi mãi!

Giải thích

Sau khi trình chạy dịch vụ được tập lệnh đăng ký đăng ký trong index.html, sự kiện install của trình chạy dịch vụ sẽ xảy ra. Trong sự kiện này, trình nghe sự kiện install sẽ mở một bộ nhớ đệm có tên và lưu các tệp được chỉ định bằng phương thức cache.addAll vào bộ nhớ đệm. Đây được gọi là "lưu trước vào bộ nhớ đệm" vì hoạt động này diễn ra trong sự kiện install, thường là lần đầu tiên người dùng truy cập vào trang web của bạn.

Sau khi được cài đặt và nếu không có trình chạy dịch vụ nào khác đang kiểm soát trang, thì trình chạy dịch vụ mới sẽ được "kích hoạt" (trình nghe sự kiện activate sẽ được kích hoạt trong trình chạy dịch vụ) và bắt đầu kiểm soát trang.

Khi một trang do một trình thực thi dịch vụ đã kích hoạt kiểm soát yêu cầu tài nguyên, các yêu cầu sẽ đi qua trình thực thi dịch vụ, giống như một proxy mạng. Sự kiện fetch sẽ được kích hoạt cho mỗi yêu cầu. Trong trình chạy dịch vụ, trình nghe sự kiện fetch sẽ tìm kiếm trong bộ nhớ đệm và phản hồi bằng tài nguyên được lưu vào bộ nhớ đệm nếu có. Nếu tài nguyên không được lưu vào bộ nhớ đệm, thì tài nguyên sẽ được yêu cầu bình thường.

Việc lưu tài nguyên vào bộ nhớ đệm cho phép ứng dụng hoạt động mà không cần kết nối mạng bằng cách tránh các yêu cầu mạng. Giờ đây, ứng dụng của chúng ta có thể trả về mã trạng thái 200 khi không có mạng!

Lưu ý: Sự kiện kích hoạt không được dùng cho mục đích nào khác ngoài việc ghi nhật ký trong ví dụ này. Sự kiện này được đưa vào để giúp gỡ lỗi các vấn đề về vòng đời của worker service.

Không bắt buộc: Bạn cũng có thể xem các tài nguyên được lưu vào bộ nhớ đệm trong thẻ Ứng dụng của Công cụ cho nhà phát triển bằng cách mở rộng phần Bộ nhớ đệm:

Khởi động lại máy chủ phát triển bằng node server.js và làm mới trang web. Sau đó, hãy mở lại thẻ Audits (Kiểm tra) trong Công cụ cho nhà phát triển và chạy lại quy trình kiểm tra Lighthouse bằng cách chọn New Audit (Kiểm tra mới) (dấu cộng ở góc trên bên trái). Khi quá trình kiểm tra hoàn tất, bạn sẽ thấy điểm PWA của chúng tôi đã cải thiện đáng kể, nhưng vẫn có thể cải thiện thêm. Chúng tôi sẽ tiếp tục cải thiện điểm số của mình trong phần tiếp theo.

Lưu ý: Bạn không bắt buộc phải thực hiện phần này vì việc kiểm thử Biểu ngữ cài đặt ứng dụng web nằm ngoài phạm vi của phòng thí nghiệm. Bạn có thể tự thử bằng cách sử dụng tính năng gỡ lỗi từ xa.

Điểm PWA của chúng ta vẫn chưa cao. Một số lỗi còn lại được liệt kê trong báo cáo là người dùng sẽ không được nhắc cài đặt ứng dụng web của chúng tôi và chúng tôi chưa định cấu hình màn hình chờ hoặc màu thương hiệu trong thanh địa chỉ. Chúng tôi có thể khắc phục những vấn đề này và từng bước triển khai tính năng Thêm vào màn hình chính bằng cách đáp ứng một số tiêu chí bổ sung. Điều quan trọng nhất là chúng ta cần tạo một tệp kê khai.

Tạo tệp kê khai

Tạo một tệp trong app/ có tên là manifest.json rồi thêm đoạn mã sau:

{
  "name": "Space Missions",
  "short_name": "Space Missions",
  "lang": "en-US",
  "start_url": "/index.html",
  "display": "standalone",
  "theme_color": "#FF9800",
  "background_color": "#FF9800",
  "icons": [
    {
      "src": "images/touch/icon-128x128.png",
      "sizes": "128x128"
    },
    {
      "src": "images/touch/icon-192x192.png",
      "sizes": "192x192"
    },
    {
      "src": "images/touch/icon-256x256.png",
      "sizes": "256x256"
    },
    {
      "src": "images/touch/icon-384x384.png",
      "sizes": "384x384"
    },
    {
      "src": "images/touch/icon-512x512.png",
      "sizes": "512x512"
    }
  ]
}

Các hình ảnh được tham chiếu trong tệp kê khai đã được cung cấp trong ứng dụng.

Sau đó, hãy thêm HTML sau vào cuối thẻ <head> trong index.html:

<link rel="manifest" href="manifest.json">

<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">

Quay lại trang web. Trong thẻ Ứng dụng của Công cụ cho nhà phát triển, hãy chọn mục Xoá bộ nhớ rồi nhấp vào Xoá dữ liệu trang web. Sau đó, hãy làm mới trang. Bây giờ, hãy chọn mục Manifest. Bạn sẽ thấy các biểu tượng và lựa chọn cấu hình được định cấu hình trong tệp manifest.json. Nếu bạn không thấy các thay đổi của mình, hãy mở trang web ở chế độ ẩn danh rồi kiểm tra lại.

Giải thích

Tệp manifest.json cho trình duyệt biết cách tạo kiểu và định dạng một số khía cạnh tiến bộ của ứng dụng, chẳng hạn như chrome của trình duyệt, biểu tượng trên màn hình chính và màn hình khởi động. Bạn cũng có thể dùng chế độ này để định cấu hình ứng dụng web mở ở chế độ standalone, giống như một ứng dụng gốc (nói cách khác là bên ngoài trình duyệt).

Tính năng hỗ trợ vẫn đang được phát triển cho một số trình duyệt tại thời điểm viết bài này và các thẻ <meta> sẽ định cấu hình một số tính năng trong số này cho một số trình duyệt chưa được hỗ trợ đầy đủ.

Chúng tôi phải Xoá dữ liệu trang web để xoá phiên bản cũ đã lưu vào bộ nhớ đệm của index.html (vì phiên bản đó không có đường liên kết đến tệp kê khai). Hãy thử chạy một quy trình kiểm tra Lighthouse khác và xem điểm số PWA đã cải thiện được bao nhiêu!

Kích hoạt lời nhắc cài đặt

Bước tiếp theo để cài đặt ứng dụng của chúng tôi là hiển thị lời nhắc cài đặt cho người dùng. Chrome 67 tự động nhắc người dùng, nhưng kể từ Chrome 68, lời nhắc cài đặt sẽ được kích hoạt theo phương thức lập trình để phản hồi một cử chỉ của người dùng.

Thêm nút và biểu ngữ "Cài đặt ứng dụng" vào đầu index.html (ngay sau thẻ <main>) bằng mã sau:

<section id="installBanner" class="banner">
    <button id="installBtn">Install app</button>
</section>

Sau đó, hãy tạo kiểu cho biểu ngữ bằng cách thêm các kiểu sau vào styles/main.css:

.banner {
  align-content: center;
  display: none;
  justify-content: center;
  width: 100%;
}

Lưu tệp. Cuối cùng, hãy thêm thẻ tập lệnh sau vào index.html:

  <script>
    let deferredPrompt;
    window.addEventListener('beforeinstallprompt', event => {

      // Prevent Chrome 67 and earlier from automatically showing the prompt
      event.preventDefault();

      // Stash the event so it can be triggered later.
      deferredPrompt = event;

      // Attach the install prompt to a user gesture
      document.querySelector('#installBtn').addEventListener('click', event => {

        // Show the prompt
        deferredPrompt.prompt();

        // Wait for the user to respond to the prompt
        deferredPrompt.userChoice
          .then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
              console.log('User accepted the A2HS prompt');
            } else {
              console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
          });
      });

      // Update UI notify the user they can add to home screen
      document.querySelector('#installBanner').style.display = 'flex';
    });
  </script>

Lưu tệp. Mở ứng dụng trong Chrome trên thiết bị Android bằng tính năng gỡ lỗi từ xa. Khi trang tải, bạn sẽ thấy nút "Cài đặt ứng dụng" (bạn sẽ không thấy nút này trên máy tính, vì vậy hãy nhớ kiểm thử trên thiết bị di động). Nhấp vào nút này và lời nhắc Thêm vào màn hình chính sẽ xuất hiện. Làm theo các bước để cài đặt ứng dụng trên thiết bị của bạn. Sau khi cài đặt, bạn có thể mở ứng dụng web ở chế độ độc lập (bên ngoài trình duyệt) bằng cách nhấn vào biểu tượng mới tạo trên màn hình chính.

Giải thích

Mã HTML và CSS sẽ thêm một biểu ngữ và nút ẩn mà chúng ta có thể dùng để cho phép người dùng kích hoạt lời nhắc cài đặt.

Sau khi sự kiện beforeinstallprompt kích hoạt, chúng tôi sẽ ngăn chặn trải nghiệm mặc định (trong đó Chrome 67 trở về trước tự động nhắc người dùng cài đặt) và ghi lại beforeinstallevent trong biến deferredPrompt chung. Sau đó, nút "Cài đặt ứng dụng" sẽ được định cấu hình để hiện lời nhắc bằng phương thức prompt() của beforeinstallevent. Sau khi người dùng đưa ra lựa chọn (cài đặt hay không), lời hứa userChoice sẽ được thực hiện theo lựa chọn của người dùng (outcome). Cuối cùng, chúng ta sẽ hiển thị nút cài đặt khi mọi thứ đã sẵn sàng.

Bạn đã tìm hiểu cách kiểm tra các trang web bằng Lighthouse và cách triển khai các chức năng cơ bản khi không có mạng. Nếu đã hoàn thành các phần không bắt buộc, bạn cũng đã tìm hiểu cách cài đặt ứng dụng web vào màn hình chính!

Tài nguyên khác

Lighthouse là nguồn mở! Bạn có thể phân nhánh, thêm các bài kiểm thử của riêng mình và báo cáo lỗi. Lighthouse cũng có sẵn dưới dạng công cụ dòng lệnh để tích hợp với các quy trình xây dựng.

Để xem tất cả các lớp học lập trình trong khoá đào tạo PWA, hãy xem Lớp học lập trình chào mừng cho khoá học này/