ชีวิตของ Service Worker

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

ก่อนเจาะลึกเกี่ยวกับ Workbox คุณควรทำความเข้าใจวงจรของ Service Worker เพื่อให้เข้าใจสิ่งที่ Workbox ทํา

การกําหนดคําศัพท์

ก่อนเข้าสู่วงจรของ Service Worker เราขออธิบายคำศัพท์บางอย่างเกี่ยวกับวิธีการทำงานของวงจรนี้

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

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

ขอบเขต

ขอบเขตของ Service Worker จะกำหนดโดยตำแหน่งในเว็บเซิร์ฟเวอร์ หาก Service Worker ทำงานในหน้าเว็บที่ /subdir/index.html และอยู่ใน /subdir/sw.js ขอบเขตของ Service Worker นั้นคือ /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 หมายเหตุ: การต้องโหลดหน้าเว็บซ้ำนั้นไม่เกี่ยวข้องกับขอบเขต แต่เกี่ยวข้องกับวงจรชีวิตของ Service Worker ซึ่งจะอธิบายในภายหลัง
  3. จากนั้นไปที่ https://service-worker-scope-viewer.glitch.me/index.html แม้ว่าจะมีการลงทะเบียน Service Worker ในต้นทางนี้ แต่ยังคงมีข้อความว่าไม่มี Service Worker ในปัจจุบัน นั่นเป็นเพราะหน้านี้ไม่ได้อยู่ในขอบเขตของ Service Worker ที่ลงทะเบียน

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

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

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

ลูกค้า

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

วงจรชีวิตของ Service Worker ใหม่

คุณต้องสร้าง Service Worker ก่อนเพื่อให้ควบคุมหน้าเว็บได้ มาเริ่มกันที่สิ่งที่จะเกิดขึ้นเมื่อมีการทําให้ 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 ที่ลงทะเบียนไว้ ให้รอจนกว่าหน้าเว็บจะโหลดจนเสร็จสมบูรณ์ก่อนลงทะเบียน วิธีนี้จะช่วยหลีกเลี่ยงการแย่งแบนด์วิดท์หาก Service Worker แคชข้อมูลไว้ล่วงหน้า
  2. แม้ว่าเบราว์เซอร์จะรองรับ Service Worker เป็นอย่างดี แต่การตรวจสอบอย่างรวดเร็วจะช่วยหลีกเลี่ยงข้อผิดพลาดในเบราว์เซอร์ที่ไม่รองรับ
  3. เมื่อหน้าเว็บโหลดเสร็จแล้ว และหากระบบรองรับ Service Worker ให้ลงทะเบียน /sw.js

สิ่งสำคัญที่ควรทราบมีดังนี้

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

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

การติดตั้ง

บริการเวิร์กเกอร์จะเรียกเหตุการณ์ 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 แบบแอซิงโครนัส

การติดตั้งจะล้มเหลวหาก Promise ที่ส่งไปยัง event.waitUntilถูกปฏิเสธ หากเป็นเช่นนั้น ระบบจะทิ้ง Service Worker

หากสัญญาสำเร็จ การติดตั้งจะสำเร็จและสถานะของ Service Worker จะเปลี่ยนเป็น 'installed' จากนั้นจะเปิดใช้งาน

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

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

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

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

เมื่อติดตั้งใช้งาน Service Worker รายการแรกแล้ว ก็อาจต้องอัปเดตในภายหลัง เช่น คุณอาจต้องอัปเดตหากมีการเปลี่ยนแปลงในการจัดการคําขอหรือตรรกะการแคชล่วงหน้า

เวลาที่อัปเดต

เบราว์เซอร์จะตรวจหาการอัปเดต Service Worker ในกรณีต่อไปนี้

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

วิธีการอัปเดต

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

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

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

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

เรียกให้ระบบตรวจสอบการอัปเดตด้วยตนเอง

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

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

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

การติดตั้ง

เมื่อใช้เครื่องมือรวมเพื่อสร้างชิ้นงานแบบคงที่ ชิ้นงานเหล่านั้นจะมีแฮชในชื่อ เช่น 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'
    ]);
  }));
});

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

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

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

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

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

เมื่อติดตั้ง Service Worker ที่อัปเดตแล้วและระยะรอสิ้นสุดลง 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 ใหม่จะควบคุมหน้าเว็บแทนที่ Service Worker เก่า

วงจรนี้ดำเนินต่อไปเรื่อยๆ

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

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