การสร้าง PWA ที่ Google ตอนที่ 1

สิ่งที่ทีม Bulletin ได้เรียนรู้เกี่ยวกับ Service Worker ขณะพัฒนา PWA

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

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

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

ข้อมูลเบื้องต้น

กระดานข่าวสารอยู่ระหว่างการพัฒนาอย่างต่อเนื่องตั้งแต่ช่วงกลางปี 2017 จนถึงกลางปี 2019

เหตุผลที่เราเลือกสร้าง PWA

ก่อนที่เราจะลงลึกถึงกระบวนการพัฒนา เรามาตรวจสอบกันว่าทำไมการสร้าง PWA ถึงน่าสนใจ ตัวเลือกสำหรับโปรเจ็กต์นี้

  • ความสามารถในการทำซ้ำได้อย่างรวดเร็ว โดยมีประโยชน์อย่างยิ่งเนื่องจาก Bulletin จะนำร่องใน ได้หลายตลาด
  • ฐานของโค้ดเดียว ผู้ใช้ของเราแบ่งระหว่าง Android กับ iOS อย่างคร่าวๆ ความหมายของ PWA เราสามารถสร้างเว็บแอปเดียวซึ่งทำงานได้บนทั้ง 2 แพลตฟอร์ม ซึ่งช่วยเพิ่มอัตราความเร็ว และผลกระทบที่เกิดจากทีม
  • อัปเดตได้อย่างรวดเร็วและไม่ขึ้นอยู่กับพฤติกรรมของผู้ใช้ PWA จะอัปเดตได้โดยอัตโนมัติ ช่วยลดจำนวนลูกค้าที่ล้าสมัย เราสามารถใช้แบ็กเอนด์ที่ใช้งานไม่ได้ การเปลี่ยนแปลงโดยให้เวลาลูกค้าย้ายข้อมูลไม่นานมาก
  • ผสานรวมกับแอปของบุคคลที่หนึ่งและแอปของบุคคลที่สามได้โดยง่าย การผสานรวมดังกล่าวเป็นข้อกำหนด สำหรับแอป การใช้ PWA มักจะหมายถึงการเปิด URL
  • ขจัดความยุ่งยากในการติดตั้งแอป

เฟรมเวิร์กของเรา

สำหรับ Bulletin เราใช้ Polymer แต่เป็นโปรแกรมที่ทันสมัยและมีการรองรับที่ดี ของเฟรมเวิร์กจึงจะใช้งานได้

สิ่งที่เราได้เรียนรู้เกี่ยวกับโปรแกรมทำงานของบริการ

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

สร้างถ้าทำได้

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

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

ห้องสมุดบางแห่งอาจใช้งานร่วมกับผู้ปฏิบัติงานบริการไม่ได้

ไลบรารี JS บางรายการแสดงสมมติฐานที่ไม่ทำงานตามที่คาดไว้เมื่อเรียกใช้โดย Service Worker สำหรับ สมมติว่า window หรือ document พร้อมใช้งาน หรือใช้ API ไม่พร้อมให้บริการ ผู้ปฏิบัติงาน (XMLHttpRequest, พื้นที่เก็บข้อมูลในเครื่อง ฯลฯ) ตรวจสอบว่ามีไลบรารีที่สำคัญที่คุณจำเป็นต้องใช้สำหรับ ที่ทำงานร่วมกับ Service Worker ได้ สำหรับ PWA นี้ เราต้องการใช้ gapi.js ในการตรวจสอบสิทธิ์ แต่เปลี่ยนเป็น ไม่ได้เพราะไม่รองรับ Service Worker ผู้เขียนห้องสมุดควรลดหรือนำออกด้วย สมมติฐานที่ไม่จำเป็นเกี่ยวกับบริบท JavaScript หากเป็นไปได้เพื่อรองรับการใช้งาน Service Worker เช่น การหลีกเลี่ยง API ที่ใช้ไม่ได้กับโปรแกรมทำงานของบริการและการหลีกเลี่ยง API ที่

หลีกเลี่ยงการเข้าถึง IndexedDB ระหว่างการเริ่มต้น

ไม่อ่าน IndexedDB เมื่อ กำลังเริ่มต้นสคริปต์โปรแกรมทำงานของบริการ มิฉะนั้นคุณอาจเข้าสู่สถานการณ์ที่ไม่พึงประสงค์นี้

  1. ผู้ใช้มีเว็บแอปที่มี IndexedDB (IDB) เวอร์ชัน N
  2. เว็บแอปใหม่พุชด้วย IDB เวอร์ชัน N+1
  3. ผู้ใช้เข้าชม PWA ซึ่งจะทริกเกอร์การดาวน์โหลด Service Worker ใหม่
  4. Service Worker ใหม่อ่านจาก IDB ก่อนลงทะเบียนเครื่องจัดการเหตุการณ์ install โดยทริกเกอร์ รอบการอัปเกรด IDB จาก N เป็น N+1
  5. เนื่องจากผู้ใช้มีไคลเอ็นต์เก่าที่ใช้เวอร์ชัน N กระบวนการอัปเกรดโปรแกรมทำงานของบริการจึงอยู่ในสถานะใช้งานอยู่ การเชื่อมต่อยังเปิดไปยังฐานข้อมูลเวอร์ชันเก่า
  6. 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 ป้องกันไม่ให้เป็นเช่นนั้น

