การพัฒนา Progressive Web App 02.0: เริ่มต้นใช้งานแบบออฟไลน์อย่างรวดเร็ว

Codelab นี้เป็นส่วนหนึ่งของหลักสูตรการฝึกอบรมการพัฒนา Progressive Web App ซึ่งพัฒนาโดยทีมฝึกอบรมของ Google Developers คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ

ดูรายละเอียดทั้งหมดเกี่ยวกับหลักสูตรได้ที่ภาพรวมการพัฒนา Progressive Web App

บทนำ

ในแล็บนี้ คุณจะได้ใช้ Lighthouse เพื่อตรวจสอบเว็บไซต์ตามมาตรฐาน Progressive Web App (PWA) นอกจากนี้ คุณยังเพิ่มฟังก์ชันการทำงานแบบออฟไลน์ด้วย Service Worker API ได้ด้วย

สิ่งที่คุณจะได้เรียนรู้

  • วิธีตรวจสอบเว็บไซต์ด้วย Lighthouse
  • วิธีเพิ่มความสามารถแบบออฟไลน์ให้กับแอปพลิเคชัน

สิ่งที่ควรทราบ

  • HTML, CSS และ JavaScript พื้นฐาน
  • ความคุ้นเคยกับ Promise ของ ES2015

สิ่งที่คุณจะต้องมี

  • คอมพิวเตอร์ที่มีสิทธิ์เข้าถึงเทอร์มินัล/เชลล์
  • การเชื่อมต่ออินเทอร์เน็ต
  • เบราว์เซอร์ Chrome (สำหรับใช้ Lighthouse)
  • โปรแกรมแก้ไขข้อความ
  • ไม่บังคับ: Chrome ในอุปกรณ์ Android

ดาวน์โหลดหรือโคลนที่เก็บ pwa-training-labs จาก GitHub และติดตั้ง Node.js เวอร์ชัน LTS หากจำเป็น

ไปที่ไดเรกทอรี offline-quickstart-lab/app/ แล้วเริ่มเซิร์ฟเวอร์การพัฒนาซอฟต์แวร์ภายใน

cd offline-quickstart-lab/app
npm install
node server.js

คุณสิ้นสุดการทำงานของเซิร์ฟเวอร์ได้ทุกเมื่อด้วย Ctrl-c

เปิดเบราว์เซอร์ แล้วไปที่ localhost:8081/ คุณควรเห็นว่าเว็บไซต์เป็นหน้าเว็บแบบคงที่ที่เรียบง่าย

หมายเหตุ: ยกเลิกการลงทะเบียน Service Worker และล้างแคชทั้งหมดของ Service Worker สำหรับ localhost เพื่อไม่ให้รบกวน Lab ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome คุณทำได้โดยคลิกล้างข้อมูลเว็บไซต์จากส่วนล้างพื้นที่เก็บข้อมูลของแท็บแอปพลิเคชัน

เปิดโฟลเดอร์ offline-quickstart-lab/app/ ในโปรแกรมแก้ไขข้อความที่ต้องการ app/ โฟลเดอร์คือที่ที่คุณจะสร้าง Lab

โฟลเดอร์นี้ประกอบด้วย

  • โฟลเดอร์ images/ มีรูปภาพตัวอย่าง
  • styles/main.css คือสไตล์ชีตหลัก
  • index.html คือหน้า HTML หลักสำหรับเว็บไซต์ตัวอย่างของเรา
  • package-lock.json และ package.json ติดตามการขึ้นต่อกันของแอป (ในกรณีนี้ การขึ้นต่อกันมีเฉพาะสำหรับเซิร์ฟเวอร์การพัฒนาในเครื่อง)
  • server.js เป็นเซิร์ฟเวอร์การพัฒนาซอฟต์แวร์ภายในสำหรับการทดสอบ
  • service-worker.js คือไฟล์ Service Worker (ปัจจุบันว่างเปล่า)

ก่อนเริ่มทำการเปลี่ยนแปลงเว็บไซต์ เรามาตรวจสอบด้วย Lighthouse เพื่อดูว่ามีอะไรที่ปรับปรุงได้บ้าง

กลับไปที่แอป (ใน Chrome) แล้วเปิดแท็บการตรวจสอบของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ คุณควรเห็นไอคอน Lighthouse และตัวเลือกการกำหนดค่า เลือก "อุปกรณ์เคลื่อนที่" สำหรับอุปกรณ์ เลือกการตรวจสอบทั้งหมด เลือกตัวเลือกการจำกัดอัตราอย่างใดอย่างหนึ่ง แล้วเลือกล้างพื้นที่เก็บข้อมูล

คลิกทำการตรวจสอบ การตรวจสอบจะใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์

คำอธิบาย

