ชีวิตของ Service Worker

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

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

นิยามคำศัพท์

ก่อนที่จะเข้าสู่วงจรการทำงานของโปรแกรมทำงาน คุณควรนิยามคำศัพท์เกี่ยวกับวิธีการทำงานของวงจรดังกล่าว

การควบคุมและขอบเขต

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

ขอบเขต

ขอบเขตของ Service Worker พิจารณาจากตำแหน่งในเว็บเซิร์ฟเวอร์ หาก Service Worker ทำงานในหน้าเว็บที่ /subdir/index.html และอยู่ที่ /subdir/sw.js ขอบเขตของโปรแกรมทำงานของบริการจะเป็น /subdir/ หากต้องการดูแนวคิดของการใช้งานจริงของขอบเขต โปรดดูตัวอย่างนี้

  1. ไปที่ https://service-worker-scope-viewer.glitch.me/subdir/index.html จะมีข้อความปรากฏขึ้นว่าไม่มี Service Worker ที่ควบคุมหน้านี้อยู่ แต่หน้าเว็บนั้นจะลงทะเบียน Service Worker จาก https://service-worker-scope-viewer.glitch.me/subdir/sw.js
  2. โหลดหน้าเว็บซ้ำ เนื่องจาก Service Worker ลงทะเบียนแล้วและกำลังทำงานอยู่ จึงเป็นการควบคุมหน้าเว็บ แบบฟอร์มที่มีขอบเขต สถานะปัจจุบันของโปรแกรมทำงาน และ URL ของ Service Worker จะปรากฏขึ้น หมายเหตุ: การต้องโหลดหน้าเว็บซ้ำไม่ได้เกี่ยวข้องกับขอบเขตนี้เลย แต่เกี่ยวข้องกับวงจรการทำงานของโปรแกรมทำงานของบริการ ซึ่งจะได้อธิบายในภายหลัง
  3. จากนั้นไปที่ https://service-worker-scope-viewer.glitch.me/index.html แม้ว่าจะมีการลงทะเบียน Service Worker ในต้นทางนี้ แต่ยังคงมีข้อความแจ้งว่าไม่มี Service Worker อยู่ในขณะนี้ เนื่องจากหน้านี้ไม่ได้อยู่ในขอบเขตของ Service Worker ที่ลงทะเบียนไว้

ขอบเขตจะจำกัดหน้าที่โปรแกรมทำงานของบริการควบคุม ในตัวอย่างนี้หมายความว่า Service Worker ที่โหลดจาก /subdir/sw.js จะควบคุมได้เฉพาะหน้าที่อยู่ใน /subdir/ หรือแผนผังย่อยเท่านั้น

วิธีการข้างต้นคือวิธีกำหนดขอบเขตโดยค่าเริ่มต้น แต่ลบล้างขอบเขตสูงสุดที่อนุญาตได้ด้วยการตั้งค่าส่วนหัวการตอบกลับ Service-Worker-Allowed รวมถึงส่ง ตัวเลือก scope ไปยังเมธอด register

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

ลูกค้า

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

วงจรการทำงานของ Service Worker ใหม่

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

การลงทะเบียน

การลงทะเบียนคือขั้นตอนเริ่มต้นของวงจรการทำงานของโปรแกรมทำงานของบริการ ดังนี้

<!-- In index.html, for example: -->
<script>
  // Don't register the service worker
  // until the page has fully loaded
  window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js').then(() => {
        console.log('Service worker registered!');
      }).catch((error) => {
        console.warn('Error registering service worker:');
        console.warn(error);
      });
    }
  });
</script>

โค้ดนี้จะทำงานในเทรดหลักและจะทำงานต่อไปนี้

  1. เนื่องจากการเข้าชมเว็บไซต์ครั้งแรกของผู้ใช้เกิดขึ้นโดยไม่มี Service Worker ที่ลงทะเบียนไว้ โปรดรอจนกว่าหน้าเว็บจะโหลดเสร็จสมบูรณ์ก่อนที่จะลงทะเบียน การทำเช่นนี้จะหลีกเลี่ยงการช่วงชิงแบนด์วิดท์ หากโปรแกรมทำงานของบริการเก็บข้อมูลใดๆ ไว้ล่วงหน้า
  2. แม้ว่าจะมีการรองรับโปรแกรมทำงานของบริการไว้เป็นอย่างดี แต่การตรวจสอบอย่างรวดเร็วจะช่วยหลีกเลี่ยงข้อผิดพลาดในเบราว์เซอร์ที่ไม่รองรับได้
  3. เมื่อหน้าเว็บโหลดเสร็จแล้ว และหากรองรับ Service Worker ให้ลงทะเบียน /sw.js

สิ่งสำคัญที่ควรทำความเข้าใจมีดังนี้

  • โปรแกรมทำงานของบริการพร้อมใช้งานผ่าน HTTPS หรือ localhost เท่านั้น
  • หากเนื้อหาของ Service Worker มีข้อผิดพลาดทางไวยากรณ์ การลงทะเบียนจะไม่สำเร็จ และ Service Worker จะถูกยกเลิก
  • หมายเหตุ: โปรแกรมทำงานของบริการดำเนินงานภายในขอบเขต ขอบเขตคือต้นทางทั้งหมด เนื่องจากโหลดจากไดเรกทอรีรูท
  • เมื่อเริ่มการลงทะเบียน สถานะของ Service Worker จะตั้งเป็น 'installing'

เมื่อการลงทะเบียนเสร็จสิ้น การติดตั้งจะเริ่มต้นขึ้น

การติดตั้ง

