Tạo kịch bản cho trình chạy dịch vụ

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

Lớp học này hướng dẫn bạn tạo một trình chạy dịch vụ đơn giản và giải thích vòng đời của trình chạy dịch vụ.

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

  • Tạo một tập lệnh cơ bản cho trình chạy dịch vụ, cài đặt tập lệnh đó và gỡ lỗi đơn giản

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

  • JavaScript và HTML cơ bản
  • Các khái niệm và cú pháp cơ bản của Promise ES2015
  • Cách bật Developer Console

Những thông tin bạn cần chuẩn bị trước khi bắt đầu

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 service-worker-lab/app/ rồi khởi động một máy chủ phát triển cục bộ:

cd service-worker-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/.

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 service-worker-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:

  • below/another.html, js/another.js, js/other.jsother.html là các tài nguyên mẫu mà chúng tôi dùng để thử nghiệm phạm vi của worker dịch vụ
  • Thư mục styles/ chứa các biểu định kiểu xếp tầng cho lớp học lập trình này
  • Thư mục test/ chứa các tệp để kiểm thử tiến trình của bạn
  • index.html là trang HTML chính cho trang web/ứng dụng mẫu của chúng tôi
  • service-worker.js là tệp JavaScript dùng để tạo trình chạy dịch vụ
  • package.jsonpackage-lock.json theo dõi các gói nút được dùng trong dự án này
  • server.js là một máy chủ express đơn giản mà chúng ta dùng để lưu trữ ứng dụng

Mở service-worker.js trong trình chỉnh sửa văn bản. Lưu ý rằng tệp này không có dữ liệu. Chúng ta chưa thêm mã nào để chạy trong trình chạy dịch vụ.

Mở index.html trong trình chỉnh sửa văn bản.

Bên trong thẻ <script>, hãy thêm mã sau để đăng ký trình chạy dịch vụ:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('Service Worker is registered', registration);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

Lưu tập lệnh và làm mới trang. Bảng điều khiển sẽ trả về một thông báo cho biết rằng service worker đã được đăng ký. Trong Chrome, bạn có thể kiểm tra xem một worker dịch vụ đã được đăng ký hay chưa bằng cách mở Công cụ cho nhà phát triển (Control + Shift + I trên Windows và Linux, hoặc ⌘ + alt + I trên máy Mac), nhấp vào thẻ Ứng dụng, rồi nhấp vào lựa chọn Worker dịch vụ. Bạn sẽ thấy nội dung tương tự như sau:

Không bắt buộc: Mở trang web trên một trình duyệt không được hỗ trợ và xác minh rằng điều kiện kiểm tra hỗ trợ hoạt động.

Giải thích

Mã ở trên đăng ký tệp service-worker.js làm trình chạy dịch vụ. Trước tiên, nó sẽ kiểm tra xem trình duyệt có hỗ trợ các worker dịch vụ hay không. Bạn nên thực hiện việc này mỗi khi đăng ký một trình chạy dịch vụ vì một số trình duyệt có thể không hỗ trợ trình chạy dịch vụ. Sau đó, mã này đăng ký trình chạy dịch vụ bằng phương thức register của API ServiceWorkerContainer, có trong giao diện Navigator của cửa sổ.

navigator.serviceWorker.register(...) trả về một lời hứa hẹn sẽ phân giải bằng đối tượng registration sau khi trình chạy dịch vụ được đăng ký thành công. Nếu quá trình đăng ký không thành công, thì lệnh hứa sẽ từ chối.

Các thay đổi về trạng thái của worker dịch vụ sẽ kích hoạt các sự kiện trong worker dịch vụ.

Thêm trình nghe sự kiện

Mở service-worker.js trong trình chỉnh sửa văn bản.

Thêm các trình nghe sự kiện sau vào worker dịch vụ:

self.addEventListener('install', event => {
  console.log('Service worker installing...');
  // Add a call to skipWaiting here
});

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

Lưu tệp.

Huỷ đăng ký trình chạy dịch vụ theo cách thủ công và làm mới trang để cài đặt cũng như kích hoạt trình chạy dịch vụ đã cập nhật. Nhật ký bảng điều khiển sẽ cho biết rằng service worker mới đã được đăng ký, cài đặt và kích hoạt.

