คู่มือที่กว้างขวางสำหรับแอปพลิเคชันเว็บแบบก้าวหน้า
เผยแพร่แล้ว: 2022-03-10มันเป็นวันเกิดพ่อของฉัน ฉันต้องการสั่งเค้กช็อคโกแลตและเสื้อให้เขา ฉันไปที่ Google เพื่อค้นหาเค้กช็อกโกแลตและคลิกลิงก์แรกในผลการค้นหา มีหน้าจอว่างเปล่าสองสามวินาที ฉันไม่เข้าใจว่าเกิดอะไรขึ้น หลังจากจ้องมองอย่างอดทนไม่กี่วินาที หน้าจอมือถือของฉันก็เต็มไปด้วยเค้กที่ดูน่าอร่อย ทันทีที่ฉันคลิกที่หนึ่งในนั้นเพื่อตรวจสอบรายละเอียด ฉันได้รับป๊อปอัปอ้วนน่าเกลียด ขอให้ฉันติดตั้งแอปพลิเคชัน Android เพื่อที่ฉันจะได้สัมผัสประสบการณ์ที่ลื่นไหลในขณะที่สั่งเค้ก
ที่น่าผิดหวัง จิตสำนึกของฉันไม่อนุญาตให้ฉันคลิกที่ปุ่ม "ติดตั้ง" ทั้งหมดที่ฉันอยากทำคือสั่งเค้กชิ้นเล็กๆ แล้วออกเดินทาง
ฉันคลิกที่ไอคอนกากบาทที่ด้านขวาสุดของป๊อปอัปเพื่อออกจากป๊อปอัปโดยเร็วที่สุด แต่แล้วป๊อปอัปการติดตั้งก็อยู่ที่ด้านล่างของหน้าจอ โดยกินพื้นที่หนึ่งในสี่ของพื้นที่ทั้งหมด และด้วย UI ที่ไม่สม่ำเสมอ การเลื่อนลงมาเป็นสิ่งที่ท้าทาย ฉันสามารถสั่งเค้กดัตช์ได้
หลังจากประสบการณ์ที่เลวร้ายนี้ ความท้าทายต่อไปของฉันคือสั่งเสื้อให้พ่อ ก่อนหน้านี้ฉันค้นหา Google สำหรับเสื้อเชิ้ต ฉันคลิกลิงก์แรก และในพริบตา เนื้อหาทั้งหมดก็อยู่ตรงหน้าฉัน การเลื่อนเป็นไปอย่างราบรื่น ไม่มีแบนเนอร์การติดตั้ง ฉันรู้สึกราวกับว่าฉันกำลังเรียกดูแอปพลิเคชันดั้งเดิม มีช่วงเวลาที่การเชื่อมต่ออินเทอร์เน็ตที่แย่มากของฉันหยุดลง แต่ฉันก็ยังเห็นเนื้อหาแทนเกมไดโนเสาร์ แม้จะเล่นเน็ตไม่ดี ฉันก็ยังสามารถสั่งเสื้อเชิ้ตและกางเกงยีนส์ให้พ่อได้ ที่น่าแปลกใจที่สุดคือฉันได้รับการแจ้งเตือนเกี่ยวกับคำสั่งซื้อของฉัน
ฉันจะเรียกสิ่งนี้ว่าประสบการณ์ที่นุ่มนวล คนเหล่านี้กำลังทำสิ่งที่ถูกต้อง ทุกเว็บไซต์ควรทำเพื่อผู้ใช้ของตน เรียกว่าโปรเกรสซีฟเว็บแอป
ดังที่ Alex Russell กล่าวไว้ในโพสต์หนึ่งในบล็อกของเขา:
“มันเกิดขึ้นบนเว็บเป็นครั้งคราวที่เทคโนโลยีอันทรงพลังมีอยู่โดยปราศจากประโยชน์ของแผนกการตลาดหรือบรรจุภัณฑ์ที่ลื่นไหล พวกเขาอ้อยอิ่งและเติบโตที่บริเวณรอบนอก กลายเป็นหมวกเฒ่าไปเป็นกลุ่มเล็ก ๆ ในขณะที่คนอื่นแทบมองไม่เห็น จนกระทั่งมีคนตั้งชื่อพวกเขา”
ประสบการณ์ใช้งานบนเว็บที่ราบรื่น ซึ่งบางครั้งเรียกว่าเว็บแอปพลิเคชันโปรเกรสซีฟ
แอปพลิเคชันเว็บโปรเกรสซีฟ (PWA) เป็นวิธีการที่เกี่ยวข้องกับการผสมผสานเทคโนโลยีเพื่อสร้างแอปพลิเคชันเว็บที่มีประสิทธิภาพ ด้วยประสบการณ์ผู้ใช้ที่ดีขึ้น ผู้คนจะใช้เวลามากขึ้นบนเว็บไซต์และดูโฆษณามากขึ้น พวกเขามักจะซื้อมากขึ้นและด้วยการอัปเดตการแจ้งเตือนพวกเขามีแนวโน้มที่จะเยี่ยมชมบ่อยขึ้น Financial Times ละทิ้งแอปที่มาพร้อมเครื่องในปี 2011 และสร้างเว็บแอปโดยใช้เทคโนโลยีที่ดีที่สุดที่มีอยู่ในขณะนั้น ปัจจุบันผลิตภัณฑ์ได้เติบโตขึ้นเป็น กปภ.
แต่ทำไมหลังจากนี้ คุณจะสร้างเว็บแอปเมื่อแอปที่มาพร้อมเครื่องทำงานได้ดีเพียงพอหรือไม่
มาดูเมตริกบางส่วนที่แชร์ใน Google IO 17 กัน
อุปกรณ์ห้าพันล้านเครื่องเชื่อมต่อกับเว็บ ทำให้เว็บเป็นแพลตฟอร์มที่ใหญ่ที่สุดในประวัติศาสตร์ของการคำนวณ บนเว็บบนมือถือ ผู้เข้าชมที่ไม่ซ้ำรายเดือน 11.4 ล้านคนไปที่คุณสมบัติเว็บ 1,000 อันดับแรก และ 4 ล้านคนไปที่แอพพันอันดับแรก เว็บบนมือถือรวบรวมผู้ใช้ได้มากเป็นสี่เท่าของแอปพลิเคชันดั้งเดิม แต่จำนวนนี้ลดลงอย่างรวดเร็วเมื่อพูดถึงการมีส่วนร่วม
ผู้ใช้ใช้เวลาโดยเฉลี่ย 188.6 นาทีในแอปแบบเนทีฟ และเพียง 9.3 นาทีบนเว็บบนมือถือ แอปพลิเคชั่นดั้งเดิมใช้ประโยชน์จากพลังของระบบปฏิบัติการเพื่อส่งการแจ้งเตือนแบบพุชเพื่อให้ผู้ใช้อัปเดตที่สำคัญ พวกเขามอบประสบการณ์ผู้ใช้ที่ดีขึ้นและบูตได้เร็วกว่าเว็บไซต์ในเบราว์เซอร์ แทนที่จะพิมพ์ URL ในเว็บเบราว์เซอร์ ผู้ใช้เพียงแค่ต้องแตะไอคอนของแอปบนหน้าจอหลัก
ผู้เข้าชมเว็บส่วนใหญ่ไม่น่าจะกลับมาอีก ดังนั้นนักพัฒนาจึงหาวิธีแก้ไขปัญหาชั่วคราวในการแสดงแบนเนอร์เพื่อติดตั้งแอปพลิเคชันที่มาพร้อมเครื่อง เพื่อพยายามทำให้พวกเขามีส่วนร่วมอย่างลึกซึ้ง แต่จากนั้น ผู้ใช้จะต้องผ่านขั้นตอนที่น่าเบื่อหน่ายในการติดตั้งไบนารีของแอปพลิเคชันดั้งเดิม การบังคับให้ผู้ใช้ติดตั้งแอปพลิเคชั่นนั้นน่ารำคาญและลดโอกาสที่พวกเขาจะติดตั้งตั้งแต่แรก โอกาสสำหรับเว็บมีความชัดเจน
การอ่านที่แนะนำ : Native และ กปภ: ทางเลือก ไม่ใช่ผู้ท้าชิง!
หากเว็บแอปพลิเคชันมาพร้อมกับประสบการณ์ผู้ใช้ที่หลากหลาย การแจ้งเตือนแบบพุช การสนับสนุนออฟไลน์ และการโหลดทันที พวกเขาสามารถพิชิตโลกได้ นี่คือสิ่งที่เว็บแอปพลิเคชันโปรเกรสซีฟทำ
กปภ. มอบประสบการณ์ผู้ใช้ที่หลากหลาย เพราะมีจุดแข็งหลายประการ:
เร็ว
UI ไม่เป็นขุย การเลื่อนเป็นไปอย่างราบรื่น และแอพตอบสนองต่อการโต้ตอบของผู้ใช้อย่างรวดเร็วเชื่อถือได้
เว็บไซต์ปกติบังคับให้ผู้ใช้รอโดยไม่ทำอะไรเลยในขณะที่กำลังยุ่งอยู่กับการขี่ไปยังเซิร์ฟเวอร์ ในขณะเดียวกัน PWA จะโหลดข้อมูลในทันทีจากแคช PWA ทำงานได้อย่างราบรื่นแม้ในการเชื่อมต่อ 2G ทุกคำขอของเครือข่ายเพื่อดึงเนื้อหาหรือข้อมูลบางส่วนจะต้องผ่านพนักงานบริการ (เพิ่มเติมในภายหลัง) ซึ่งจะตรวจสอบก่อนว่าการตอบกลับสำหรับคำขอเฉพาะนั้นอยู่ในแคชแล้วหรือไม่ เมื่อผู้ใช้ได้รับเนื้อหาจริงเกือบจะในทันที แม้ในการเชื่อมต่อที่ไม่ดี พวกเขาก็ไว้วางใจแอปมากขึ้นและมองว่าแอปมีความน่าเชื่อถือมากขึ้นมีส่วนร่วม
กปภ.สามารถรับตำแหน่งบนหน้าจอหลักของผู้ใช้ได้ มันมอบประสบการณ์ที่เหมือนแอพเนทีฟโดยให้พื้นที่ทำงานแบบเต็มหน้าจอ ใช้การแจ้งเตือนแบบพุชเพื่อให้ผู้ใช้มีส่วนร่วม
ตอนนี้เรารู้แล้วว่า กปปส. นำเสนออะไรในตาราง มาดูรายละเอียดกันว่าทำไม กปปส. มีความได้เปรียบเหนือแอปพลิเคชันแบบเนทีฟ PWA สร้างขึ้นด้วยเทคโนโลยี เช่น พนักงานบริการ รายการแอปบนเว็บ การแจ้งเตือนแบบพุช และโครงสร้างข้อมูล IndexedDB/โลคัล สำหรับการแคช มาดูรายละเอียดกันทีละอย่างกัน
พนักงานบริการ
พนักงานบริการคือไฟล์ JavaScript ที่ทำงานในพื้นหลังโดยไม่รบกวนการโต้ตอบของผู้ใช้ คำขอ GET ทั้งหมดที่ส่งไปยังเซิร์ฟเวอร์ต้องผ่านพนักงานบริการ มันทำหน้าที่เหมือนพร็อกซีฝั่งไคลเอ็นต์ การสกัดกั้นคำขอของเครือข่ายจะใช้การควบคุมที่สมบูรณ์ในการตอบกลับที่ส่งกลับไปยังไคลเอนต์ PWA โหลดทันทีเนื่องจากพนักงานบริการกำจัดการพึ่งพาบนเครือข่ายโดยตอบสนองด้วยข้อมูลจากแคช
พนักงานบริการสามารถสกัดกั้นคำขอเครือข่ายที่อยู่ในขอบเขตเท่านั้น ตัวอย่างเช่น พนักงานบริการที่มีขอบเขตรูทสามารถสกัดกั้นคำขอดึงข้อมูลทั้งหมดที่มาจากหน้าเว็บได้ พนักงานบริการทำงานเป็นระบบที่ขับเคลื่อนด้วยเหตุการณ์ มันจะเข้าสู่สภาวะสงบนิ่งเมื่อไม่ต้องการ จึงเป็นการประหยัดหน่วยความจำ ในการใช้พนักงานบริการในเว็บแอปพลิเคชัน อันดับแรก เราต้องลงทะเบียนบนหน้าด้วย JavaScript
(function main () { /* navigator is a WEB API that allows scripts to register themselves and carry out their activities. */ if ('serviceWorker' in navigator) { console.log('Service Worker is supported in your browser') /* register method takes in the path of service worker file and returns a promises, which returns the registration object */ navigator.serviceWorker.register('./service-worker.js').then (registration => { console.log('Service Worker is registered!') }) } else { console.log('Service Worker is not supported in your browser') } })()
ก่อนอื่นเราตรวจสอบว่าเบราว์เซอร์รองรับพนักงานบริการหรือไม่ ในการลงทะเบียนพนักงานบริการในเว็บแอปพลิเคชัน เราได้จัดเตรียม URL เป็นพารามิเตอร์สำหรับฟังก์ชัน register
ซึ่งมีอยู่ใน navigator.serviceWorker
( navigator
เป็นเว็บ API ที่อนุญาตให้สคริปต์ลงทะเบียนตัวเองและดำเนินกิจกรรมต่างๆ ได้) พนักงานบริการลงทะเบียนเพียงครั้งเดียว การลงทะเบียนไม่ได้เกิดขึ้นทุกครั้งที่โหลดหน้า เบราว์เซอร์ดาวน์โหลดไฟล์พนักงานบริการ ( ./service-worker.js
) เฉพาะในกรณีที่มีความแตกต่างไบต์ระหว่างพนักงานบริการที่เปิดใช้งานที่มีอยู่กับไฟล์ที่ใหม่กว่า หรือหาก URL มีการเปลี่ยนแปลง
เจ้าหน้าที่บริการด้านบนจะสกัดกั้นคำขอทั้งหมดที่มาจากรูท ( /
) เพื่อจำกัดขอบเขตของพนักงานบริการ เราจะส่งพารามิเตอร์ทางเลือกที่มีคีย์ใดคีย์หนึ่งเป็นขอบเขต
if ('serviceWorker' in navigator) { /* register method takes in an optional second parameter as an object. To restrict the scope of a service worker, the scope should be provided. scope: '/books' will intercept requests with '/books' in the url. */ navigator.serviceWorker.register('./service-worker.js', { scope: '/books' }).then(registration => { console.log('Service Worker for scope /books is registered', registration) }) }
พนักงานบริการด้านบนจะสกัดกั้นคำขอที่มี /books
ใน URL ตัวอย่างเช่น จะไม่สกัดกั้นคำขอด้วย /products
แต่สามารถสกัดกั้นคำขอด้วย /books/products
ได้เป็นอย่างดี
ดังที่กล่าวไว้ พนักงานบริการทำงานเป็นระบบที่ขับเคลื่อนด้วยเหตุการณ์ มันฟังเหตุการณ์ (ติดตั้ง เปิดใช้งาน ดึงข้อมูล พุช) และเรียกใช้ตัวจัดการเหตุการณ์ตามลำดับ เหตุการณ์เหล่านี้บางส่วนเป็นส่วนหนึ่งของวงจรชีวิตของพนักงานบริการ ซึ่งจะผ่านเหตุการณ์เหล่านี้ตามลำดับเพื่อเปิดใช้งาน
การติดตั้ง
เมื่อลงทะเบียนพนักงานบริการสำเร็จแล้ว กิจกรรมการติดตั้งจะเริ่มขึ้น นี่เป็นสถานที่ที่ดีในการทำงานเริ่มต้น เช่น การตั้งค่าแคชหรือการสร้างที่เก็บอ็อบเจ็กต์ใน IndexedDB (IndexedDB จะเข้าใจคุณมากขึ้นเมื่อเราลงรายละเอียดแล้ว สำหรับตอนนี้ เราสามารถพูดได้ว่าเป็นโครงสร้างคู่คีย์-ค่า)
self.addEventListener('install', (event) => { let CACHE_NAME = 'xyz-cache' let urlsToCache = [ '/', '/styles/main.css', '/scripts/bundle.js' ] event.waitUntil( /* open method available on caches, takes in the name of cache as the first parameter. It returns a promise that resolves to the instance of cache All the URLS above can be added to cache using the addAll method. */ caches.open(CACHE_NAME) .then (cache => cache.addAll(urlsToCache)) ) })
ที่นี่ เรากำลังแคชไฟล์บางไฟล์เพื่อให้การโหลดครั้งต่อไปเป็นแบบทันที self
อ้างถึงอินสแตนซ์ของผู้ปฏิบัติงานบริการ event.waitUntil
ทำให้พนักงานบริการรอจนกว่ารหัสทั้งหมดภายในจะเสร็จสิ้นการดำเนินการ
การเปิดใช้งาน
เมื่อติดตั้งพนักงานบริการแล้ว ยังไม่สามารถฟังคำขอดึงข้อมูลได้ แต่เหตุการณ์ activate
จะถูกไล่ออก หากไม่มีผู้ปฏิบัติงานบริการที่ใช้งานอยู่บนเว็บไซต์ในขอบเขตเดียวกัน พนักงานบริการที่ติดตั้งไว้จะเปิดใช้งานทันที อย่างไรก็ตาม หากเว็บไซต์มีพนักงานบริการที่ใช้งานอยู่อยู่แล้ว การเปิดใช้งานพนักงานบริการใหม่จะล่าช้าจนกว่าแท็บทั้งหมดที่ทำงานบนพนักงานบริการเก่าจะปิดลง เรื่องนี้สมเหตุสมผลเพราะพนักงานบริการเก่าอาจใช้อินสแตนซ์ของแคชที่ได้รับการแก้ไขในเวอร์ชันที่ใหม่กว่า ดังนั้น ขั้นตอนการเปิดใช้งานจึงเป็นที่ที่ดีในการกำจัดแคชเก่า
self.addEventListener('activate', (event) => { let cacheWhitelist = ['products-v2'] // products-v2 is the name of the new cache event.waitUntil( caches.keys().then (cacheNames => { return Promise.all( cacheNames.map( cacheName => { /* Deleting all the caches except the ones that are in cacheWhitelist array */ if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName) } }) ) }) ) })
ในโค้ดด้านบน เรากำลังลบแคชเก่า หากชื่อแคชไม่ตรงกับ cacheWhitelist
นั้นจะถูกลบออก หากต้องการข้ามขั้นตอนการรอและเปิดใช้งานพนักงานบริการทันที เราใช้ skip.waiting()
self.addEventListener('activate', (event) => { self.skipWaiting() // The usual stuff })
เมื่อเปิดใช้งานพนักงานบริการแล้ว ก็จะสามารถรับฟังคำขอดึงข้อมูลและเหตุการณ์พุชได้
ดึงตัวจัดการเหตุการณ์
เมื่อใดก็ตามที่หน้าเว็บส่งคำขอดึงข้อมูลสำหรับทรัพยากรผ่านเครือข่าย เหตุการณ์การดึงข้อมูลจากพนักงานบริการจะถูกเรียก อันดับแรก ตัวจัดการเหตุการณ์ดึงข้อมูลจะค้นหาทรัพยากรที่ร้องขอในแคชก่อน หากมีอยู่ในแคช ก็จะส่งคืนการตอบสนองด้วยทรัพยากรที่แคชไว้ มิฉะนั้น จะเริ่มต้นคำขอดึงข้อมูลไปยังเซิร์ฟเวอร์ และเมื่อเซิร์ฟเวอร์ส่งการตอบกลับด้วยทรัพยากรที่ร้องขอ เซิร์ฟเวอร์ดังกล่าวจะใส่ไปยังแคชสำหรับคำขอที่ตามมา
/* Fetch event handler for responding to GET requests with the cached assets */ self.addEventListener('fetch', (event) => { event.respondWith( caches.open('products-v2') .then (cache => { /* Checking if the request is already present in the cache. If it is present, sending it directly to the client */ return cache.match(event.request).then (response => { if (response) { console.log('Cache hit! Fetching response from cache', event.request.url) return response } /* If the request is not present in the cache, we fetch it from the server and then put it in cache for subsequent requests. */ fetch(event.request).then (response => { cache.put(event.request, response.clone()) return response }) }) }) ) })
event.respondWith
ช่วยให้พนักงานบริการส่งการตอบสนองที่กำหนดเองไปยังไคลเอนต์
ออฟไลน์ก่อนตอนนี้เป็นเรื่อง สำหรับคำขอที่ไม่สำคัญใดๆ เราต้องให้บริการการตอบกลับจากแคช แทนที่จะทำการขี่ไปยังเซิร์ฟเวอร์ หากไม่มีเนื้อหาใดอยู่ในแคช เราได้รับจากเซิร์ฟเวอร์แล้วแคชสำหรับคำขอที่ตามมา
เจ้าหน้าที่บริการทำงานบนเว็บไซต์ HTTPS เท่านั้น เนื่องจากมีอำนาจในการจัดการการตอบสนองต่อคำขอดึงข้อมูลใดๆ บุคคลที่มีเจตนาร้ายอาจขัดขวางการตอบสนองต่อคำขอบนเว็บไซต์ HTTP ดังนั้น การโฮสต์ PWA บน HTTPS จึงเป็นข้อบังคับ เจ้าหน้าที่บริการไม่ขัดจังหวะการทำงานปกติของ DOM พวกเขาไม่สามารถสื่อสารโดยตรงกับหน้าเว็บ ในการส่งข้อความใด ๆ ไปยังหน้าเว็บ จะใช้ข้อความโพสต์
การแจ้งเตือนทางเว็บ
สมมติว่าคุณกำลังยุ่งกับการเล่นเกมบนมือถือของคุณ และการแจ้งเตือนจะปรากฏขึ้นเพื่อแจ้งส่วนลด 30% สำหรับแบรนด์โปรดของคุณ โดยไม่ต้องกังวลใจอีกต่อไป คุณคลิกที่การแจ้งเตือนและเลือกซื้อสินค้าของคุณ รับข้อมูลอัปเดตสด เช่น การแข่งขันคริกเก็ตหรือฟุตบอล หรือรับอีเมลและการแจ้งเตือนที่สำคัญเนื่องจากการแจ้งเตือนเป็นเรื่องใหญ่เมื่อพูดถึงการมีส่วนร่วมกับผู้ใช้ด้วยผลิตภัณฑ์ คุณลักษณะนี้มีให้เฉพาะในแอปพลิเคชันดั้งเดิมจนกว่า PWA จะเข้ามา PWA ใช้ประโยชน์จากการแจ้งเตือนแบบพุชของเว็บเพื่อแข่งขันกับคุณลักษณะอันทรงพลังนี้ซึ่งแอปแบบเนทีฟมีให้ตั้งแต่แกะกล่อง ผู้ใช้จะยังคงได้รับการแจ้งเตือนทางเว็บ แม้ว่า PWA จะไม่เปิดอยู่ในแท็บเบราว์เซอร์ใดๆ และแม้ว่าเบราว์เซอร์จะไม่เปิดอยู่ก็ตาม
เว็บแอปพลิเคชันต้องขออนุญาตผู้ใช้ในการส่งการแจ้งเตือนแบบพุช
เมื่อผู้ใช้ยืนยันโดยคลิกปุ่ม "อนุญาต" เบราว์เซอร์จะสร้างโทเค็นการสมัครรับข้อมูลที่ไม่ซ้ำกัน โทเค็นนี้ไม่ซ้ำกันสำหรับอุปกรณ์นี้ รูปแบบของโทเค็นการสมัครรับข้อมูลที่สร้างโดย Chrome มีดังนี้:
{ "endpoint": "https://fcm.googleapis.com/fcm/send/c7Veb8VpyM0:APA91bGnMFx8GIxf__UVy6vJ-n9i728CUJSR1UHBPAKOCE_SrwgyP2N8jL4MBXf8NxIqW6NCCBg01u8c5fcY0kIZvxpDjSBA75sVz64OocQ-DisAWoW7PpTge3SwvQAx5zl_45aAXuvS", "expirationTime": null, "keys": { "p256dh": "BJsj63kz8RPZe8Lv1uu-6VSzT12RjxtWyWCzfa18RZ0-8sc5j80pmSF1YXAj0HnnrkyIimRgLo8ohhkzNA7lX4w", "auth": "TJXqKozSJxcWvtQasEUZpQ" } }
endpoint
ที่มีอยู่ในโทเค็นด้านบนจะไม่ซ้ำกันสำหรับการสมัครรับข้อมูลทุกครั้ง บนเว็บไซต์โดยเฉลี่ย ผู้ใช้หลายพันคนยอมรับที่จะรับข้อความ Push และสำหรับพวกเขาแต่ละคน endpoint
นี้จะไม่ซ้ำกัน ดังนั้น ด้วยความช่วยเหลือของ endpoint
นี้ แอปพลิเคชันจึงสามารถกำหนดเป้าหมายผู้ใช้เหล่านี้ได้ในอนาคตโดยส่งการแจ้งเตือนแบบพุช expirationTime
คือระยะเวลาที่การสมัครรับข้อมูลถูกต้องสำหรับอุปกรณ์เฉพาะ หาก expirationTime
20 วัน หมายความว่าการสมัครรับข้อมูลแบบพุชของผู้ใช้จะหมดอายุหลังจาก 20 วัน และผู้ใช้จะไม่สามารถรับการแจ้งเตือนแบบพุชในการสมัครรับข้อมูลแบบเก่าได้ ในกรณีนี้ เบราว์เซอร์จะสร้างโทเค็นการสมัครสมาชิกใหม่สำหรับอุปกรณ์นั้น คีย์การ p256dh
auth
สำหรับการเข้ารหัส
ในการส่งการแจ้งเตือนแบบพุชไปยังผู้ใช้หลายพันคนในอนาคต อันดับแรก เราต้องบันทึกโทเค็นการสมัครรับข้อมูลตามลำดับ เป็นหน้าที่ของเซิร์ฟเวอร์แอปพลิเคชัน (เซิร์ฟเวอร์ส่วนหลัง อาจเป็นสคริปต์ Node.js) ในการส่งการแจ้งเตือนแบบพุชไปยังผู้ใช้เหล่านี้ ซึ่งอาจฟังดูง่ายเหมือนการสร้างคำขอ POST
ไปยัง URL ปลายทางด้วยข้อมูลการแจ้งเตือนในเพย์โหลดคำขอ อย่างไรก็ตาม ควรสังเกตว่าหากผู้ใช้ไม่ได้ออนไลน์เมื่อมีการเรียกใช้การแจ้งเตือนแบบพุชสำหรับพวกเขาโดยเซิร์ฟเวอร์ พวกเขาควรยังคงได้รับการแจ้งเตือนนั้นเมื่อพวกเขากลับมาออนไลน์ เซิร์ฟเวอร์จะต้องดูแลสถานการณ์ดังกล่าวพร้อมกับส่งคำขอนับพันถึงผู้ใช้ เซิร์ฟเวอร์ที่คอยติดตามการเชื่อมต่อของผู้ใช้นั้นฟังดูซับซ้อน ดังนั้น สิ่งที่อยู่ตรงกลางจะรับผิดชอบในการกำหนดเส้นทางการแจ้งเตือนทางเว็บจากเซิร์ฟเวอร์ไปยังไคลเอนต์ สิ่งนี้เรียกว่าบริการพุชและทุกเบราว์เซอร์มีการใช้งานบริการพุชของตัวเอง เบราว์เซอร์ต้องแจ้งข้อมูลต่อไปนี้ไปยังบริการพุชเพื่อส่งการแจ้งเตือน:
- ช่วงเวลาแห่งชีวิต
นี่คือระยะเวลาที่ข้อความควรอยู่ในคิว เผื่อว่าข้อความนั้นจะไม่ถูกส่งไปยังผู้ใช้ เมื่อเวลานี้ผ่านไป ข้อความจะถูกลบออกจากคิว - ความเร่งด่วนของข้อความ
ทั้งนี้เพื่อให้บริการพุชรักษาแบตเตอรี่ของผู้ใช้โดยส่งข้อความที่มีลำดับความสำคัญสูงเท่านั้น
บริการพุชกำหนดเส้นทางข้อความไปยังไคลเอนต์ เนื่องจากไคลเอนต์จะต้องรับการพุชแม้ว่าเว็บแอปพลิเคชันที่เกี่ยวข้องจะไม่เปิดในเบราว์เซอร์ เหตุการณ์พุชจะต้องได้รับฟังจากบางสิ่งที่คอยตรวจสอบในเบื้องหลังอย่างต่อเนื่อง คุณเดาได้เลย นั่นเป็นงานของพนักงานบริการ พนักงานบริการรับฟังเหตุการณ์พุชและทำหน้าที่แสดงการแจ้งเตือนต่อผู้ใช้
ดังนั้น ตอนนี้เรารู้แล้วว่าเบราว์เซอร์ บริการพุช พนักงานบริการ และแอปพลิเคชันเซิร์ฟเวอร์ทำงานประสานกันเพื่อส่งการแจ้งเตือนแบบพุชไปยังผู้ใช้ มาดูรายละเอียดการใช้งานกัน
ไคลเอนต์พุชเว็บ
การขออนุญาตผู้ใช้เป็นสิ่งที่ทำเพียงครั้งเดียว หากผู้ใช้ได้รับอนุญาตให้รับการแจ้งเตือนแบบพุชแล้ว เราไม่ควรถามอีก ค่าการอนุญาตจะถูกบันทึกไว้ใน Notification.permission
/* Notification.permission can have one of these three values: default, granted or denied. */ if (Notification.permission === 'default') { /* The Notification.requestPermission() method shows a notification permission prompt to the user. It returns a promise that resolves to the value of permission*/ Notification.requestPermission().then (result => { if (result === 'denied') { console.log('Permission denied') return } if (result === 'granted') { console.log('Permission granted') /* This means the user has clicked the Allow button. We're to get the subscription token generated by the browser and store it in our database. The subscription token can be fetched using the getSubscription method available on pushManager of the serviceWorkerRegistration object. If subscription is not available, we subscribe using the subscribe method available on pushManager. The subscribe method takes in an object. */ serviceWorkerRegistration.pushManager.getSubscription() .then (subscription => { if (!subscription) { const applicationServerKey = ' ' serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, // All push notifications from server should be displayed to the user applicationServerKey // VAPID Public key }) } else { saveSubscriptionInDB(subscription, userId) // A method to save subscription token in the database } }) } }) }
/* Notification.permission can have one of these three values: default, granted or denied. */ if (Notification.permission === 'default') { /* The Notification.requestPermission() method shows a notification permission prompt to the user. It returns a promise that resolves to the value of permission*/ Notification.requestPermission().then (result => { if (result === 'denied') { console.log('Permission denied') return } if (result === 'granted') { console.log('Permission granted') /* This means the user has clicked the Allow button. We're to get the subscription token generated by the browser and store it in our database. The subscription token can be fetched using the getSubscription method available on pushManager of the serviceWorkerRegistration object. If subscription is not available, we subscribe using the subscribe method available on pushManager. The subscribe method takes in an object. */ serviceWorkerRegistration.pushManager.getSubscription() .then (subscription => { if (!subscription) { const applicationServerKey = ' ' serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, // All push notifications from server should be displayed to the user applicationServerKey // VAPID Public key }) } else { saveSubscriptionInDB(subscription, userId) // A method to save subscription token in the database } }) } }) }
ในวิธีการ subscribe
ด้านบน เรากำลังส่ง userVisibleOnly
และ applicationServerKey
เพื่อสร้างโทเค็นการสมัครสมาชิก คุณสมบัติ userVisibleOnly
ควรเป็นจริงเสมอ เนื่องจากบอกเบราว์เซอร์ว่าการแจ้งเตือนแบบพุชที่เซิร์ฟเวอร์ส่งจะแสดงต่อไคลเอ็นต์ เพื่อให้เข้าใจวัตถุประสงค์ของ applicationServerKey
ลองพิจารณาสถานการณ์สมมติ
หากมีคนได้รับโทเค็นการสมัครรับข้อมูลนับพันของคุณ พวกเขาสามารถส่งการแจ้งเตือนไปยังปลายทางที่มีอยู่ในการสมัครรับข้อมูลเหล่านี้ได้เป็นอย่างดี ไม่มีทางที่ปลายทางจะเชื่อมโยงกับข้อมูลเฉพาะตัวของคุณ เราใช้โปรโตคอล VAPID เพื่อมอบเอกลักษณ์เฉพาะให้กับโทเค็นการสมัครที่สร้างขึ้นบนเว็บแอปพลิเคชันของคุณ ด้วย VAPID แอปพลิเคชันเซิร์ฟเวอร์จะระบุตนเองไปยังบริการพุชโดยสมัครใจขณะส่งการแจ้งเตือนแบบพุช เราสร้างสองคีย์ดังนี้:
const webpush = require('web-push') const vapidKeys = webpush.generateVAPIDKeys()
web-push เป็นโมดูล npm vapidKeys
จะมีคีย์สาธารณะหนึ่งคีย์และคีย์ส่วนตัวหนึ่งคีย์ รหัสเซิร์ฟเวอร์แอปพลิเคชันที่ใช้ด้านบนเป็นรหัสสาธารณะ
เว็บพุชเซิร์ฟเวอร์
งานของเว็บพุชเซิร์ฟเวอร์ (เซิร์ฟเวอร์แอปพลิเคชัน) ตรงไปตรงมา มันส่งเพย์โหลดการแจ้งเตือนไปยังโทเค็นการสมัครสมาชิก
const options = { TTL: 24*60*60, //TTL is the time to live, the time that the notification will be queued in the push service vapidDetails: { subject: '[email protected]', publicKey: ' ', privateKey: ' ' } } const data = { title: 'Update', body: 'Notification sent by the server' } webpush.sendNotification(subscription, data, options)
const options = { TTL: 24*60*60, //TTL is the time to live, the time that the notification will be queued in the push service vapidDetails: { subject: '[email protected]', publicKey: ' ', privateKey: ' ' } } const data = { title: 'Update', body: 'Notification sent by the server' } webpush.sendNotification(subscription, data, options)
const options = { TTL: 24*60*60, //TTL is the time to live, the time that the notification will be queued in the push service vapidDetails: { subject: '[email protected]', publicKey: ' ', privateKey: ' ' } } const data = { title: 'Update', body: 'Notification sent by the server' } webpush.sendNotification(subscription, data, options)
มันใช้วิธี sendNotification
จากไลบรารีพุชของเว็บ
พนักงานบริการ
พนักงานบริการแสดงการแจ้งเตือนไปยังผู้ใช้ดังนี้:
self.addEventListener('push', (event) => { let options = { body: event.data.body, icon: 'images/example.png', } event.waitUntil( /* The showNotification method is available on the registration object of the service worker. The first parameter to showNotification method is the title of notification, and the second parameter is an object */ self.registration.showNotification(event.data.title, options) ) })
จนถึงขณะนี้ เราได้เห็นวิธีที่พนักงานบริการใช้แคชเพื่อจัดเก็บคำขอ และทำให้ PWA รวดเร็วและเชื่อถือได้ และเราได้เห็นแล้วว่าข้อความ Push บนเว็บทำให้ผู้ใช้มีส่วนร่วมได้อย่างไร
ในการจัดเก็บข้อมูลบนฝั่งไคลเอ็นต์สำหรับการสนับสนุนแบบออฟไลน์ เราจำเป็นต้องมีโครงสร้างข้อมูลขนาดยักษ์ มาดู Financial Times PWA กัน คุณต้องเป็นพยานถึงพลังของโครงสร้างข้อมูลนี้ด้วยตัวของคุณเอง โหลด URL ในเบราว์เซอร์ของคุณ แล้วปิดการเชื่อมต่ออินเทอร์เน็ตของคุณ โหลดหน้าซ้ำ ก๊าก! มันยังคงทำงานอยู่หรือไม่? มันคือ. (อย่างที่บอกครับว่าออฟไลน์เป็นสีดำใหม่) ข้อมูลไม่ได้มาจากสายไฟ มันถูกเสิร์ฟจากที่บ้าน ตรงไปที่แท็บ "แอปพลิเคชัน" ของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ Chrome ใต้ "Storage" คุณจะพบ "IndexedDB"
ตรวจสอบที่เก็บอ็อบเจ็กต์ "บทความ" และขยายรายการใด ๆ เพื่อดูความมหัศจรรย์สำหรับตัวคุณเอง Financial Times ได้จัดเก็บข้อมูลนี้ไว้สำหรับการสนับสนุนแบบออฟไลน์ โครงสร้างข้อมูลที่ช่วยให้เราสามารถจัดเก็บข้อมูลจำนวนมหาศาลนี้เรียกว่า IndexedDB IndexedDB เป็นฐานข้อมูลเชิงวัตถุที่ใช้ JavaScript สำหรับการจัดเก็บข้อมูลที่มีโครงสร้าง เราสามารถสร้างที่เก็บอ็อบเจ็กต์ต่างๆ ในฐานข้อมูลนี้เพื่อวัตถุประสงค์ต่างๆ ตัวอย่างเช่น ดังที่เราเห็นในภาพด้านบนว่า "ทรัพยากร", "รูปภาพบทความ" และ "บทความ" ถูกเรียกว่าเป็นที่เก็บวัตถุ แต่ละเร็กคอร์ดในที่เก็บอ็อบเจ็กต์จะถูกระบุโดยไม่ซ้ำกันด้วยคีย์ IndexedDB สามารถใช้จัดเก็บไฟล์และ Blobs ได้
มาทำความเข้าใจ IndexedDB โดยสร้างฐานข้อมูลสำหรับจัดเก็บหนังสือกัน
let openIdbRequest = window.indexedDB.open('booksdb', 1)
หากไม่มีฐานข้อมูล booksdb
อยู่ รหัสด้านบนจะสร้างฐานข้อมูล booksdb
พารามิเตอร์ที่สองของวิธีการเปิดคือเวอร์ชันของฐานข้อมูล การระบุเวอร์ชันจะดูแลการเปลี่ยนแปลงที่เกี่ยวข้องกับสคีมาที่อาจเกิดขึ้นในอนาคต ตัวอย่างเช่น ตอนนี้ booksdb
มีเพียงหนึ่งตาราง แต่เมื่อแอปพลิเคชันเติบโตขึ้น เราตั้งใจที่จะเพิ่มตารางอีกสองตารางเข้าไป เพื่อให้แน่ใจว่าฐานข้อมูลของเราซิงค์กับสคีมาที่อัปเดต เราจะระบุเวอร์ชันที่สูงกว่าเวอร์ชันก่อนหน้า
การเรียกเมธอด open
ไม่ได้เปิดฐานข้อมูลในทันที เป็นคำขอแบบอะซิงโครนัสที่ส่งคืนวัตถุ IDBOpenDBRequest
ออบเจ็กต์นี้มีคุณสมบัติสำเร็จและข้อผิดพลาด เราจะต้องเขียนตัวจัดการที่เหมาะสมสำหรับคุณสมบัติเหล่านี้เพื่อจัดการสถานะของการเชื่อมต่อของเรา
let dbInstance openIdbRequest.onsuccess = (event) => { dbInstance = event.target.result console.log('booksdb is opened successfully') } openIdbRequest.onerror = (event) => { console.log('There was an error in opening booksdb database') } openIdbRequest.onupgradeneeded = (event) => { let db = event.target.result let objectstore = db.createObjectStore('books', { keyPath: 'id' }) }
ในการจัดการการสร้างหรือแก้ไขที่เก็บอ็อบเจ็กต์ (ที่เก็บอ็อบเจ็กต์นั้นคล้ายคลึงกับตารางที่ใช้ SQL โดยมีโครงสร้างคีย์-ค่า) เมธอด onupgradeneeded
จะถูกเรียกบนอ็อบเจ็กต์ openIdbRequest
วิธีการ onupgradeneeded
จะถูกเรียกใช้ทุกครั้งที่มีการเปลี่ยนแปลงเวอร์ชัน ในข้อมูลโค้ดด้านบน เรากำลังสร้างร้านหนังสือที่มีรหัสเฉพาะเป็นรหัส
สมมติว่าหลังจากปรับใช้โค้ดชิ้นนี้แล้ว เราต้องสร้างที่เก็บอ็อบเจ็กต์อีกหนึ่งแห่ง ซึ่งเรียกว่าเป็น users
ดังนั้นตอนนี้เวอร์ชันของฐานข้อมูลของเราจะเป็น 2
let openIdbRequest = window.indexedDB.open('booksdb', 2) // New Version - 2 /* Success and error event handlers remain the same. The onupgradeneeded method gets called when the version of the database changes. */ openIdbRequest.onupgradeneeded = (event) => { let db = event.target.result if (!db.objectStoreNames.contains('books')) { let objectstore = db.createObjectStore('books', { keyPath: 'id' }) } let oldVersion = event.oldVersion let newVersion = event.newVersion /* The users tables should be added for version 2. If the existing version is 1, it will be upgraded to 2, and the users object store will be created. */ if (oldVersion === 1) { db.createObjectStore('users', { keyPath: 'id' }) } }
เราได้แคช dbInstance
ในตัวจัดการเหตุการณ์ความสำเร็จของคำขอที่เปิดอยู่ ในการดึงหรือเพิ่มข้อมูลใน IndexedDB เราจะใช้ประโยชน์จาก dbInstance
มาเพิ่มบันทึกหนังสือในร้านหนังสือของเรากันเถอะ
let transaction = dbInstance.transaction('books') let objectstore = transaction.objectstore('books') let bookRecord = { id: '1', name: 'The Alchemist', author: 'Paulo Coelho' } let addBookRequest = objectstore.add(bookRecord) addBookRequest.onsuccess = (event) => { console.log('Book record added successfully') } addBookRequest.onerror = (event) => { console.log('There was an error in adding book record') }
เราใช้ transactions
โดยเฉพาะอย่างยิ่งในขณะที่เขียนบันทึกในที่เก็บอ็อบเจ็กต์ ธุรกรรมเป็นเพียงเครื่องห่อหุ้มการดำเนินการเพื่อให้แน่ใจว่าข้อมูลมีความสมบูรณ์ หากการดำเนินการใด ๆ ในธุรกรรมล้มเหลว จะไม่มีการดำเนินการใด ๆ ในฐานข้อมูล
มาแก้ไขบันทึกหนังสือด้วยวิธีการ put
:
let modifyBookRequest = objectstore.put(bookRecord) // put method takes in an object as the parameter modifyBookRequest.onsuccess = (event) => { console.log('Book record updated successfully') }
ลองดึงบันทึกหนังสือด้วยวิธี get
:
let transaction = dbInstance.transaction('books') let objectstore = transaction.objectstore('books') /* get method takes in the id of the record */ let getBookRequest = objectstore.get(1) getBookRequest.onsuccess = (event) => { /* event.target.result contains the matched record */ console.log('Book record', event.target.result) } getBookRequest.onerror = (event) => { console.log('Error while retrieving the book record.') }
การเพิ่มไอคอนบนหน้าจอหลัก
ขณะนี้แทบไม่มีความแตกต่างใดๆ ระหว่าง PWA และแอปพลิเคชันแบบเนทีฟ การเสนอตำแหน่งที่สำคัญให้กับการประปาส่วนภูมิภาค หากเว็บไซต์ของคุณมีคุณสมบัติตรงตามเกณฑ์พื้นฐานของ PWA (โฮสต์บน HTTPS ผสานรวมกับพนักงานบริการและมี manifest.json
) และหลังจากที่ผู้ใช้ใช้เวลาบนหน้าเว็บแล้ว เบราว์เซอร์จะเรียกใช้ข้อความแจ้งที่ด้านล่าง ให้ผู้ใช้เพิ่มแอปไปที่หน้าจอหลักดังที่แสดงด้านล่าง:
เมื่อผู้ใช้คลิกที่ "เพิ่ม FT ไปที่หน้าจอหลัก" การประปาส่วนภูมิภาคจะเข้าสู่หน้าจอหลักเช่นเดียวกับในลิ้นชักแอป เมื่อผู้ใช้ค้นหาแอปพลิเคชันใด ๆ บนโทรศัพท์ PWA ที่ตรงกับคำค้นหาจะปรากฏขึ้น นอกจากนี้ยังสามารถเห็นได้ในการตั้งค่าระบบ ซึ่งทำให้ผู้ใช้จัดการได้ง่าย ในแง่นี้ กปภ. มีลักษณะเหมือนแอปพลิเคชันดั้งเดิม
PWAs ใช้ประโยชน์จาก manifest.json
เพื่อจัดเตรียมคุณลักษณะนี้ มาดูไฟล์ manifest.json
ง่ายกัน
{ "name": "Demo PWA", "short_name": "Demo", "start_url": "/?standalone", "background_color": "#9F0C3F", "theme_color": "#fff1e0", "display": "standalone", "icons": [{ "src": "/lib/img/icons/xxhdpi.png?v2", "sizes": "192x192" }] }
short_name
ปรากฏบนหน้าจอหลักของผู้ใช้และในการตั้งค่าระบบ name
จะปรากฏในพรอมต์ของ Chrome และบนหน้าจอเริ่มต้น หน้าจอเริ่มต้นคือสิ่งที่ผู้ใช้เห็นเมื่อแอปพร้อมที่จะเปิดตัว start_url
คือหน้าจอหลักของแอปของคุณ นี่คือสิ่งที่ผู้ใช้ได้รับเมื่อแตะไอคอนบนหน้าจอหลัก background_color
ใช้บนหน้าจอเริ่มต้น theme_color
กำหนดสีของแถบเครื่องมือ ค่า standalone
สำหรับโหมดการ display
ระบุว่าแอปต้องทำงานในโหมดเต็มหน้าจอ (ซ่อนแถบเครื่องมือของเบราว์เซอร์) เมื่อผู้ใช้ติดตั้ง PWA ขนาดของมันเป็นเพียงกิโลไบต์ แทนที่จะเป็นเมกะไบต์ของแอปพลิเคชันดั้งเดิม
เจ้าหน้าที่บริการ การแจ้งเตือนแบบพุชของเว็บ IndexedDB และตำแหน่งหน้าจอหลักประกอบขึ้นสำหรับการสนับสนุนออฟไลน์ ความน่าเชื่อถือ และการมีส่วนร่วม ควรสังเกตว่าพนักงานบริการไม่มีชีวิตและเริ่มทำงานในภาระแรก การโหลดครั้งแรกจะยังคงช้าจนกว่าจะมีการแคชเนื้อหาแบบคงที่และทรัพยากรอื่นๆ ทั้งหมด เราสามารถใช้กลยุทธ์บางอย่างเพื่อเพิ่มประสิทธิภาพการโหลดครั้งแรก
การรวมกลุ่มสินทรัพย์
ทรัพยากรทั้งหมด รวมทั้ง HTML สไตล์ชีต รูปภาพ และ JavaScript จะต้องดึงมาจากเซิร์ฟเวอร์ ยิ่งไฟล์มีจำนวนคำขอ HTTPS มากขึ้นในการดึงข้อมูล เราสามารถใช้เครื่องบันเดิลอย่าง WebPack เพื่อรวมกลุ่มทรัพย์สินแบบคงที่ของเรา ดังนั้นจึงลดจำนวนคำขอ HTTP ที่ส่งไปยังเซิร์ฟเวอร์ WebPack ทำงานได้ดีมากในการเพิ่มประสิทธิภาพบันเดิลโดยใช้เทคนิคต่างๆ เช่น การแยกโค้ด (เช่น การรวมกลุ่มเฉพาะไฟล์ที่จำเป็นสำหรับการโหลดหน้าปัจจุบัน แทนที่จะรวมเข้าด้วยกันทั้งหมด) และการสั่นของโครงสร้าง (เช่น ลบการขึ้นต่อกันที่ซ้ำกันหรือ การอ้างอิงที่นำเข้าแต่ไม่ได้ใช้ในโค้ด)
ลดการเดินทางไป-กลับ
สาเหตุหลักประการหนึ่งที่ทำให้เว็บทำงานช้าคือเวลาแฝงของเครือข่าย เวลาที่ใช้สำหรับไบต์เดินทางจาก A ถึง B จะแตกต่างกันไปตามการเชื่อมต่อเครือข่าย ตัวอย่างเช่น การเดินทางไปกลับโดยเฉพาะผ่าน Wi-Fi ใช้เวลา 50 มิลลิวินาทีและ 500 มิลลิวินาทีในการเชื่อมต่อ 3G แต่ใช้เวลา 2,500 มิลลิวินาทีในการเชื่อมต่อ 2G คำขอเหล่านี้ถูกส่งโดยใช้โปรโตคอล HTTP ซึ่งหมายความว่าในขณะที่ใช้การเชื่อมต่อเฉพาะสำหรับคำขอ จะไม่สามารถใช้สำหรับคำขออื่น ๆ ได้จนกว่าจะมีการตอบกลับคำขอก่อนหน้า เว็บไซต์สามารถสร้างคำขอ HTTP แบบอะซิงโครนัสได้หกรายการในแต่ละครั้ง เนื่องจากมีการเชื่อมต่อหกรายการในเว็บไซต์เพื่อส่งคำขอ HTTP เว็บไซต์โดยเฉลี่ยส่งคำขอประมาณ 100 รายการ; ดังนั้น ด้วยการเชื่อมต่อสูงสุดหกเครื่อง ผู้ใช้อาจใช้จ่ายเงินประมาณ 833 มิลลิวินาทีในการเดินทางไปกลับครั้งเดียว (การคำนวณคือ 833 มิลลิวินาที - 100 ⁄ 6 = 1666 เราต้องหาร 1666 ด้วย 2 เพราะเรากำลังคำนวณเวลาที่ใช้ในการเดินทางไปกลับ) ด้วย HTTP2 ในสถานที่ เวลาตอบสนองจะลดลงอย่างมาก HTTP2 ไม่ได้บล็อกหัวการเชื่อมต่อ จึงสามารถส่งคำขอหลายรายการพร้อมกันได้
การตอบสนอง HTTP ส่วนใหญ่มีส่วนหัว last-modified
และ etag
ส่วนหัว last-modified
คือวันที่ที่ไฟล์ถูกแก้ไขล่าสุด และ etag
เป็นค่าที่ไม่ซ้ำตามเนื้อหาของไฟล์ จะมีการเปลี่ยนแปลงก็ต่อเมื่อเนื้อหาของไฟล์มีการเปลี่ยนแปลง สามารถใช้ส่วนหัวทั้งสองนี้เพื่อหลีกเลี่ยงการดาวน์โหลดไฟล์อีกครั้ง หากเวอร์ชันที่แคชมีอยู่แล้วในเครื่อง หากเบราว์เซอร์มีเวอร์ชันของไฟล์นี้ในเครื่อง จะเพิ่มส่วนหัวใดก็ได้ในสองคำขอดังนี้:
เซิร์ฟเวอร์สามารถตรวจสอบได้ว่าเนื้อหาของไฟล์มีการเปลี่ยนแปลงหรือไม่ If the contents of the file have not changed, then it responds with a status code of 304 ( not modified ).
This indicates to the browser to use the locally available cached version of the file. By doing all of this, we've prevented the file from being downloaded.
Faster responses are in now place, but our job is not done yet. We still have to parse the HTML, load the style sheets and make the web page interactive. It makes sense to show some empty boxes with a loader to the user, instead of a blank screen. While the HTML document is getting parsed, when it comes across <script src='asset.js'></script>
, it will make a synchronous HTTP request to the server to fetch asset.js
, and the whole parsing process will be paused until the response comes back. Imagine having a dozen of synchronous static asset references. These could very well be managed just by making use of the async
keyword in script references, like <script src='asset.js' async></script>
. With the introduction of the async
keyword here, the browser will make an asynchronous request to fetch asset.js
without hindering the parsing of the HTML. If a script file is required at a later stage, we can defer the downloading of that file until the entire HTML has been parsed. A script file can be deferred by using the defer
keyword, like <script src='asset.js' defer></script>
.
บทสรุป
We've learned a lot of many new things that make for a cool web application. Here's a summary of all of the things we've explored in this article:
- Service workers make good use of the cache to speed up the loading of assets.
- Web push notifications work under the hood.
- We use IndexedDB to store a massive amount of data.
- Some of the optimizations for instant first load, like using HTTP2 and adding headers like
Etag
,last-modified
andIf-None-Match
, prevent the downloading of valid cached assets.
That's all, folks!