ใช้การแสดงโฆษณาอย่างต่อเนื่อง

เนื่องจากโปรแกรมทำงานของบริการจะอัปเดตได้โดยอัตโนมัติ ผู้ใช้จึงจำกัดการอัปเกรดไม่ได้ ช่วงเวลานี้ ช่วยลดปริมาณไคลเอ็นต์ที่ล้าสมัยได้เป็นอย่างมาก เมื่อผู้ใช้เปิดแอปของเรา โปรแกรมทำงานของบริการจะให้บริการไคลเอ็นต์เก่าขณะที่โปรแกรมดาวน์โหลดไคลเอ็นต์ใหม่อย่าง Lazy Loading เมื่อ มีการดาวน์โหลดไคลเอ็นต์ใหม่ ระบบจะแจ้งให้ผู้ใช้รีเฟรชหน้าเว็บเพื่อเข้าถึงคุณลักษณะใหม่ แม้ว่า ผู้ใช้เพิกเฉยต่อคำขอนี้ ในครั้งถัดไปที่รีเฟรชหน้าเว็บ ผู้ใช้จะได้รับ ไคลเอ็นต์ ด้วยเหตุนี้ ค่อนข้างยากที่ผู้ใช้จะปฏิเสธการอัปเดต สำหรับแอป iOS/Android

เราสามารถผลักดันการเปลี่ยนแปลงแบ็กเอนด์ที่ส่งผลกับส่วนอื่นในระบบได้โดยใช้เวลาสั้นๆ ในการย้ายข้อมูล ลูกค้า โดยปกติ เราจะให้เวลาผู้ใช้ 1 เดือนในการอัปเดต ลูกค้าใหม่ก่อนที่จะ การเปลี่ยนแปลงที่ส่งผลกับส่วนอื่นในระบบ เนื่องจากแอปจะแสดงแม้ไม่มีอัปเดต จริงๆ แล้วไคลเอ็นต์รุ่นเก่าก็ทำได้ ที่จะมีอยู่จริงหากผู้ใช้ไม่ได้เปิดแอปเป็นเวลานาน Service Worker ใน iOS ถูกขับออกหลังจาก 2-3 สัปดาห์ กรณีนี้จึงไม่เกิดขึ้น สำหรับ Android ปัญหานี้อาจลดลงได้ด้วยการไม่แสดง ไม่มีอัปเดต หรือจะหมดอายุเนื้อหาด้วยตนเองหลังผ่านไป 2-3 สัปดาห์ ในทางปฏิบัติ เราไม่เคยพบ จากไคลเอ็นต์ที่ไม่มีอัปเดต ระดับความเข้มงวดที่ทีมจะต้องดำเนินการจะขึ้นอยู่กับการใช้งานเฉพาะของทีม แต่ PWA มีความยืดหยุ่นมากกว่าแอป iOS/Android เป็นอย่างมาก

การรับค่าคุกกี้ใน Service Worker

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

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

ข้อผิดพลาดสำหรับ Service Worker ที่ไม่ได้สร้างขึ้น

ตรวจสอบว่าสคริปต์ของ Service Worker มีการเปลี่ยนแปลง หากมีการเปลี่ยนแปลงไฟล์ที่แคชไว้แบบคงที่

รูปแบบ PWA ทั่วไปคือให้โปรแกรมทำงานของบริการติดตั้งไฟล์แอปพลิเคชันแบบคงที่ทั้งหมดในระหว่าง install ซึ่งช่วยให้ไคลเอ็นต์เข้าถึงแคช Cache Storage API ได้โดยตรงสำหรับ การเข้าชมครั้งต่อมา ระบบจะติดตั้งโปรแกรมทำงานของบริการเมื่อเบราว์เซอร์ตรวจพบว่าบริการ สคริปต์ของผู้ปฏิบัติงานมีการเปลี่ยนแปลงในทางใดทางหนึ่ง เราจึงต้องตรวจสอบให้แน่ใจว่าตัวไฟล์สคริปต์ของ Service Worker นั้นเอง มีการเปลี่ยนแปลงบางอย่างเมื่อไฟล์ที่แคชไว้มีการเปลี่ยนแปลง เราดำเนินการด้วยตนเองโดยการฝังแฮชของ ชุดไฟล์ทรัพยากรแบบคงที่ภายในสคริปต์ ของ Service Worker ดังนั้น แต่ละรุ่นจึงสร้าง ไฟล์ JavaScript ของ Service Worker ไลบรารีของ Service Worker เช่น Workbox จะดำเนินการกระบวนการนี้ให้คุณโดยอัตโนมัติ

การทดสอบ 1 หน่วย

Service Work API ทำงานโดยเพิ่ม Listener เหตุการณ์ลงในออบเจ็กต์ส่วนกลาง เช่น

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

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

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

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

โปรดติดตามส่วนที่ 2 และ 3 ต่อไป

ในส่วนที่ 2 และ 3 ของซีรีส์นี้ เราจะพูดถึงการจัดการสื่อและปัญหาเฉพาะของ iOS หากคุณ คุณต้องการถามเราเพิ่มเติมเกี่ยวกับการสร้าง PWA ที่ Google โปรดไปที่โปรไฟล์ผู้เขียนของเราเพื่อดูข้อมูล วิธีติดต่อเรา: