หน้าต่างกล่องงาน

แพ็กเกจ workbox-window คือชุดโมดูลที่มีจุดประสงค์ให้ทำงานในบริบท window ซึ่งก็คือภายในหน้าเว็บของคุณ และเป็นส่วนเสริมของแพ็กเกจงานอื่นๆ ที่ทำงานในโปรแกรมทำงานของบริการ

ฟีเจอร์/เป้าหมายที่สำคัญของ workbox-window ได้แก่

การนำเข้าและการใช้หน้าต่างกล่องงาน

จุดแรกเข้าหลักสำหรับแพ็กเกจ workbox-window คือคลาส Workbox ซึ่งคุณจะนำเข้ามาในโค้ดได้จาก CDN ของเราหรือใช้เครื่องมือรวมกลุ่ม JavaScript ยอดนิยมก็ได้

การใช้ CDN ของเรา

วิธีที่ง่ายที่สุดในการนำเข้าคลาส Workbox ในเว็บไซต์คือจาก CDN ของเรา ดังนี้

<script type="module">
  import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';

  if ('serviceWorker' in navigator) {
    const wb = new Workbox('/sw.js');

    wb.register();
  }
</script>

โปรดทราบว่าตัวอย่างนี้ใช้ <script type="module"> และคำสั่ง import เพื่อโหลดคลาส Workbox แม้คุณอาจคิดว่าจำเป็นต้องแปลงโค้ดนี้เพื่อให้ทำงานได้ในเบราว์เซอร์รุ่นเก่ากว่านั้น จริงๆ แล้วไม่จำเป็น

เบราว์เซอร์หลักทั้งหมดที่รองรับ Service Worker จะรองรับโมดูล JavaScript แบบเนทีฟด้วย ดังนั้นจึงเหมาะที่สุดที่จะแสดงโค้ดนี้ในทุกเบราว์เซอร์ (เบราว์เซอร์รุ่นเก่าจะไม่สนใจ)

กำลังโหลด Workbox ด้วย Bundler ของ JavaScript

แม้ว่าไม่จำเป็นต้องใช้เครื่องมือใดๆ ในการใช้ workbox-window แต่หากโครงสร้างพื้นฐานการพัฒนาของคุณมี Bundler เช่น Webpack หรือ Rollup ที่ทำงานกับทรัพยากร Dependency npm อยู่แล้ว คุณก็ใช้ทรัพยากรดังกล่าวเพื่อโหลด workbox-window ได้

ขั้นตอนแรกคือ ติดตั้ง workbox-window เป็นทรัพยากร Dependency ของแอปพลิเคชัน ดังนี้

npm install workbox-window

จากนั้นในไฟล์ JavaScript ของแอปพลิเคชันไฟล์ใดไฟล์หนึ่ง ให้ไปที่ import Workbox โดยระบุชื่อแพ็กเกจ workbox-window ดังนี้

import {Workbox} from 'workbox-window';

if ('serviceWorker' in navigator) {
  const wb = new Workbox('/sw.js');

  wb.register();
}

หาก Bundler รองรับการแยกโค้ดผ่านคำสั่งการนำเข้าแบบไดนามิก คุณจะโหลด workbox-window อย่างมีเงื่อนไขได้ด้วย ซึ่งจะช่วยลดขนาดของ Bundle หลักของหน้าได้

แม้ว่า workbox-window จะมีขนาดค่อนข้างเล็ก แต่ก็ไม่มีเหตุผลที่จะต้องโหลดตรรกะของแอปพลิเคชันหลักของเว็บไซต์ไว้ เนื่องจากการทำงานของ Service Worker จึงเป็นการเพิ่มประสิทธิภาพแบบต่อเนื่อง

if ('serviceWorker' in navigator) {
  const {Workbox} = await import('workbox-window');

  const wb = new Workbox('/sw.js');
  wb.register();
}

แนวคิดการรวมกลุ่มขั้นสูง

ไฟล์บิลด์ที่อ้างอิงโดยช่อง main และ module ของ workbox-window ใน package.json จะแปลงเป็น ES5 ซึ่งแตกต่างจากแพ็กเกจ Workbox ที่เรียกใช้ใน Service Worker ซึ่งทำให้เครื่องมือเหล่านี้เข้ากันได้กับเครื่องมือสร้างในปัจจุบัน ซึ่งบางรายการไม่อนุญาตให้นักพัฒนาซอฟต์แวร์ถ่ายโอนทรัพยากร Dependency ของ node_module ได้ทั้งหมด

หากระบบของบิลด์อนุญาตให้คุณสลับทรัพยากร Dependency (หรือคุณไม่จำเป็นต้องเปลี่ยนรูปแบบโค้ด) ก็ควรนำเข้าไฟล์ต้นฉบับเฉพาะแทนการนำเข้าตัวแพ็กเกจ

ต่อไปนี้คือวิธีต่างๆ ที่คุณจะนำเข้า Workbox ได้ รวมถึงคำอธิบายว่าแต่ละรายการจะแสดงผลอะไร

// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');

// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';

// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';

ตัวอย่าง

เมื่อนำเข้าคลาส Workbox แล้ว คุณจะใช้คลาสดังกล่าวเพื่อลงทะเบียนและโต้ตอบกับ Service Worker ได้ ตัวอย่างวิธีที่คุณอาจใช้ Workbox ในแอปพลิเคชันมีดังนี้

ลงทะเบียน Service Worker และแจ้งให้ผู้ใช้ทราบในครั้งแรกที่โปรแกรมทำงานของบริการทำงาน

โปรแกรมทำงานของบริการสำหรับผู้ใช้เว็บแอปพลิเคชันจำนวนมากต้องแคชเนื้อหาล่วงหน้าเพื่อให้แอปทำงานแบบออฟไลน์เมื่อโหลดหน้าเว็บในครั้งต่อๆ ไป ในบางกรณี การบอกให้ผู้ใช้ทราบว่า แอปพร้อมให้บริการแบบออฟไลน์แล้ว