คุณควรเห็นรายงานที่มีคะแนนในเครื่องมือสำหรับนักพัฒนาเว็บเมื่อการตรวจสอบเสร็จสมบูรณ์ โดยควรแสดงคะแนนคล้ายกับตัวอย่างนี้ (คะแนนอาจไม่ตรงกันทุกประการ)

หมายเหตุ: คะแนน Lighthouse เป็นค่าประมาณและอาจได้รับผลกระทบจากสภาพแวดล้อมของคุณ (เช่น หากคุณเปิดหน้าต่างเบราว์เซอร์จำนวนมาก) คะแนนของคุณอาจไม่เหมือนกับที่แสดงที่นี่

และส่วน Progressive Web App ควรมีลักษณะคล้ายกับส่วนนี้

รายงานมีคะแนนและเมตริกใน 5 หมวดหมู่ ได้แก่

  • Progressive Web App
  • ประสิทธิภาพ
  • การช่วยเหลือพิเศษ
  • แนวทางปฏิบัติแนะนำ
  • SEO

ดังที่คุณเห็น แอปของเราได้คะแนนไม่ดีในหมวดหมู่ Progressive Web App (PWA) มาปรับปรุงคะแนนกัน

โปรดสละเวลาสักครู่เพื่อดูส่วน PWA ของรายงานและดูว่ามีอะไรขาดหายไป

ลงทะเบียน Service Worker

ข้อผิดพลาดอย่างหนึ่งที่ระบุไว้ในรายงานคือไม่มีการลงทะเบียน Service Worker ขณะนี้เรามีไฟล์ Service Worker ที่ว่างเปล่าที่ app/service-worker.js

เพิ่มสคริปต์ต่อไปนี้ที่ด้านล่างของ index.html ก่อนแท็กปิด </body>

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js')
      .then(reg => {
        console.log('Service worker registered! 😎', reg);
      })
      .catch(err => {
        console.log('😥 Service worker registration failed: ', err);
      });
  });
}
</script>

คำอธิบาย

โค้ดนี้จะลงทะเบียนไฟล์ Service Worker service-worker.js ที่ว่างเปล่าเมื่อหน้าเว็บโหลดแล้ว อย่างไรก็ตาม ไฟล์ Service Worker ปัจจุบันว่างเปล่าและจะไม่ดำเนินการใดๆ มาเพิ่มรหัสบริการในขั้นตอนถัดไปกัน

แคชทรัพยากรล่วงหน้า

ความล้มเหลวอีกอย่างที่ระบุไว้ในรายงานคือแอปไม่ตอบสนองด้วยรหัสสถานะ 200 เมื่อออฟไลน์ เราต้องอัปเดต Service Worker เพื่อแก้ปัญหานี้

เพิ่มโค้ดต่อไปนี้ลงในไฟล์ Service Worker (service-worker.js)

const cacheName = 'cache-v1';
const precacheResources = [
  '/',
  'index.html',
  'styles/main.css',
  'images/space1.jpg',
  'images/space2.jpg',
  'images/space3.jpg'
];

self.addEventListener('install', event => {
  console.log('Service worker install event!');
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => {
        return cache.addAll(precacheResources);
      })
  );
});

self.addEventListener('activate', event => {
  console.log('Service worker activate event!');
});

self.addEventListener('fetch', event => {
  console.log('Fetch intercepted for:', event.request.url);
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});

ตอนนี้ให้กลับไปที่เบราว์เซอร์แล้วรีเฟรชเว็บไซต์ ตรวจสอบคอนโซลเพื่อให้แน่ใจว่า Service Worker มีลักษณะดังนี้

  • ลงทะเบียนแล้ว
  • ติดตั้งแล้ว
  • เปิดอยู่

หมายเหตุ: หากคุณลงทะเบียน Service Worker ไว้ก่อนหน้านี้แล้ว หรือพบปัญหาในการทำให้เหตุการณ์ทั้งหมดทริกเกอร์ ให้ยกเลิกการลงทะเบียน Service Worker แล้วรีเฟรชหน้าเว็บ หากไม่ได้ผล ให้ปิดแอปทั้งหมดแล้วเปิดอีกครั้ง

จากนั้น ให้สิ้นสุดเซิร์ฟเวอร์การพัฒนาซอฟต์แวร์ภายในในบรรทัดคำสั่งโดยเรียกใช้ Ctrl + c รีเฟรชเว็บไซต์อีกครั้ง แล้วสังเกตว่าเว็บไซต์โหลดได้แม้ว่าเซิร์ฟเวอร์จะออฟไลน์อยู่ก็ตาม

หมายเหตุ: คุณอาจเห็นข้อผิดพลาดในคอนโซลที่ระบุว่าดึงข้อมูล Service Worker ไม่ได้: An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED ข้อผิดพลาดนี้แสดงขึ้นเนื่องจากเบราว์เซอร์ดึงข้อมูลสคริปต์ Service Worker ไม่ได้ (เนื่องจากเว็บไซต์ออฟไลน์) แต่เป็นเรื่องปกติเนื่องจากเราไม่สามารถใช้ Service Worker เพื่อแคชตัวเองได้ มิเช่นนั้นเบราว์เซอร์ของผู้ใช้จะติดอยู่กับ Service Worker เดียวตลอดไป

คำอธิบาย

เมื่อสคริปต์การลงทะเบียนลงทะเบียน Service Worker ใน index.html แล้ว เหตุการณ์ install ของ Service Worker จะเกิดขึ้น ในเหตุการณ์นี้ install Event Listener จะเปิดแคชที่มีชื่อและแคชไฟล์ที่ระบุด้วยเมธอด cache.addAll เราเรียกกระบวนการนี้ว่า "การแคชล่วงหน้า" เนื่องจากเกิดขึ้นระหว่างเหตุการณ์ install ซึ่งโดยปกติแล้วคือครั้งแรกที่ผู้ใช้เข้าชมเว็บไซต์

หลังจากติดตั้ง Service Worker แล้ว และหากขณะนี้ไม่มี Service Worker อื่นควบคุมหน้าเว็บอยู่ ระบบจะ "เปิดใช้งาน" Service Worker ใหม่ (ทริกเกอร์เครื่องมือฟังเหตุการณ์ activate ใน Service Worker) และ Service Worker จะเริ่มควบคุมหน้าเว็บ

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

การแคชทรัพยากรช่วยให้แอปทำงานแบบออฟไลน์ได้โดยไม่ต้องส่งคำขอของเครือข่าย ตอนนี้แอปของเราตอบสนองด้วยรหัสสถานะ 200 เมื่อออฟไลน์ได้แล้ว

หมายเหตุ: ระบบจะไม่ใช้เหตุการณ์เปิดใช้งานเพื่อวัตถุประสงค์อื่นนอกเหนือจากการเข้าสู่ระบบในตัวอย่างนี้ เราได้รวมเหตุการณ์นี้ไว้เพื่อช่วยแก้ไขข้อบกพร่องเกี่ยวกับปัญหาในวงจรของ Service Worker

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

รีสตาร์ทเซิร์ฟเวอร์การพัฒนาด้วย node server.js แล้วรีเฟรชเว็บไซต์ จากนั้นเปิดแท็บการตรวจสอบในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์อีกครั้ง แล้วเรียกใช้การตรวจสอบ Lighthouse อีกครั้งโดยเลือกการตรวจสอบใหม่ (เครื่องหมายบวกที่มุมซ้ายบน) เมื่อการตรวจสอบเสร็จสิ้น คุณจะเห็นว่าคะแนน PWA ของเราดีขึ้นอย่างมาก แต่ก็ยังปรับปรุงได้ เราจะปรับปรุงคะแนนต่อไปในส่วนถัดไป

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

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

สร้างไฟล์ Manifest

สร้างไฟล์ใน app/ ชื่อ manifest.json แล้วเพิ่มโค้ดต่อไปนี้

{
  "name": "Space Missions",
  "short_name": "Space Missions",
  "lang": "en-US",
  "start_url": "/index.html",
  "display": "standalone",
  "theme_color": "#FF9800",
  "background_color": "#FF9800",
  "icons": [
    {
      "src": "images/touch/icon-128x128.png",
      "sizes": "128x128"
    },
    {
      "src": "images/touch/icon-192x192.png",
      "sizes": "192x192"
    },
    {
      "src": "images/touch/icon-256x256.png",
      "sizes": "256x256"
    },
    {
      "src": "images/touch/icon-384x384.png",
      "sizes": "384x384"
    },
    {
      "src": "images/touch/icon-512x512.png",
      "sizes": "512x512"
    }
  ]
}

รูปภาพที่อ้างอิงในไฟล์ Manifest มีอยู่ในแอปอยู่แล้ว

จากนั้นเพิ่ม HTML ต่อไปนี้ที่ด้านล่างของแท็ก <head> ใน index.html

<link rel="manifest" href="manifest.json">

<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">

กลับไปที่เว็บไซต์ ในแท็บแอปพลิเคชันของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ ให้เลือกส่วนล้างพื้นที่เก็บข้อมูล แล้วคลิกล้างข้อมูลเว็บไซต์ จากนั้นรีเฟรชหน้าเว็บ ตอนนี้ให้เลือกส่วน Manifest คุณควรเห็นไอคอนและตัวเลือกการกำหนดค่าที่กำหนดค่าไว้ในไฟล์ manifest.json หากไม่เห็นการเปลี่ยนแปลง ให้เปิดเว็บไซต์ในหน้าต่างไม่ระบุตัวตนแล้วตรวจสอบอีกครั้ง

คำอธิบาย