Lưu ý: Nhật ký đăng ký có thể xuất hiện không theo thứ tự so với các nhật ký khác (nhật ký cài đặt và kích hoạt). Trình chạy dịch vụ chạy đồng thời với trang, vì vậy, chúng tôi không thể đảm bảo thứ tự của nhật ký (nhật ký đăng ký đến từ trang, trong khi nhật ký cài đặt và kích hoạt đến từ trình chạy dịch vụ). Các sự kiện cài đặt, kích hoạt và các sự kiện khác của trình chạy dịch vụ xảy ra theo một thứ tự xác định bên trong trình chạy dịch vụ, tuy nhiên, các sự kiện này phải luôn xuất hiện theo thứ tự dự kiến.

Giải thích

Trình chạy dịch vụ phát ra một sự kiện install vào cuối quá trình đăng ký. Trong đoạn mã trên, một thông báo được ghi lại bên trong trình nghe sự kiện install, nhưng trong một ứng dụng thực tế, đây sẽ là một vị trí phù hợp để lưu trữ tài sản tĩnh vào bộ nhớ đệm.

Khi một trình chạy dịch vụ được đăng ký, trình duyệt sẽ phát hiện xem trình chạy dịch vụ đó có phải là trình chạy mới hay không (do trình chạy đó khác với trình chạy dịch vụ đã cài đặt trước đó hoặc do không có trình chạy dịch vụ nào được đăng ký cho trang web này). Nếu trình thực thi dịch vụ là mới (như trong trường hợp này), thì trình duyệt sẽ cài đặt trình thực thi đó.

Trình chạy dịch vụ phát ra một sự kiện activate khi kiểm soát trang. Đoạn mã trên ghi nhật ký một thông báo tại đây, nhưng sự kiện này thường được dùng để cập nhật bộ nhớ đệm.

Chỉ một worker dịch vụ có thể hoạt động cùng một lúc cho một phạm vi nhất định (xem phần Khám phá phạm vi của worker dịch vụ), vì vậy, một worker dịch vụ mới cài đặt sẽ không được kích hoạt cho đến khi worker dịch vụ hiện có không còn được sử dụng nữa. Đây là lý do khiến tất cả các trang do một trình chạy dịch vụ kiểm soát phải được đóng trước khi một trình chạy dịch vụ mới có thể tiếp quản. Vì chúng tôi đã huỷ đăng ký trình chạy dịch vụ hiện có, nên trình chạy dịch vụ mới đã được kích hoạt ngay lập tức.

Lưu ý: Chỉ làm mới trang là không đủ để chuyển quyền kiểm soát cho một trình chạy dịch vụ mới, vì trang mới sẽ được yêu cầu trước khi trang hiện tại được gỡ xuống và sẽ không có thời điểm nào mà trình chạy dịch vụ cũ không được sử dụng.

Lưu ý: Bạn cũng có thể kích hoạt một trình chạy dịch vụ mới theo cách thủ công bằng cách sử dụng công cụ dành cho nhà phát triển của một số trình duyệt và theo phương thức lập trình bằng skipWaiting(). Chúng ta sẽ thảo luận về vấn đề này trong phần 3.4.

Cập nhật service worker

Thêm nhận xét sau vào bất kỳ vị trí nào trong service-worker.js:

// I'm a new service worker

Lưu tệp rồi làm mới trang. Xem nhật ký trong bảng điều khiển; lưu ý rằng trình chạy dịch vụ mới sẽ cài đặt nhưng không kích hoạt. Trong Chrome, bạn có thể thấy trình chạy dịch vụ đang chờ trong thẻ Ứng dụng trong Công cụ cho nhà phát triển.

Đóng tất cả các trang liên kết với service worker. Sau đó, hãy mở lại localhost:8081/. Nhật ký bảng điều khiển sẽ cho biết rằng trình chạy dịch vụ mới hiện đã được kích hoạt.

Lưu ý: Nếu bạn nhận được kết quả không mong muốn, hãy đảm bảo rằng bộ nhớ đệm HTTP của bạn đã bị vô hiệu hoá trong các công cụ cho nhà phát triển.

Giải thích

Trình duyệt phát hiện thấy sự khác biệt về byte giữa tệp trình chạy dịch vụ mới và tệp trình chạy dịch vụ hiện có (do có thêm nhận xét), vì vậy trình chạy dịch vụ mới sẽ được cài đặt. Vì chỉ có một worker dịch vụ có thể hoạt động cùng một lúc (cho một phạm vi nhất định), nên ngay cả khi worker dịch vụ mới được cài đặt, worker đó sẽ không được kích hoạt cho đến khi worker dịch vụ hiện có không còn được sử dụng nữa. Bằng cách đóng tất cả các trang thuộc quyền kiểm soát của trình chạy dịch vụ cũ, chúng ta có thể kích hoạt trình chạy dịch vụ mới.

Bỏ qua giai đoạn chờ

Trình thực thi dịch vụ mới có thể kích hoạt ngay lập tức, ngay cả khi có trình thực thi dịch vụ hiện tại, bằng cách bỏ qua giai đoạn chờ.

Trong service-worker.js, hãy thêm một lệnh gọi đến skipWaiting trong trình nghe sự kiện install:

self.skipWaiting();

Lưu tệp rồi làm mới trang. Xin lưu ý rằng trình thực thi dịch vụ mới sẽ cài đặt và kích hoạt ngay lập tức, ngay cả khi một trình thực thi dịch vụ trước đó đang kiểm soát.

Giải thích

Phương thức skipWaiting() cho phép trình chạy dịch vụ kích hoạt ngay khi hoàn tất quá trình cài đặt. Trình nghe sự kiện cài đặt là vị trí phổ biến để đặt lệnh gọi skipWaiting(), nhưng bạn có thể gọi lệnh này ở bất kỳ đâu trong hoặc trước giai đoạn chờ. Hãy xem tài liệu này để biết thêm về thời điểm và cách sử dụng skipWaiting(). Trong phần còn lại của phòng thí nghiệm, giờ đây, chúng ta có thể kiểm thử mã trình chạy dịch vụ mới mà không cần huỷ đăng ký trình chạy dịch vụ theo cách thủ công.

Thông tin khác

Trình chạy dịch vụ có thể đóng vai trò là một proxy giữa ứng dụng web và mạng.

Hãy thêm một trình nghe tìm nạp để chặn các yêu cầu từ miền của chúng ta.

Thêm mã sau vào service-worker.js:

self.addEventListener('fetch', event => {
  console.log('Fetching:', event.request.url);
});

Lưu tập lệnh và làm mới trang để cài đặt cũng như kích hoạt service worker đã cập nhật.

Kiểm tra bảng điều khiển và nhận thấy không có sự kiện tìm nạp nào được ghi lại. Làm mới trang rồi kiểm tra lại bảng điều khiển. Lần này, bạn sẽ thấy các sự kiện tìm nạp cho trang và nội dung của trang (chẳng hạn như CSS).

Nhấp vào các đường liên kết đến Trang khác, Một trang khácQuay lại.

Bạn sẽ thấy các sự kiện tìm nạp trong bảng điều khiển cho từng trang và thành phần của trang. Tất cả nhật ký có hợp lý không?

Lưu ý: Nếu bạn truy cập vào một trang và không tắt bộ nhớ đệm HTTP, thì các thành phần CSS và JavaScript có thể được lưu vào bộ nhớ đệm cục bộ. Nếu điều này xảy ra, bạn sẽ không thấy các sự kiện tìm nạp cho những tài nguyên này.

Giải thích

Trình chạy dịch vụ nhận được một sự kiện tìm nạp cho mọi yêu cầu HTTP do trình duyệt thực hiện trong phạm vi của trình chạy dịch vụ. Đối tượng sự kiện tìm nạp chứa yêu cầu. Việc theo dõi các sự kiện tìm nạp trong worker dịch vụ cũng tương tự như việc theo dõi các sự kiện nhấp chuột trong DOM. Trong mã của chúng tôi, khi một sự kiện tìm nạp xảy ra, chúng tôi sẽ ghi nhật ký URL được yêu cầu vào bảng điều khiển (trong thực tế, chúng tôi cũng có thể tạo và trả về phản hồi tuỳ chỉnh của riêng mình với các tài nguyên tuỳ ý).

Tại sao không có sự kiện tìm nạp nào được ghi lại trong lần làm mới đầu tiên? Theo mặc định, các sự kiện tìm nạp từ một trang sẽ không thông qua trình chạy dịch vụ, trừ phi chính yêu cầu trang đó đã thông qua trình chạy dịch vụ. Điều này giúp đảm bảo tính nhất quán trên trang web của bạn; nếu một trang tải mà không có trình chạy dịch vụ, thì các tài nguyên phụ của trang đó cũng vậy.

Thông tin khác

Đoạn mã giải pháp