Service Worker เริ่มการทำงานของเหตุการณ์ install หลังการลงทะเบียน ระบบจะเรียกใช้ install เพียงครั้งเดียวต่อ Service Worker และจะไม่เริ่มทำงานอีกจนกว่าจะอัปเดต ลงทะเบียนโค้ดเรียกกลับสำหรับเหตุการณ์ install ในขอบเขตของผู้ปฏิบัติงานได้ด้วย addEventListener:

// /sw.js
self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v1';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v1'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.bc7b80b7.css',
      '/css/home.fe5d0b23.css',
      '/js/home.d3cc4ba4.js',
      '/js/jquery.43ca4933.js'
    ]);
  }));
});

การดำเนินการนี้จะสร้างอินสแตนซ์ Cache ใหม่และแคชเนื้อหาล่วงหน้า เราจะมีโอกาสได้พูดคุยกันอีกมากในภายหลังเกี่ยวกับการสร้างแคช ดังนั้นเรามาโฟกัสที่บทบาทของ event.waitUntil กันต่อดีกว่า event.waitUntil ยอมรับสัญญาและรอจนกว่าคำขอนั้นจะได้รับการแก้ไข ในตัวอย่างนี้ คำสัญญานั้นจะทำ 2 สิ่งที่ไม่พร้อมกัน ได้แก่

  1. สร้างอินสแตนซ์ Cache ใหม่ชื่อ 'MyFancyCache_v1'
  2. หลังจากสร้างแคชแล้ว ระบบจะแคชอาร์เรย์ของ URL เนื้อหาล่วงหน้าโดยใช้เมธอด addAll แบบอะซิงโครนัส

การติดตั้งจะไม่สำเร็จหากปฏิเสธคำมั่นสัญญาที่ส่งไปยัง event.waitUntil ในกรณีนี้ โปรแกรมทำงานของบริการจะถูกยกเลิก

หากคำสัญญาresolve การติดตั้งสำเร็จและสถานะของโปรแกรมทำงานของบริการจะเปลี่ยนเป็น 'installed' และหลังจากนั้นจะเปิดใช้งาน

การดำเนินการ

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

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

การจัดการการอัปเดต Service Worker

เมื่อทำให้ Service Worker แรกใช้งานได้แล้ว อาจต้องมีการอัปเดตในภายหลัง เช่น อาจต้องอัปเดตหากมีการเปลี่ยนแปลงในการจัดการคำขอหรือตรรกะการแคชล่วงหน้า

เมื่อมีการอัปเดต

เบราว์เซอร์จะตรวจหาอัปเดตของโปรแกรมทำงานของบริการในกรณีต่อไปนี้

  • ผู้ใช้จะไปยังหน้าเว็บที่อยู่ในขอบเขตของโปรแกรมทำงานของบริการ
  • navigator.serviceWorker.register() จะมีการเรียกด้วย URL ที่ต่างจาก Service Worker ที่ติดตั้งอยู่ในปัจจุบัน แต่อย่าเปลี่ยน URL ของ Service Worker
  • navigator.serviceWorker.register() จะถูกเรียกด้วย URL เดียวกันกับ Service Worker ที่ติดตั้งไว้ แต่มีขอบเขตต่างกัน ขอย้ำว่าให้หลีกเลี่ยงกรณีเช่นนี้โดยคงขอบเขตไว้ที่รูทของต้นทาง หากเป็นไปได้
  • เมื่อมีการทริกเกอร์เหตุการณ์ เช่น 'push' หรือ 'sync' ภายใน 24 ชั่วโมงที่ผ่านมา แต่ยังไม่ต้องกังวลเกี่ยวกับเหตุการณ์เหล่านี้

การอัปเดตเกิดขึ้นได้อย่างไร

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

เบราว์เซอร์จะตรวจหาการเปลี่ยนแปลงด้วยวิธีต่อไปนี้

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

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

การทริกเกอร์การตรวจสอบการอัปเดตด้วยตนเอง

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

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

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

การติดตั้ง

เมื่อใช้ Bundler เพื่อสร้างเนื้อหาแบบคงที่ เนื้อหาเหล่านั้นจะมีแฮชในชื่อ เช่น framework.3defa9d2.js สมมติว่าเนื้อหาบางส่วนถูกแคชล่วงหน้าไว้สำหรับการเข้าถึงแบบออฟไลน์ในภายหลัง การดำเนินการนี้จะต้องมีการอัปเดต Service Worker เพื่อแคชเนื้อหาที่อัปเดตล่วงหน้า:

self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v2';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v2'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.ced4aef2.css',
      '/css/home.cbe409ad.css',
      '/js/home.109defa4.js',
      '/js/jquery.38caf32d.js'
    ]);
  }));
});

มี 2 สิ่งที่แตกต่างจากตัวอย่างเหตุการณ์ install แรกจากก่อนหน้านี้

  1. ระบบสร้างอินสแตนซ์ Cache ใหม่ที่มีคีย์ 'MyFancyCacheName_v2' แล้ว
  2. ชื่อของเนื้อหาที่จัดเก็บในแคชล่วงหน้ามีการเปลี่ยนแปลง

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

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

การดำเนินการ

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

self.addEventListener('activate', (event) => {
  // Specify allowed cache keys
  const cacheAllowList = ['MyFancyCacheName_v2'];

  // Get all the currently active `Cache` instances.
  event.waitUntil(caches.keys().then((keys) => {
    // Delete all caches that aren't in the allow list:
    return Promise.all(keys.map((key) => {
      if (!cacheAllowList.includes(key)) {
        return caches.delete(key);
      }
    }));
  }));
});

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

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

ใช้งานได้อย่างไม่มีที่สิ้นสุด

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

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