manifest.json จะบอกเบราว์เซอร์วิธีจัดรูปแบบและจัดสไตล์ลักษณะบางอย่างของแอปที่ทำงานแบบ Progressive เช่น Chrome ของเบราว์เซอร์ ไอคอนหน้าจอหลัก และหน้าจอเริ่มต้น นอกจากนี้ยังใช้เพื่อกำหนดค่าเว็บแอปให้เปิดในโหมด standalone ได้เช่นเดียวกับแอปเนทีฟ (กล่าวคืออยู่นอกเบราว์เซอร์)

ขณะที่เขียนบทความนี้ เบราว์เซอร์บางตัวยังอยู่ระหว่างการพัฒนาการรองรับ และแท็ก <meta> จะกำหนดค่าฟีเจอร์ย่อยของฟีเจอร์เหล่านี้สำหรับเบราว์เซอร์บางตัวที่ยังไม่รองรับอย่างเต็มรูปแบบ

เราต้องล้างข้อมูลเว็บไซต์เพื่อนำ index.html เวอร์ชันเก่าที่แคชไว้ของเราออก (เนื่องจากเวอร์ชันนั้นไม่มีลิงก์ไฟล์ Manifest) ลองเรียกใช้การตรวจสอบ Lighthouse อีกครั้ง แล้วดูว่าคะแนน PWA ดีขึ้นมากน้อยเพียงใด

การเปิดใช้งานข้อความแจ้งให้ติดตั้ง

ขั้นตอนถัดไปในการติดตั้งแอปของเราคือการแสดงข้อความแจ้งให้ติดตั้งต่อผู้ใช้ Chrome 67 จะแจ้งให้ผู้ใช้ทราบโดยอัตโนมัติ แต่ตั้งแต่ Chrome 68 เป็นต้นไป ระบบจะเปิดใช้งานข้อความแจ้งให้ติดตั้งโดยอัตโนมัติเพื่อตอบสนองต่อท่าทางของผู้ใช้

เพิ่มปุ่มและแบนเนอร์ "ติดตั้งแอป" ที่ด้านบนของ index.html (หลังแท็ก <main>) ด้วยโค้ดต่อไปนี้

<section id="installBanner" class="banner">
    <button id="installBtn">Install app</button>
</section>

จากนั้นจัดรูปแบบแบนเนอร์โดยเพิ่มรูปแบบต่อไปนี้ลงใน styles/main.css

.banner {
  align-content: center;
  display: none;
  justify-content: center;
  width: 100%;
}

บันทึกไฟล์ สุดท้าย ให้เพิ่มแท็กสคริปต์ต่อไปนี้ลงใน index.html

  <script>
    let deferredPrompt;
    window.addEventListener('beforeinstallprompt', event => {

      // Prevent Chrome 67 and earlier from automatically showing the prompt
      event.preventDefault();

      // Stash the event so it can be triggered later.
      deferredPrompt = event;

      // Attach the install prompt to a user gesture
      document.querySelector('#installBtn').addEventListener('click', event => {

        // Show the prompt
        deferredPrompt.prompt();

        // Wait for the user to respond to the prompt
        deferredPrompt.userChoice
          .then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
              console.log('User accepted the A2HS prompt');
            } else {
              console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
          });
      });

      // Update UI notify the user they can add to home screen
      document.querySelector('#installBanner').style.display = 'flex';
    });
  </script>

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

คำอธิบาย

โค้ด HTML และ CSS จะเพิ่มแบนเนอร์และปุ่มที่ซ่อนไว้ซึ่งเราใช้เพื่ออนุญาตให้ผู้ใช้เปิดใช้งานข้อความแจ้งให้ติดตั้ง

เมื่อbeforeinstallprompt event ทำงานแล้ว เราจะป้องกันประสบการณ์เริ่มต้น (ซึ่ง Chrome 67 และเวอร์ชันก่อนหน้าจะแจ้งให้ผู้ใช้ติดตั้งโดยอัตโนมัติ) และบันทึก beforeinstallevent ในตัวแปร deferredPrompt ทั่วโลก จากนั้นระบบจะกำหนดค่าปุ่ม "ติดตั้งแอป" ให้แสดงข้อความแจ้งด้วยวิธีการของ beforeinstallevent's prompt() เมื่อผู้ใช้เลือกแล้ว (ว่าจะติดตั้งหรือไม่) userChoicePromise จะได้รับการแก้ไขตามตัวเลือกของผู้ใช้ (outcome) สุดท้าย เราจะแสดงปุ่มติดตั้งเมื่อทุกอย่างพร้อม

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

แหล่งข้อมูลเพิ่มเติม

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

หากต้องการดู Codelab ทั้งหมดในหลักสูตรการฝึกอบรม PWA โปรดดู Codelab ต้อนรับสำหรับหลักสูตร