Để lấy bản sao của đoạn mã đang hoạt động, hãy chuyển đến thư mục 04-intercepting-network-requests/.

Trình chạy dịch vụ có phạm vi. Phạm vi của trình chạy dịch vụ xác định đường dẫn mà trình chạy dịch vụ chặn các yêu cầu.

Tìm phạm vi

Cập nhật mã đăng ký trong index.html bằng:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('SW registered with scope:', registration.scope);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

Làm mới trình duyệt. Lưu ý rằng bảng điều khiển cho thấy phạm vi của trình chạy dịch vụ (trong trường hợp này là http://localhost:8081/).

Giải thích

Lệnh hứa do register() trả về sẽ phân giải thành đối tượng đăng ký, trong đó có phạm vi của worker dịch vụ.

Phạm vi mặc định là đường dẫn đến tệp worker dịch vụ và mở rộng ra tất cả các thư mục cấp thấp hơn. Vì vậy, trình chạy dịch vụ trong thư mục gốc của một ứng dụng sẽ kiểm soát các yêu cầu từ tất cả các tệp trong ứng dụng đó.

Di chuyển trình chạy dịch vụ

Di chuyển service-worker.js vào thư mục below/ và cập nhật URL của worker dịch vụ trong mã đăng ký trong index.html.

Huỷ đăng ký trình chạy dịch vụ hiện tại trong trình duyệt và làm mới trang.

Bảng điều khiển cho biết phạm vi của worker dịch vụ hiện là http://localhost:8081/below/. Trong Chrome, bạn cũng có thể xem phạm vi của worker dịch vụ trong thẻ ứng dụng của DevTools:

Quay lại trang chính, hãy nhấp vào Trang khác, Một trang khácQuay lại. Những yêu cầu tìm nạp nào đang được ghi lại? Những ứng dụng nào không có?

Giải thích

Phạm vi mặc định của trình chạy dịch vụ là đường dẫn đến tệp trình chạy dịch vụ. Vì tệp worker dịch vụ hiện nằm trong below/, nên đó là phạm vi của tệp. Giờ đây, bảng điều khiển chỉ ghi lại các sự kiện tìm nạp cho another.html, another.cssanother.js, vì đây là những tài nguyên duy nhất trong phạm vi của trình chạy dịch vụ.

Đặt phạm vi tuỳ ý

Di chuyển trình xử lý dịch vụ trở lại thư mục gốc của dự án (app/) và cập nhật URL của trình xử lý dịch vụ trong mã đăng ký trong index.html.

Sử dụng tài liệu tham khảo trên MDN để đặt phạm vi của worker dịch vụ thành thư mục below/ bằng cách sử dụng tham số không bắt buộc trong register().

Huỷ đăng ký trình chạy dịch vụ và làm mới trang. Nhấp vào Trang khác, Trang khácQuay lại.

Một lần nữa, bảng điều khiển cho thấy phạm vi của trình chạy dịch vụ hiện là http://localhost:8081/below/ và chỉ ghi lại các sự kiện tìm nạp cho another.html, another.cssanother.js.

Giải thích

Bạn có thể đặt một phạm vi tuỳ ý bằng cách truyền thêm một tham số khi đăng ký, ví dụ:

navigator.serviceWorker.register('/service-worker.js', {
  scope: '/kitten/'
});

Trong ví dụ trên, phạm vi của trình chạy dịch vụ được đặt thành /kitten/. Trình chạy dịch vụ chặn các yêu cầu từ các trang trong /kitten//kitten/lower/ nhưng không chặn các yêu cầu từ các trang như /kitten hoặc /.

Lưu ý: Bạn không thể đặt một phạm vi tuỳ ý nằm phía trên vị trí thực tế của worker dịch vụ. Tuy nhiên, nếu worker dịch vụ của bạn đang hoạt động trên một máy khách được phân phát bằng tiêu đề Service-Worker-Allowed, bạn có thể chỉ định phạm vi tối đa cho worker dịch vụ đó ở phía trên vị trí của worker dịch vụ.

Thông tin khác

Đoạn mã giải pháp

Để lấy bản sao của đoạn mã đang hoạt động, hãy chuyển đến thư mục solution/.

Giờ đây, bạn đã có một trình chạy dịch vụ đơn giản đang hoạt động và hiểu rõ vòng đời của trình chạy dịch vụ.

Thông tin khác

Vòng đời của worker dịch vụ

Để 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/