const wb = new Workbox('/sw.js');

wb.addEventListener('activated', event => {
  // `event.isUpdate` will be true if another version of the service
  // worker was controlling the page when this version was registered.
  if (!event.isUpdate) {
    console.log('Service worker activated for the first time!');

    // If your service worker is configured to precache assets, those
    // assets should all be available now.
  }
});

// Register the service worker after event listeners have been added.
wb.register();

แจ้งผู้ใช้หาก Service Worker ติดตั้งแล้วแต่ต้องรอเปิดใช้งาน

เมื่อหน้าเว็บที่ควบคุมโดย Service Worker ที่มีอยู่ลงทะเบียน Service Worker ใหม่โดยค่าเริ่มต้นนั้น Service Worker จะไม่เปิดใช้งานจนกว่าไคลเอ็นต์ทั้งหมดที่ควบคุมโดย Service Worker จะเริ่มต้นยกเลิกการโหลดโดยสมบูรณ์

ปัญหานี้มักทำให้นักพัฒนาซอฟต์แวร์สับสน โดยเฉพาะอย่างยิ่งในกรณีที่การโหลดหน้าเว็บปัจจุบันซ้ำไม่ทำให้โปรแกรมทำงานของบริการใหม่เปิดใช้งาน

คลาส Workbox มีเหตุการณ์ waiting ที่คุณรับฟังได้เพื่อช่วยลดความสับสนและอธิบายให้ชัดเจนว่าจะเกิดสถานการณ์เช่นนี้ขึ้นเมื่อใด

const wb = new Workbox('/sw.js');

wb.addEventListener('waiting', event => {
  console.log(
    `A new service worker has installed, but it can't activate` +
      `until all tabs running the current version have fully unloaded.`
  );
});

// Register the service worker after event listeners have been added.
wb.register();

แจ้งผู้ใช้เกี่ยวกับการอัปเดตแคชจากแพ็กเกจ workbox-broadcast-update

