สิ่งที่ทีม Bulletin ได้เรียนรู้เกี่ยวกับ Service Worker ขณะพัฒนา PWA
นี่เป็นบล็อกโพสต์ชุดแรกเกี่ยวกับบทเรียนที่ทีม Google Bulletin ได้เรียนรู้ขณะสร้าง PWA ที่แสดงต่อภายนอก ในโพสต์เหล่านี้ เราจะแชร์ความท้าทายที่เราพบ วิธีที่เราใช้เพื่อเอาชนะปัญหา และคำแนะนำทั่วไปในการหลีกเลี่ยงข้อผิดพลาด ข้อมูลนี้ไม่ได้เป็นภาพรวมที่สมบูรณ์ของ PWA โดยมีจุดประสงค์เพื่อแชร์สิ่งที่ได้เรียนรู้จากประสบการณ์ของทีม
ในโพสต์แรกนี้ เราจะพูดถึงข้อมูลเบื้องต้นเล็กน้อยก่อน จากนั้นจึงเจาะลึกข้อมูลทั้งหมดที่เราได้เรียนรู้เกี่ยวกับ Service Worker
ข้อมูลเบื้องต้น
กระดานข่าวสารอยู่ระหว่างการพัฒนาตั้งแต่กลางปี 2017 ถึงกลางปี 2019
เหตุผลที่เราเลือกสร้าง PWA
ก่อนจะเจาะลึกกระบวนการพัฒนา เรามาพิจารณากันว่าเหตุใดการสร้าง PWA จึงเป็นตัวเลือกที่น่าสนใจสำหรับโปรเจ็กต์นี้
- ความสามารถในการทําซ้ำได้อย่างรวดเร็ว มีประโยชน์อย่างยิ่งเนื่องจาก Bulletin จะเริ่มใช้งานในหลายตลาด
- ฐานโค้ดเดียว ผู้ใช้ของเราแบ่งออกเป็น Android และ iOS เท่าๆ กันโดยประมาณ PWA ช่วยให้เราสามารถสร้างเว็บแอปเดียวที่ใช้งานได้บนทั้ง 2 แพลตฟอร์ม ซึ่งช่วยเพิ่มความเร็วและผลลัพธ์ของทีม
- อัปเดตได้อย่างรวดเร็วและไม่ขึ้นอยู่กับพฤติกรรมของผู้ใช้ PWA สามารถอัปเดตโดยอัตโนมัติ ซึ่งจะช่วยลดจำนวนไคลเอ็นต์ที่ล้าสมัยในการใช้งานจริง เราทําการเปลี่ยนแปลงที่ส่งผลต่อแบ็กเอนด์ได้โดยมีเวลาในการย้ายข้อมูลให้ลูกค้าเพียงสั้นๆ
- ผสานรวมกับแอปของบุคคลที่หนึ่งและบุคคลที่สามได้อย่างง่ายดาย การผสานรวมดังกล่าวเป็นข้อกำหนดสำหรับแอป แต่ PWA มักจะหมายถึงการเปิด URL เพียงอย่างเดียว
- ลดความยุ่งยากในการติดตั้งแอป
เฟรมเวิร์กของเรา
สำหรับ Bulletin เราใช้ Polymer แต่จะใช้เฟรมเวิร์กสมัยใหม่ที่ได้รับการรองรับอย่างดีก็ได้
สิ่งที่เราได้เรียนรู้เกี่ยวกับ Service Worker
คุณจะมี PWA ไม่ได้หากไม่มี service worker Service Worker ช่วยให้คุณทำสิ่งต่างๆ ได้มากมาย เช่น กลยุทธ์การแคชขั้นสูง ความสามารถในการทำงานแบบออฟไลน์ การซิงค์ในเบื้องหลัง และอื่นๆ แม้ว่า Service Worker จะเพิ่มความซับซ้อน แต่เราพบว่าประโยชน์ของ Service Worker นั้นมากกว่าความซับซ้อนที่เพิ่มขึ้น
สร้างรายงานหากทำได้
หลีกเลี่ยงการเขียนสคริปต์ Service Worker ด้วยตนเอง การเขียน Service Worker ด้วยตนเองต้องจัดการทรัพยากรที่แคชไว้และเขียนตรรกะใหม่ด้วยตนเอง ซึ่งเป็นสิ่งที่พบได้ทั่วไปในคลัง Service Worker ส่วนใหญ่ เช่น Workbox
อย่างไรก็ตาม เราใช้ไลบรารีเพื่อสร้างและจัดการ Service Worker ไม่ได้เนื่องจากสแต็กเทคโนโลยีภายใน ข้อมูลด้านล่างนี้จึงสะท้อนถึงสิ่งที่เราได้เรียนรู้ อ่านเพิ่มเติมได้ที่ข้อควรระวังสำหรับ Service Worker ที่ไม่ได้สร้าง
ไลบรารีบางรายการใช้ร่วมกับ Service Worker ไม่ได้
ไลบรารี JS บางรายการจะทำการคาดเดาที่ไม่ทำงานตามที่คาดไว้เมื่อเรียกใช้โดย Service Worker เช่น สมมติว่า window
หรือ document
พร้อมใช้งาน หรือใช้ API ที่ไม่พร้อมให้บริการแก่ผู้ให้บริการ (XMLHttpRequest
, พื้นที่เก็บข้อมูลในเครื่อง ฯลฯ) ตรวจสอบว่าไลบรารีสําคัญที่จําเป็นสําหรับแอปพลิเคชันของคุณเข้ากันได้กับ Service Worker สําหรับ PWA นี้ เราต้องการใช้ gapi.js เพื่อตรวจสอบสิทธิ์ แต่ทําไม่ได้เนื่องจากไม่รองรับ Service Worker ผู้เขียนไลบรารีควรลดหรือนําการคาดเดาที่ไม่จําเป็นเกี่ยวกับบริบท JavaScript ออกหากเป็นไปได้เพื่อรองรับกรณีการใช้งาน Service Worker เช่น หลีกเลี่ยง API ที่เข้ากันไม่ได้กับ Service Worker และหลีกเลี่ยงสถานะส่วนกลาง
หลีกเลี่ยงการเข้าถึง IndexedDB ระหว่างการเริ่มต้น
อย่าอ่าน IndexedDB เมื่อเริ่มต้นสคริปต์ Service Worker ไม่เช่นนั้นคุณอาจพบสถานการณ์ที่ไม่พึงประสงค์นี้
- ผู้ใช้มีเว็บแอปที่ใช้ IndexedDB (IDB) เวอร์ชัน N
- เว็บแอปใหม่ได้รับการพุชด้วย IDB เวอร์ชัน N+1
- ผู้ใช้เข้าชม PWA ซึ่งทริกเกอร์การดาวน์โหลด Service Worker ใหม่
- Service Worker ใหม่จะอ่านจาก IDB ก่อนที่จะลงทะเบียน
install
event handler ซึ่งจะทริกเกอร์วงจรการอัปเกรด IDB จาก N เป็น N+1 - เนื่องจากผู้ใช้มีไคลเอ็นต์เวอร์ชันเก่า N กระบวนการอัปเกรด Service Worker จึงหยุดชะงักเนื่องจากการเชื่อมต่อที่ใช้งานอยู่ยังคงเปิดอยู่สำหรับฐานข้อมูลเวอร์ชันเก่า
- Service Worker ค้างและไม่ติดตั้ง
ในกรณีของเรา แคชไม่ถูกต้องเมื่อติดตั้ง Service Worker ดังนั้นหากไม่เคยติดตั้ง Service Worker ผู้ใช้ก็จะไม่ได้รับการอัปเดตแอป
สร้างให้ยืดหยุ่น
แม้ว่าสคริปต์ Service Worker จะทำงานในเบื้องหลัง แต่ก็สิ้นสุดได้ทุกเมื่อ แม้ในขณะที่ดำเนินการ I/O (เครือข่าย, IDB ฯลฯ) อยู่ก็ตาม กระบวนการที่ทำงานเป็นเวลานานควรกลับมาทำงานต่อได้ทุกเมื่อ
ในกรณีที่กระบวนการซิงค์อัปโหลดไฟล์ขนาดใหญ่ไปยังเซิร์ฟเวอร์และบันทึกลงใน IDB ทางออกของเราสำหรับการอัปโหลดบางส่วนที่ถูกขัดจังหวะคือใช้ประโยชน์จากระบบที่กลับมาดำเนินการต่อได้ของไลบรารีการอัปโหลดภายใน โดยบันทึก URL การอัปโหลดที่กลับมาดำเนินการต่อได้ไปยัง IDB ก่อนการอัปโหลด และใช้ URL ดังกล่าวเพื่ออัปโหลดต่อหากดำเนินการไม่เสร็จสมบูรณ์ในครั้งแรก นอกจากนี้ ก่อนการดำเนินการ I/O ที่ทำงานเป็นเวลานาน ระบบจะบันทึกสถานะไปยัง IDB เพื่อระบุตำแหน่งในกระบวนการสำหรับแต่ละระเบียน
อย่าใช้สถานะส่วนกลาง
เนื่องจาก Service Worker อยู่ในบริบทอื่น คุณจึงอาจไม่เห็นสัญลักษณ์หลายอย่างที่คาดไว้ โค้ดจำนวนมากของเราทํางานทั้งในบริบท window
และบริบท Service Worker (เช่น การบันทึก การตั้งค่าสถานะ การซิงค์ ฯลฯ) โค้ดต้องป้องกันบริการที่ใช้ เช่น พื้นที่เก็บข้อมูลในเครื่องหรือคุกกี้ คุณสามารถใช้
globalThis
เพื่ออ้างอิงออบเจ็กต์ส่วนกลางในลักษณะที่จะทํางานได้ในทุกบริบท นอกจากนี้ ให้ใช้ข้อมูลที่จัดเก็บไว้ในตัวแปรส่วนกลางอย่างประหยัด เนื่องจากไม่มีการรับประกันว่าสคริปต์จะสิ้นสุดลงเมื่อใดและสถานะจะถูกนำออกเมื่อใด
การพัฒนาในพื้นที่
องค์ประกอบหลักของ Service Worker คือการจัดเก็บแคชทรัพยากรไว้ในเครื่อง อย่างไรก็ตาม ในระหว่างการพัฒนา การดำเนินการนี้จะตรงข้ามกับสิ่งที่คุณต้องการโดยสิ้นเชิง โดยเฉพาะเมื่อมีการอัปเดตแบบไม่สนใจ คุณยังคงต้องติดตั้งโปรแกรมทำงานของเซิร์ฟเวอร์เพื่อให้แก้ไขข้อบกพร่องหรือทำงานร่วมกับ API อื่นๆ ได้ เช่น การซิงค์ในเบื้องหลังหรือการแจ้งเตือน ใน Chrome คุณจะทำสิ่งต่อไปนี้ได้ผ่านเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ Chrome โดยเปิดใช้ช่องทําเครื่องหมายข้ามสําหรับเครือข่าย (แผงแอปพลิเคชัน > แผงService Worker) นอกเหนือจากการเปิดใช้ช่องทําเครื่องหมายปิดใช้แคชในแผงเครือข่ายเพื่อปิดใช้แคชหน่วยความจําด้วย เราเลือกใช้วิธีแก้ปัญหาอื่นเพื่อรองรับเบราว์เซอร์เพิ่มเติม โดยเพิ่ม Flag เพื่อปิดใช้การแคชใน Service Worker ซึ่งเปิดใช้โดยค่าเริ่มต้นในบิลด์ของนักพัฒนาซอฟต์แวร์ วิธีนี้ช่วยให้นักพัฒนาแอปได้รับการเปลี่ยนแปลงล่าสุดเสมอโดยไม่มีปัญหาการแคช คุณต้องใส่ส่วนหัว Cache-Control: no-cache
ด้วยเพื่อป้องกันไม่ให้เบราว์เซอร์แคชชิ้นงาน
ประภาคาร
Lighthouse มีเครื่องมือแก้ไขข้อบกพร่องหลายรายการที่เป็นประโยชน์สำหรับ PWA โดยเครื่องมือนี้จะสแกนเว็บไซต์และสร้างรายงานเกี่ยวกับ PWA, ประสิทธิภาพ, การช่วยเหลือพิเศษ, SEO และแนวทางปฏิบัติแนะนำอื่นๆ เราขอแนะนําให้เรียกใช้ Lighthouse ในการผสานรวมอย่างต่อเนื่องเพื่อแจ้งเตือนคุณในกรณีที่คุณละเมิดเกณฑ์ข้อใดข้อหนึ่งในการเป็น PWA เรื่องนี้เคยเกิดขึ้นกับเราครั้งหนึ่ง นั่นคือ Service Worker ไม่ได้ติดตั้ง และเราไม่ทันสังเกตเห็นก่อนที่จะมีการพุชไปยังเวอร์ชันที่ใช้งานจริง การมี Lighthouse เป็นส่วนหนึ่งของ CI จะช่วยป้องกันปัญหานี้ได้
ใช้การส่งมอบอย่างต่อเนื่อง
เนื่องจาก Service Worker สามารถอัปเดตโดยอัตโนมัติ ผู้ใช้จึงไม่สามารถจํากัดการอัปเกรดได้ วิธีนี้ช่วยลดจำนวนไคลเอ็นต์เวอร์ชันเก่าในการใช้งานได้อย่างมาก เมื่อผู้ใช้เปิดแอปของเรา บริการเวิร์กเกอร์จะแสดงไคลเอ็นต์เดิมขณะที่ดาวน์โหลดไคลเอ็นต์ใหม่แบบ Lazy เมื่อดาวน์โหลดไคลเอ็นต์ใหม่แล้ว ระบบจะแจ้งให้ผู้ใช้รีเฟรชหน้าเว็บเพื่อเข้าถึงฟีเจอร์ใหม่ แม้ว่าผู้ใช้จะเพิกเฉยต่อคำขอนี้ แต่ครั้งถัดไปที่รีเฟรชหน้าเว็บ ผู้ใช้จะได้รับไคลเอ็นต์เวอร์ชันใหม่ ด้วยเหตุนี้ ผู้ใช้จึงปฏิเสธการอัปเดตได้ยากกว่าเมื่อเทียบกับแอป iOS/Android
เราทําการเปลี่ยนแปลงที่ส่งผลต่อแบ็กเอนด์ได้โดยมีเวลาย้ายข้อมูลให้ลูกค้าในระยะเวลาสั้นๆ โดยปกติแล้ว เราจะให้เวลาผู้ใช้ 1 เดือนในการอัปเดตเป็นไคลเอ็นต์เวอร์ชันใหม่ก่อนที่จะทำการเปลี่ยนแปลงที่ส่งผลอย่างมาก เนื่องจากแอปจะแสดงในขณะที่ล้าสมัย จึงเป็นไปได้ที่ไคลเอ็นต์เวอร์ชันเก่าจะยังคงมีอยู่หากผู้ใช้ไม่ได้เปิดแอปเป็นเวลานาน ใน iOS ระบบจะนำ Service Worker ออกหลังจากผ่านไป 2-3 สัปดาห์ ดังนั้นกรณีนี้จึงไม่เกิดขึ้น สำหรับ Android ปัญหานี้อาจแก้ไขได้ด้วยการไม่แสดงเนื้อหาเมื่อล้าสมัย หรือกำหนดให้เนื้อหาหมดอายุด้วยตนเองหลังจากผ่านไป 2-3 สัปดาห์ ในทางปฏิบัติ เราไม่เคยพบปัญหาจากลูกค้าที่ล้าสมัย ระดับความเข้มงวดที่ทีมหนึ่งๆ ต้องการใช้กับ PWA ขึ้นอยู่กับกรณีการใช้งานที่เฉพาะเจาะจง แต่ PWA มีความยืดหยุ่นมากกว่าแอป iOS/Android อย่างมาก
การดึงค่าคุกกี้ใน Service Worker
บางครั้งจำเป็นต้องเข้าถึงค่าคุกกี้ในบริบทของ Service Worker ในกรณีของเรา เราจำเป็นต้องเข้าถึงค่าคุกกี้เพื่อสร้างโทเค็นเพื่อตรวจสอบสิทธิ์คำขอ API ของบุคคลที่หนึ่ง ใน Service Worker จะไม่มี API แบบซิงค์ เช่น document.cookies
คุณสามารถส่งข้อความไปยังไคลเอ็นต์ที่ใช้งานอยู่ (มีกรอบเวลา) จาก Service Worker เพื่อขอค่าคุกกี้ได้ทุกเมื่อ แต่ Service Worker อาจทำงานในเบื้องหลังโดยไม่มีไคลเอ็นต์ที่มีกรอบเวลา เช่น ในระหว่างการซิงค์ในเบื้องหลัง ในการแก้ปัญหานี้ เราจึงสร้างปลายทางในเซิร์ฟเวอร์ส่วนหน้าของเราซึ่งเพียงแค่ส่งค่าคุกกี้กลับไปให้ไคลเอ็นต์ บริการเวิร์กเกอร์ส่งคำขอเครือข่ายไปยังปลายทางนี้และอ่านการตอบกลับเพื่อรับค่าคุกกี้
เมื่อมีการเปิดตัว Cookie Store API แล้ว เบราว์เซอร์ที่รองรับ API นี้จะไม่จําเป็นต้องใช้วิธีแก้ปัญหานี้อีกต่อไป เนื่องจาก API ดังกล่าวให้การเข้าถึงคุกกี้ของเบราว์เซอร์แบบไม่สอดคล้องกันและ Service Worker สามารถใช้ได้โดยตรง
ข้อควรระวังสำหรับ Service Worker ที่ไม่ได้สร้าง
ตรวจสอบว่าสคริปต์ Service Worker มีการเปลี่ยนแปลงหากไฟล์แคชแบบคงที่มีการเปลี่ยนแปลง
รูปแบบ PWA ที่พบบ่อยคือ Service Worker จะติดตั้งไฟล์แอปพลิเคชันแบบคงที่ทั้งหมดในเฟส install
ซึ่งช่วยให้ไคลเอ็นต์เข้าถึงแคชของ Cache Storage API ได้โดยตรงสำหรับการเข้าชมครั้งต่อๆ ไปทั้งหมด ระบบจะติดตั้ง Service Worker เฉพาะเมื่อเบราว์เซอร์ตรวจพบว่าสคริปต์ Service Worker มีการเปลี่ยนแปลงในลักษณะใดลักษณะหนึ่ง เราจึงต้องตรวจสอบว่าไฟล์สคริปต์ Service Worker เองมีการเปลี่ยนแปลงในลักษณะใดลักษณะหนึ่งเมื่อไฟล์ที่แคชไว้มีการเปลี่ยนแปลง เราทําด้วยตนเองโดยการฝังแฮชของไฟล์ชุดทรัพยากรแบบคงที่ภายในสคริปต์ Service Worker เพื่อให้แต่ละรุ่นสร้างไฟล์ JavaScript ของ Service Worker ที่แตกต่างกัน ไลบรารี Service Worker เช่น Workbox จะทํากระบวนการนี้ให้คุณโดยอัตโนมัติ
การทดสอบ 1 หน่วย
Service Worker API ทํางานโดยการเพิ่ม Listener เหตุการณ์ลงในออบเจ็กต์ส่วนกลาง เช่น
self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));
การทดสอบอาจเป็นเรื่องยากเนื่องจากคุณต้องจำลองทริกเกอร์เหตุการณ์ ออบเจ็กต์เหตุการณ์ รอการเรียกคืน respondWith()
แล้วรอพรอมต์ จากนั้นจึงยืนยันผลลัพธ์ วิธีที่ง่ายขึ้นในการกําหนดโครงสร้างนี้คือมอบหมายการติดตั้งใช้งานทั้งหมดไปยังไฟล์อื่น ซึ่งทดสอบได้ง่ายกว่า
import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));
เนื่องจากความยากลำบากในการทดสอบยูนิตสคริปต์ของ Service Worker เราจึงเก็บสคริปต์ Service Worker หลักไว้ให้เรียบง่ายที่สุดเท่าที่จะเป็นไปได้ โดยแยกการติดตั้งใช้งานส่วนใหญ่ออกเป็นโมดูลอื่นๆ เนื่องจากไฟล์เหล่านั้นเป็นเพียงโมดูล JS มาตรฐาน จึงทำการทดสอบหน่วยได้ง่ายขึ้นด้วยไลบรารีทดสอบมาตรฐาน
โปรดติดตามส่วนที่ 2 และ 3
ส่วนที่ 2 และ 3 ของชุดนี้จะพูดถึงการจัดการสื่อและปัญหาเฉพาะของ iOS หากต้องการสอบถามข้อมูลเพิ่มเติมเกี่ยวกับการสร้าง PWA ที่ Google โปรดไปที่โปรไฟล์ผู้เขียนเพื่อดูวิธีติดต่อเรา