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