แพ็กเกจ workbox-broadcast-update เป็นวิธีที่ยอดเยี่ยมในการแสดงเนื้อหาจากแคช (เพื่อให้นำส่งได้อย่างรวดเร็ว) ทั้งยังสามารถแจ้งให้ผู้ใช้ทราบการอัปเดตเนื้อหานั้น (โดยใช้กลยุทธ์ "ไม่มีอัปเดตขณะตรวจสอบใหม่)

หากต้องการรับการอัปเดตจากหน้าต่าง คุณจะฟังเหตุการณ์ message ประเภท CACHE_UPDATED ได้ดังนี้

const wb = new Workbox('/sw.js');

wb.addEventListener('message', event => {
  if (event.data.type === 'CACHE_UPDATED') {
    const {updatedURL} = event.data.payload;

    console.log(`A newer version of ${updatedURL} is available!`);
  }
});

// Register the service worker after event listeners have been added.
wb.register();

ส่งรายการ URL ให้โปรแกรมทำงานของบริการเพื่อแคช

สำหรับแอปพลิเคชันบางตัว อาจเป็นไปได้ที่จะทราบเนื้อหาทั้งหมดที่ต้องทำการแคชล่วงหน้าในเวลาสร้าง แต่บางแอปพลิเคชันจะแสดงหน้าเว็บที่แตกต่างกันโดยสิ้นเชิง ขึ้นอยู่กับ URL ที่ผู้ใช้ไปถึงเป็นอันดับแรก

สำหรับแอปในหมวดหมู่หลัง คุณอาจต้องแคชเฉพาะเนื้อหาที่จำเป็นสำหรับหน้าเว็บที่ผู้ใช้เข้าชมเท่านั้น เมื่อใช้แพ็กเกจ workbox-routing คุณสามารถส่งรายการ URL ของเราเตอร์ไปยังแคชได้ และระบบจะแคช URL เหล่านั้นตามกฎที่กำหนดไว้บนเราเตอร์เอง

ตัวอย่างนี้จะส่งรายการ URL ที่โหลดโดยหน้าเว็บไปยังเราเตอร์ทุกครั้งที่มีการเปิดใช้งาน Service Worker ใหม่ โปรดทราบว่าคุณส่ง URL ทั้งหมดได้ เนื่องจากระบบจะแคชเฉพาะ URL ที่ตรงกับเส้นทางที่กำหนดไว้ใน Service Worker เท่านั้น

const wb = new Workbox('/sw.js');

wb.addEventListener('activated', event => {
  // Get the current page URL + all resources the page loaded.
  const urlsToCache = [
    location.href,
    ...performance.getEntriesByType('resource').map(r => r.name),
  ];
  // Send that list of URLs to your router in the service worker.
  wb.messageSW({
    type: 'CACHE_URLS',
    payload: {urlsToCache},
  });
});

// Register the service worker after event listeners have been added.
wb.register();

ช่วงเวลาในวงจรการทำงานของ Service Worker ที่สำคัญ

วงจรการทำงานของบริการนั้นซับซ้อนและอาจเป็นเรื่องท้าทายในการทำความเข้าใจอย่างถ่องแท้ เหตุผลหนึ่งที่มีความซับซ้อนก็คือต้องจัดการ Edge Case ทั้งหมดสำหรับการใช้งาน Service Worker ที่เป็นไปได้ทั้งหมด (เช่น การลงทะเบียน Service Worker มากกว่า 1 โปรแกรม ลงทะเบียน Service Worker ต่างๆ ในเฟรมต่างกัน ลงทะเบียน Service Worker ด้วยชื่อต่างกัน เป็นต้น)

แต่นักพัฒนาซอฟต์แวร์ส่วนใหญ่ที่ใช้ Service Worker ไม่ควรกังวลเกี่ยวกับ Edge Case เหล่านี้ทั้งหมดเพราะการใช้งานนั้นค่อนข้างง่าย นักพัฒนาซอฟต์แวร์ส่วนใหญ่จะลงทะเบียน Service Worker เพียง 1 ครั้งต่อการโหลดหน้าเว็บ และไม่เปลี่ยนชื่อไฟล์ Service Worker ที่ทำให้ใช้งานได้กับเซิร์ฟเวอร์

คลาส Workbox จะนำมุมมองที่ง่ายกว่านี้เข้ามาสำหรับวงจรการทำงานของ Service Worker โดยแบ่งการลงทะเบียนโปรแกรมทำงานของบริการทั้งหมดออกเป็น 2 หมวดหมู่ ได้แก่ โปรแกรมทำงานของบริการเอง โปรแกรมทำงานของบริการที่ลงทะเบียนไว้ และโปรแกรมทำงานของบริการภายนอก ดังนี้

  • Service Worker ที่ลงทะเบียนแล้ว: โปรแกรมทำงานของบริการที่เริ่มติดตั้งเนื่องจาก Workbox การเรียกใช้อินสแตนซ์ register() หรือโปรแกรมทำงานของบริการที่ใช้งานอยู่แล้ว หากการเรียกใช้ register() ไม่ทริกเกอร์เหตุการณ์ updatefound ในการลงทะเบียน
  • โปรแกรมทำงานของบริการภายนอก: โปรแกรมทำงานของบริการที่เริ่มติดตั้งโดยอิสระของการเรียกใช้อินสแตนซ์ Workbox ที่เรียกใช้ register() ปัญหานี้มักเกิดขึ้นเมื่อผู้ใช้เปิดเว็บไซต์เวอร์ชันใหม่ในแท็บอื่น เมื่อเหตุการณ์มาจาก Service Worker ภายนอก ระบบจะตั้งค่าพร็อพเพอร์ตี้ isExternal ของเหตุการณ์เป็น true

เมื่อคำนึงถึง Service Worker ทั้ง 2 ประเภทนี้แล้ว ต่อไปนี้เป็นรายละเอียดช่วงเวลาสำคัญในวงจรการทำงานของโปรแกรมทำงานของบริการทั้งหมด รวมทั้งคำแนะนำจากนักพัฒนาซอฟต์แวร์เกี่ยวกับวิธีจัดการกับช่วงเวลาเหล่านั้น

ครั้งแรกที่มีการติดตั้ง Service Worker

คุณอาจต้องการดำเนินการกับการติดตั้งโปรแกรมทำงานของบริการครั้งแรกโดยแตกต่างจากการอัปเดตในอนาคตทั้งหมด

ใน workbox-window คุณแยกความแตกต่างระหว่างการติดตั้งครั้งแรกกับการอัปเดตในอนาคตได้โดยตรวจสอบพร็อพเพอร์ตี้ isUpdate ในเหตุการณ์ใดก็ได้ต่อไปนี้ สำหรับการติดตั้งครั้งแรก isUpdate จะเป็น false

const wb = new Workbox('/sw.js');

wb.addEventListener('installed', event => {
  if (!event.isUpdate) {
    // First-installed code goes here...
  }
});

wb.register();
ช่วงเวลาสำคัญ เหตุการณ์ การดำเนินการที่แนะนำ
ติดตั้ง Service Worker ใหม่แล้ว (เป็นครั้งแรก) installed

ครั้งแรกที่ติดตั้ง Service Worker จะเป็นการแคชเนื้อหาทั้งหมดที่จำเป็นเพื่อให้เว็บไซต์ทำงานแบบออฟไลน์ไว้ล่วงหน้าได้ คุณอาจพิจารณาแจ้งให้ผู้ใช้ทราบว่าขณะนี้เว็บไซต์ใช้งานได้แบบออฟไลน์แล้ว

นอกจากนี้ เนื่องจากในครั้งแรกที่ติดตั้ง Service Worker โปรแกรมดังกล่าวก็จะไม่ขัดขวางเหตุการณ์การดึงข้อมูลสำหรับการโหลดหน้าเว็บนั้น คุณจึงอาจพิจารณาแคชเนื้อหาที่โหลดไว้แล้วด้วย (แต่ไม่จำเป็นต้องทำหากเนื้อหาเหล่านั้นมีการแคชล่วงหน้าอยู่แล้ว) ตัวอย่างส่งรายการ URL ไปยังแคชให้โปรแกรมทำงานด้านบนแสดงวิธีดำเนินการ

Service Worker เริ่มควบคุมหน้าเว็บแล้ว controlling

เมื่อติดตั้ง Service Worker ใหม่และเริ่มควบคุมหน้าเว็บแล้ว เหตุการณ์การดึงข้อมูลหลังจากนั้นทั้งหมดจะส่งผ่าน Service Worker นั้น หากโปรแกรมทำงานของบริการเพิ่มตรรกะพิเศษเพื่อจัดการเหตุการณ์การดึงข้อมูลเฉพาะ จุดนี้เมื่อคุณทราบว่าตรรกะจะทำงาน

โปรดทราบว่าครั้งแรกที่ติดตั้ง Service Worker โปรแกรมจะไม่เริ่มควบคุมหน้าปัจจุบัน เว้นแต่ว่าโปรแกรมทำงานของบริการจะเรียกใช้ clients.claim() ในเหตุการณ์การเปิดใช้งาน ลักษณะการทำงานเริ่มต้นคือการรอจนกว่าการโหลดหน้าเว็บถัดไปจะเริ่มควบคุม

จากมุมมองของ workbox-window หมายความว่าระบบจะส่งเหตุการณ์ controlling ในกรณีที่โปรแกรมทำงานของบริการเรียกใช้ clients.claim() เท่านั้น ระบบจะไม่ส่งเหตุการณ์นี้หากมีการควบคุมหน้าเว็บก่อนการลงทะเบียนแล้ว

โปรแกรมทำงานของบริการได้เปิดใช้งานเสร็จแล้ว activated

ดังที่กล่าวไว้ข้างต้น ครั้งแรกที่โปรแกรมทำงานของบริการทำงานจนเสร็จอาจ (หรืออาจไม่) เริ่มควบคุมหน้าเว็บ

ด้วยเหตุนี้ คุณจึงไม่ควรฟังเหตุการณ์ "เปิดใช้งาน" เพื่อให้ทราบเมื่อโปรแกรมทำงานของบริการ (Service Worker) ควบคุมหน้าเว็บได้ อย่างไรก็ตาม หากเรียกใช้ตรรกะในเหตุการณ์ที่ใช้งานอยู่ (ใน Service Worker) และคุณจำเป็นต้องทราบเมื่อตรรกะนั้นเสร็จสมบูรณ์ เหตุการณ์ที่เปิดใช้งานจะแจ้งให้คุณทราบ

เมื่อพบ Service Worker เวอร์ชันอัปเดต

เมื่อ Service Worker ใหม่เริ่มติดตั้ง แต่เวอร์ชันที่มีอยู่กำลังควบคุมหน้าเว็บอยู่ในขณะนี้ พร็อพเพอร์ตี้ isUpdate ของเหตุการณ์ต่อไปนี้ทั้งหมดจะเป็น true

ปฏิกิริยาของคุณในสถานการณ์นี้มักจะแตกต่างจากการติดตั้งครั้งแรก เพราะคุณต้องจัดการเวลาและวิธีที่ผู้ใช้จะได้รับการอัปเดตนี้

ช่วงเวลาสำคัญ เหตุการณ์ การดำเนินการที่แนะนำ
มี Service Worker ใหม่ติดตั้งแล้ว (กำลังอัปเดตรายการก่อนหน้า) installed

หากนี่ไม่ใช่โปรแกรมทำงานของบริการแรกที่ติดตั้ง (event.isUpdate === true) แสดงว่าพบและติดตั้ง Service Worker เวอร์ชันใหม่กว่าแล้ว (ซึ่งเป็นเวอร์ชันที่แตกต่างจากเวอร์ชันที่ควบคุมหน้าเว็บอยู่)

โดยทั่วไปหมายความว่ามีการทำให้เว็บไซต์เวอร์ชันใหม่ใช้งานได้ในเซิร์ฟเวอร์ของคุณ และเนื้อหาใหม่อาจเพิ่งเสร็จสิ้นการแคชล่วงหน้า

หมายเหตุ: นักพัฒนาซอฟต์แวร์บางรายใช้เหตุการณ์ installed เพื่อแจ้งผู้ใช้ว่ามีเว็บไซต์เวอร์ชันใหม่ให้ใช้งานแล้ว อย่างไรก็ตาม ขึ้นอยู่กับว่าคุณเรียกใช้ skipWaiting() ในโปรแกรมทำงานของบริการที่ติดตั้งนั้น โปรแกรมทำงานของบริการที่ติดตั้งไว้นั้นอาจทำงานในทันทีหรือไม่ก็ได้ หากคุณไม่โทรหา skipWaiting() วิธีที่ดีที่สุดคือแจ้งให้ผู้ใช้ทราบเกี่ยวกับการอัปเดตเมื่อเปิดใช้ Service Worker ใหม่แล้ว หากคุณไม่โทรหา skipWaiting ควรแจ้งผู้ใช้เกี่ยวกับการอัปเดตที่รอดำเนินการในกิจกรรมรอ (ดูรายละเอียดเพิ่มเติมด้านล่าง)

มี Service Worker ติดตั้งแล้วแต่ค้างอยู่ในขั้นตอนรอ waiting

หากเวอร์ชันที่อัปเดตแล้วของ Service Worker ไม่ได้เรียกใช้ skipWaiting() ขณะทำการติดตั้ง โปรแกรมจะไม่เปิดใช้งานจนกว่าหน้าเว็บทั้งหมดที่ควบคุมโดย Service Worker ที่ใช้งานอยู่ในขณะนั้นจะถูกยกเลิกการโหลด คุณอาจต้องแจ้งให้ผู้ใช้ทราบว่ามีการอัปเดตพร้อมใช้งานและระบบจะนําไปใช้ในครั้งถัดไปที่ผู้ใช้เข้าชม

คำเตือน! นักพัฒนาซอฟต์แวร์มักจะแจ้งให้ผู้ใช้โหลดซ้ำเพื่อรับอัปเดต แต่ในหลายกรณี การรีเฟรชหน้าเว็บจะไม่เปิดใช้งานผู้ปฏิบัติงานที่ติดตั้งไว้ หากผู้ใช้รีเฟรชหน้าเว็บและ Service Worker กำลังรออยู่ เหตุการณ์ waiting จะเริ่มทำงานอีกครั้งและพร็อพเพอร์ตี้ event.wasWaitingBeforeRegister จะเป็นจริง โปรดทราบว่าเราวางแผนที่จะปรับปรุงประสบการณ์นี้ในรุ่นต่อๆ ไป ติดตามปัญหา #1848 เพื่อดูข้อมูลอัปเดต

อีกทางเลือกหนึ่งคือการแจ้งผู้ใช้และถามผู้ใช้ว่าต้องการรับการอัปเดตต่อไป หรือรอต่อไป หากเลือกรับการอัปเดต คุณจะใช้ postMessage() เพื่อบอก Service Worker ให้เรียกใช้ skipWaiting() ได้ โปรดดูตัวอย่างในสูตรขั้นสูงที่ เสนอการโหลดหน้าเว็บซ้ำให้กับผู้ใช้

Service Worker เริ่มควบคุมหน้าเว็บแล้ว controlling

เมื่อ Service Worker ที่อัปเดตเริ่มควบคุมหน้าเว็บ หมายความว่าเวอร์ชันของ Service Worker ที่ควบคุมอยู่นั้นต่างจากเวอร์ชันที่เคยควบคุมเมื่อโหลดหน้าเว็บ ในบางกรณีอาจไม่มีปัญหา แต่ก็อาจหมายความว่าเนื้อหาบางส่วนที่หน้าปัจจุบันอ้างอิงไม่ได้อยู่ในแคชแล้ว (และอาจจะไม่ได้อยู่ในเซิร์ฟเวอร์ด้วย) คุณอาจต้องแจ้งให้ผู้ใช้ทราบว่าบางส่วนของหน้าอาจทำงานไม่ถูกต้อง

หมายเหตุ: เหตุการณ์ controlling จะไม่เริ่มทำงานหากคุณไม่ได้เรียกใช้ skipWaiting() ใน Service Worker

โปรแกรมทำงานของบริการได้เปิดใช้งานเสร็จแล้ว activated เมื่อ Service Worker ที่อัปเดตทำงานเสร็จแล้ว หมายความว่าตรรกะใดก็ตามที่คุณเรียกใช้ใน activate ของ Service Worker นั้นทำงานเสร็จแล้ว หากมีสิ่งใดที่คุณต้องเลื่อนเวลาออกไปจนกว่าตรรกะนั้นจะทำงานเสร็จสิ้น เวลานี้จะเป็นเวลาที่กำหนดให้เรียกใช้

เมื่อพบ Service Worker เวอร์ชันที่ไม่คาดคิด

บางครั้งผู้ใช้จะเปิดเว็บไซต์ของคุณไว้ในแท็บเบื้องหลังเป็นเวลานานมาก พวกเขาอาจเปิดแท็บใหม่และมาที่เว็บไซต์ของคุณโดยที่ไม่รู้ตัวว่าเว็บไซต์เปิดในแท็บเบื้องหลังอยู่แล้ว ในกรณีดังกล่าว เป็นไปได้ว่าคุณมีเว็บไซต์ 2 เวอร์ชันที่ทำงานพร้อมกัน และอาจทำให้เกิดปัญหาที่น่าสนใจสำหรับคุณในฐานะนักพัฒนาซอฟต์แวร์

ลองพิจารณาสถานการณ์ที่คุณมีแท็บ A ที่ทำงานอยู่ เวอร์ชัน 1 ของเว็บไซต์และแท็บ B ที่ทำงานอยู่ เวอร์ชัน 2 เมื่อแท็บ B โหลด เวอร์ชันของ Service Worker ที่มาพร้อมกับ v1 จะควบคุมแท็บนั้น แต่หน้าเว็บที่แสดงโดยเซิร์ฟเวอร์ (หากใช้กลยุทธ์การแคชเครือข่ายเป็นหลักสำหรับคำขอการนำทางของคุณ) จะมีชิ้นงาน v2 ทั้งหมดของคุณ

แต่โดยทั่วไปกรณีนี้จะไม่เป็นปัญหาสำหรับแท็บ B เนื่องจากเมื่อคุณเขียนโค้ด v2 คุณก็ทราบวิธีการทำงานของโค้ด v1 ของคุณ อย่างไรก็ตาม นี่อาจเป็นปัญหาสำหรับแท็บ A เนื่องจากโค้ด v1 ของคุณไม่สามารถคาดการณ์สิ่งที่อาจทำให้โค้ด v2 ของคุณเปลี่ยนแปลงไปได้

เพื่อช่วยรับมือกับสถานการณ์เหล่านี้ workbox-window จะส่งเหตุการณ์ในวงจรเมื่อตรวจพบอัปเดตจาก Service Worker "ภายนอก" โดยที่ภายนอกหมายถึงเวอร์ชันใดก็ตามที่ไม่ใช่เวอร์ชันที่ลงทะเบียนอินสแตนซ์ Workbox ปัจจุบัน

ใน Workbox เวอร์ชัน 6 ขึ้นไป เหตุการณ์เหล่านี้จะเทียบเท่ากับเหตุการณ์ที่บันทึกด้านบน โดยมีการตั้งค่าพร็อพเพอร์ตี้ isExternal: true ในออบเจ็กต์เหตุการณ์แต่ละรายการ หากเว็บแอปพลิเคชันจำเป็นต้องใช้ตรรกะที่เฉพาะเจาะจงเพื่อจัดการกับ Service Worker "ภายนอก" คุณก็ตรวจสอบพร็อพเพอร์ตี้นั้นในเครื่องจัดการเหตุการณ์ได้

การหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย

หนึ่งในฟีเจอร์ที่มีประโยชน์มากที่สุดที่ Workbox มีให้คือการบันทึกของนักพัฒนาซอฟต์แวร์ โดยเฉพาะอย่างยิ่งสำหรับ workbox-window

เราทราบดีว่าการพัฒนาด้วย Service Worker มักเป็นเรื่องที่สับสน และเมื่อเจอสิ่งที่ขัดกับสิ่งที่คุณคาดหวัง คุณก็อาจหาสาเหตุได้ยาก

เช่น เมื่อทำการเปลี่ยนแปลง Service Worker และโหลดหน้าเว็บซ้ำ คุณอาจไม่เห็นการเปลี่ยนแปลงนั้นในเบราว์เซอร์ สาเหตุที่เป็นไปได้มากที่สุดสำหรับปัญหานี้คือ โปรแกรมทำงานของบริการยังรอเปิดใช้งานอยู่

แต่เมื่อลงทะเบียน Service Worker ด้วยคลาส Workbox คุณจะได้รับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงสถานะวงจรทั้งหมดในคอนโซลของนักพัฒนาซอฟต์แวร์ ซึ่งน่าจะช่วยในการแก้ไขข้อบกพร่องที่ทำให้สิ่งต่างๆ ไม่เป็นไปตามที่คุณคาดหวัง

คำเตือนของคอนโซลหน้าต่างงานสำหรับผู้ปฏิบัติงานที่รอ

นอกจากนี้ ข้อผิดพลาดที่นักพัฒนาซอฟต์แวร์มักจะทำเมื่อใช้ Service Worker เป็นครั้งแรกคือการลงทะเบียน Service Worker ที่อยู่ในขอบเขตที่ไม่ถูกต้อง

คลาส Workbox จะเตือนคุณหากหน้าเว็บที่ลงทะเบียน Service Worker ไม่อยู่ในขอบเขตของ Service Worker เพื่อป้องกันไม่ให้เกิดเหตุการณ์ดังกล่าว และจะเตือนคุณในกรณีที่ Service Worker ทำงานอยู่แต่ยังไม่ได้ควบคุมหน้า

คำเตือนคอนโซลหน้าต่างงานสำหรับผู้ปฏิบัติงานที่ไม่มีการควบคุม

การสื่อสารจากหน้าต่างสู่บริการผู้ปฏิบัติงาน

การใช้งาน Service Worker ขั้นสูงที่สุดจะมีการรับส่งข้อความจำนวนมากระหว่างโปรแกรมทำงานของบริการและหน้าต่าง คลาส Workbox ช่วยในเรื่องนี้ได้เหมือนกันโดยมอบเมธอด messageSW() ซึ่งจะ postMessage() โปรแกรมทำงานของบริการที่ลงทะเบียนของอินสแตนซ์และรอการตอบกลับ

แม้ว่าคุณจะสามารถส่งข้อมูลไปยังโปรแกรมทำงานของบริการในรูปแบบใดก็ได้ แต่รูปแบบที่แชร์โดยแพ็กเกจ Workbox ทั้งหมดจะเป็นออบเจ็กต์ที่มี 3 พร็อพเพอร์ตี้ (2 ตัวเลือกหลังเป็นตัวเลือก) ดังนี้

พร็อพเพอร์ตี้ จำเป็นหรือไม่ ประเภท คำอธิบาย
type มี string

สตริงที่ไม่ซ้ำกันซึ่งใช้ระบุข้อความนี้

โดยปกติแล้ว ประเภทจะเป็นตัวพิมพ์ใหญ่ทั้งหมดและมีขีดล่างคั่นคำ หากประเภทแสดงถึงการดำเนินการที่ต้องทำ ประเภทนั้นควรเป็นคำสั่งในปัจจุบัน (เช่น CACHE_URLS) หากประเภทแสดงถึงข้อมูลที่กำลังรายงาน ประเภทนั้นควรเป็นคำสั่งในอดีต (เช่น URLS_CACHED)

meta ไม่ string ใน Workbox จะเป็นชื่อของแพ็กเกจ Workbox ที่ส่งข้อความเสมอ เมื่อส่งข้อความถึงตัวเอง คุณจะละเว้นพร็อพเพอร์ตี้นี้หรือตั้งค่าเป็นอะไรก็ได้ตามต้องการ
payload ไม่ * กำลังส่งข้อมูล โดยปกติแล้วจะเป็นวัตถุ แต่ไม่จำเป็นต้องเป็นวัตถุ

ข้อความที่ส่งผ่านเมธอด messageSW() จะใช้ MessageChannel เพื่อให้ผู้รับตอบกลับได้ หากต้องการตอบกลับข้อความ ให้โทรหา event.ports[0].postMessage(response) ใน Listener เหตุการณ์ เมธอด messageSW() จะส่งคืนสัญญาว่าจะแก้ไขให้กับ response ใดก็ตามที่คุณตอบกลับ

ต่อไปนี้เป็นตัวอย่างการส่งข้อความจากหน้าต่างไปยัง Service Worker และการได้รับการตอบกลับ โค้ดบล็อกแรกคือ Listener ข้อความใน Service Worker และบล็อกที่ 2 ใช้คลาส Workbox เพื่อส่งข้อความและรอการตอบกลับ ดังนี้

โค้ดใน sw.js:

const SW_VERSION = '1.0.0';

addEventListener('message', event => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

โค้ดใน main.js (ทำงานในหน้าต่าง):

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

การจัดการความไม่เข้ากันของเวอร์ชัน

ตัวอย่างด้านบนแสดงวิธีที่คุณใช้การตรวจสอบเวอร์ชัน Service Worker จากหน้าต่าง เราใช้ตัวอย่างนี้เนื่องจากเมื่อคุณส่งข้อความกลับไปกลับมาระหว่างหน้าต่างและ Service Worker คุณควรทราบว่า Service Worker อาจไม่ได้ใช้งานเว็บไซต์เวอร์ชันเดียวกับที่โค้ดของหน้าเว็บทำงานอยู่ และวิธีแก้ปัญหานี้ขึ้นอยู่กับว่าคุณแสดงหน้าเว็บโดยเน้นเครือข่ายหรือเน้นแคชเป็นหลัก

เครือข่ายก่อน

เมื่อแสดงเครือข่ายหน้าเว็บก่อน ผู้ใช้จะได้รับ HTML เวอร์ชันล่าสุดจากเซิร์ฟเวอร์ของคุณเสมอ อย่างไรก็ตาม ครั้งแรกที่ผู้ใช้เข้าชมเว็บไซต์อีกครั้ง (หลังจากที่คุณทำให้การอัปเดตใช้งานได้) HTML ที่ผู้ใช้ได้รับจะเป็นเวอร์ชันล่าสุด แต่โปรแกรมทำงานของบริการที่ทำงานในเบราว์เซอร์ของตนจะเป็นเวอร์ชันที่ติดตั้งก่อนหน้านี้ (อาจเป็นเวอร์ชันเก่าหลายเวอร์ชัน)

คุณต้องทำความเข้าใจเกี่ยวกับความเป็นไปได้นี้ เพราะหาก JavaScript ที่โหลดโดยเวอร์ชันปัจจุบันของหน้าส่งข้อความถึง Service Worker เวอร์ชันเก่า เวอร์ชันนั้นอาจไม่รู้ว่าต้องตอบสนองอย่างไร (หรืออาจตอบสนองด้วยรูปแบบที่เข้ากันไม่ได้)

ด้วยเหตุนี้ คุณจึงควรกำหนดเวอร์ชัน Service Worker ไว้ทุกครั้งและตรวจสอบเวอร์ชันที่เข้ากันได้ก่อนที่จะทำงานสำคัญ

ตัวอย่างเช่น ในโค้ดด้านบน หากเวอร์ชันของ Service Worker กลับมาโดยการเรียก messageSW() นั้นเก่ากว่าเวอร์ชันที่คาดไว้ คุณควรรอจนกว่าจะพบอัปเดต (ซึ่งจะเกิดขึ้นเมื่อเรียกใช้ register()) เมื่อถึงจุดนี้ คุณสามารถแจ้งให้ผู้ใช้ทราบหรืออัปเดต หรือจะข้ามขั้นตอนการรอเพื่อเปิดใช้งานบริการใหม่ทันทีก็ได้

แคชก่อน

เมื่อแสดงแคชของหน้าเว็บเป็นอันดับแรก คุณจะทราบว่าในช่วงแรกหน้าเว็บจะเป็นเวอร์ชันเดียวกับ Service Worker เสมอ (เพราะเป็นการแสดงผลหน้าเว็บ) ซึ่งตรงข้ามกับเมื่อแสดงหน้าเว็บเป็นอันดับแรกในเครือข่าย ด้วยเหตุนี้ การใช้ messageSW() ทันที จึงปลอดภัย

อย่างไรก็ตาม หากพบ Service Worker เวอร์ชันที่อัปเดตแล้วและเปิดใช้งานเมื่อหน้าเว็บเรียกใช้ register() (กล่าวคือ คุณตั้งใจข้ามขั้นตอนการรอ) การส่งข้อความถึงโปรแกรมดังกล่าวอาจไม่ปลอดภัยอีกต่อไป

กลยุทธ์หนึ่งในการจัดการความเป็นไปได้นี้คือการใช้รูปแบบการกำหนดเวอร์ชันที่ช่วยให้คุณแยกความแตกต่างระหว่างการอัปเดตที่ด่วนกับการอัปเดตที่ไม่สำคัญ และในกรณีที่มีการอัปเดตที่เสียหาย คุณจะทราบว่าการส่งข้อความถึงโปรแกรมทำงานของบริการนั้นไม่ปลอดภัย แต่คุณควรเตือนผู้ใช้ว่ากำลังใช้หน้าเวอร์ชันเก่าอยู่ และแนะนำให้ผู้ใช้โหลดซ้ำเพื่อรับการอัปเดต

ข้ามตัวช่วยในการรอ

รูปแบบการใช้งานทั่วไปสำหรับการรับส่งข้อความจาก Window to Service Worker คือการส่งข้อความ {type: 'SKIP_WAITING'} เพื่อบอกให้ Service Worker ที่ติดตั้ง ข้ามขั้นตอนการรอและเปิดใช้งาน

ตั้งแต่ Workbox v6 เป็นต้นไป คุณจะใช้เมธอด messageSkipWaiting() เพื่อส่งข้อความ {type: 'SKIP_WAITING'} ไปยัง Service Worker รอที่เชื่อมโยงกับการลงทะเบียน Service Worker ปัจจุบันได้ โปรแกรมจะไม่ทำงานเงียบๆ หากไม่มี Service Worker รออยู่

ประเภท

Workbox

คลาสที่จะช่วยจัดการการลงทะเบียน การอัปเดต และการดำเนินการกับเหตุการณ์ในวงจรการทำงานของโปรแกรมทำงานของบริการ

พร็อพเพอร์ตี้

  • เครื่องมือสร้าง

    void

    สร้างอินสแตนซ์ Workbox ใหม่ด้วย URL สคริปต์และตัวเลือกโปรแกรมทำงานของบริการ URL ของสคริปต์และตัวเลือกจะเหมือนกับ URL ที่ใช้เมื่อเรียกใช้ navigator.serviceWorker.register(scriptURL, options)

    ฟังก์ชัน constructor มีลักษณะดังนี้

    (scriptURL: string|TrustedScriptURL,registerOptions?: object)=> {...}

    • scriptURL

      สตริง|TrustedScriptURL

      สคริปต์ Service Worker ที่เชื่อมโยงกับอินสแตนซ์นี้ รองรับการใช้ TrustedScriptURL

    • registerOptions

      ออบเจ็กต์ ไม่บังคับ

  • ใช้งาน

    Promise<ServiceWorker>

  • กำลังควบคุม

    Promise<ServiceWorker>

  • getSW

    void

    แก้ไขโดยมีการอ้างอิงไปยัง Service Worker ที่ตรงกับ URL สคริปต์ของอินสแตนซ์นี้ทันทีที่พร้อมใช้งาน

    ณ เวลาลงทะเบียน หากมีโปรแกรมทำงานของบริการที่กำลังทำงานอยู่หรือกำลังรอซึ่งมี URL ของสคริปต์ที่ตรงกันอยู่แล้ว ระบบจะใช้โปรแกรมทำงานดังกล่าว (โดยที่โปรแกรมทำงานของบริการรอจะมีลำดับความสำคัญมากกว่าโปรแกรมทำงานของบริการที่ใช้งานอยู่หากตรงกันทั้งสองรายการ เนื่องจากโปรแกรมทำงานของบริการที่รออยู่เพิ่งลงทะเบียนไปเมื่อเร็วๆ นี้) หากไม่มี Service Worker ที่ใช้งานอยู่หรือที่รออยู่ที่ตรงกัน ณ เวลาที่ลงทะเบียน สัญญาจะไม่แก้ไขจนกว่าจะพบการอัปเดตและเริ่มการติดตั้ง ซึ่งเป็นจุดที่มีการใช้ Service Worker ที่ติดตั้ง

    ฟังก์ชัน getSW มีลักษณะดังนี้

    ()=> {...}

    • returns

      Promise<ServiceWorker>

  • messageSW

    void

    ส่งออบเจ็กต์ข้อมูลที่ส่งผ่านไปยัง Service Worker ที่ลงทะเบียนไว้โดยอินสแตนซ์นี้ (ผ่าน workbox-window.Workbox#getSW) และแก้ไขด้วยการตอบกลับ (หากมี)

    คุณตั้งค่าการตอบกลับได้ในเครื่องจัดการข้อความใน Service Worker โดยเรียกใช้ event.ports[0].postMessage(...) ซึ่งจะแก้ไขคำสัญญาที่ messageSW() ส่งคืน หากไม่มีการตอบกลับ คำสัญญาจะไม่มีทางแก้ปัญหาสำเร็จ

    ฟังก์ชัน messageSW มีลักษณะดังนี้

    (data: object)=> {...}

    • ข้อมูล

      ออบเจ็กต์

      ออบเจ็กต์ที่จะส่งไปยัง Service Worker

    • returns

      คำสัญญา<any>

  • messageSkipWaiting

    void

    ส่งข้อความ {type: 'SKIP_WAITING'} ไปยัง Service Worker ที่ตอนนี้อยู่ในสถานะ waiting ซึ่งเชื่อมโยงกับการลงทะเบียนปัจจุบัน

    หากไม่มีการลงทะเบียนปัจจุบันหรือไม่มี Service Worker คือ waiting การเรียกใช้ดังกล่าวจะไม่มีผลใดๆ

    ฟังก์ชัน messageSkipWaiting มีลักษณะดังนี้

    ()=> {...}

  • register

    void

    ลงทะเบียนโปรแกรมทำงานของบริการสำหรับ URL สคริปต์ของอินสแตนซ์นี้และตัวเลือกผู้ปฏิบัติงานบริการ โดยค่าเริ่มต้น วิธีนี้จะทำให้การลงทะเบียนล่าช้าลงจนกว่าจะโหลดหน้าต่างแล้ว

    ฟังก์ชัน register มีลักษณะดังนี้

    (options?: object)=> {...}

    • ตัวเลือก

      ออบเจ็กต์ ไม่บังคับ

      • ที่อยู่ติดๆ กัน

        บูลีน ไม่บังคับ

    • returns

      Promise<ServiceWorkerRegistration>

  • อัปเดต

    void

    ตรวจหาการอัปเดตสำหรับ Service Worker ที่ลงทะเบียนไว้

    ฟังก์ชัน update มีลักษณะดังนี้

    ()=> {...}

    • returns

      Promise<void>

WorkboxEventMap

พร็อพเพอร์ตี้

WorkboxLifecycleEvent

พร็อพเพอร์ตี้

  • isExternal

    บูลีน ไม่บังคับ

  • isUpdate

    บูลีน ไม่บังคับ

  • originalEvent

    กิจกรรม ไม่บังคับ

  • sw

    ServiceWorker ไม่บังคับ

  • เป้าหมาย

    WorkboxEventTarget ไม่บังคับ

  • ประเภท

    typeOperator

WorkboxLifecycleEventMap

พร็อพเพอร์ตี้

WorkboxLifecycleWaitingEvent

พร็อพเพอร์ตี้

  • isExternal

    บูลีน ไม่บังคับ

  • isUpdate

    บูลีน ไม่บังคับ

  • originalEvent

    กิจกรรม ไม่บังคับ

  • sw

    ServiceWorker ไม่บังคับ

  • เป้าหมาย

    WorkboxEventTarget ไม่บังคับ

  • ประเภท

    typeOperator

  • wasWaitingBeforeRegister

    บูลีน ไม่บังคับ

WorkboxMessageEvent

พร็อพเพอร์ตี้

  • ข้อมูล

    อะไรก็ได้

  • isExternal

    บูลีน ไม่บังคับ

  • originalEvent

    เหตุการณ์

  • ports

    typeOperator

  • sw

    ServiceWorker ไม่บังคับ

  • เป้าหมาย

    WorkboxEventTarget ไม่บังคับ

  • ประเภท

วิธีการ

messageSW()

workbox-window.messageSW(
  sw: ServiceWorker,
  data: object,
)

ส่งออบเจ็กต์ข้อมูลไปยัง Service Worker ผ่าน postMessage และแก้ไขด้วยการตอบกลับ (หากมี)

คุณตั้งค่าการตอบกลับได้ในเครื่องจัดการข้อความใน Service Worker โดยเรียกใช้ event.ports[0].postMessage(...) ซึ่งจะแก้ไขคำสัญญาที่ messageSW() ส่งคืน หากไม่มีการตั้งค่าการตอบสนอง คำสัญญาจะไม่หายไป

พารามิเตอร์

  • sw

    ServiceWorker

    Service Worker ที่จะส่งข้อความไปให้

  • ข้อมูล

    ออบเจ็กต์

    ออบเจ็กต์ที่จะส่งไปยัง Service Worker

การคืนสินค้า

  • คำสัญญา<any>