Progressive Web Apps (PWA) คืออะไร

Progressive Web Apps หรือ PWA คือเทคโนโลยีที่จะทำให้เว็บของเราใกล้เคียงแอปบนมือถือมากขึ้น โดยมี Google เป็นผู้ผลักดัน (ทำให้ฝั่ง Apple ไม่ค่อยอยากจะเอาด้วย แต่ปัจจุบันก็รองรับบางส่วนแล้ว)

ความสามารถของ Progressive Web Apps
  1. สามารถทำงานได้แบบ Offline แอปต่างจากเว็บก็อีตรงที่มันสามารถทำงานได้แม้จะไม่มีอินเตอร์เน็ตนี่แหละ PWA ก็เลยถูกออกแบบมาเพื่อให้เราสามารถเรียกใช้งานเว็บได้แม้จะไม่มีอินเตอร์เน็ตในขณะนั้น แทนที่จะขึ้นหน้า 404
  2. สามารถอัปเดทได้ทันทีเมื่อมือถือกลับมา Online และเมื่อไรที่มือถือกลับมาออนไลน์อีกครั้งเว็บก็สามารถอัปเดทได้ทันทีถึงแม้จะปิดโปรแกรมหรือเว็บไปแล้ว (ทำงานในเบื้องหลัง)
  3. สามารถวางไอคอนของเว็บลงบนหน้าจอมือถือ และ สามารถเรียกใช้งานเว็บแบบเดียวกับที่เรียกใช้งานแอปได้ เหมือนการติดตั้งแอป โดยที่ไม่ต้องมีการติดตั้งอะไรเลย รวมถึงไม่จำเป็นต้องติดตั้งและอัปเดทผ่าน Play Store ด้วย
  4. สามารถเปิดเว็บแบบ Full screen โดยไม่มี Address Bar ได้เมื่อ User กดเข้ามาจากหน้า Homescreen อารมณ์คล้ายๆการเปิด App เลย (ไม่รองรับบน Desktop)
  5. สามารถใช้งาน Push Notification หรือข้อความแจ้งเตือนคล้ายๆที่ไลน์เด้งขึ้นมาเมื่อมีข้อความใหม่นั่นแหละ โดยที่ไม่จำเป็นต้องเปิดเว็บทิ้งไว้
  6. สามารถออกแบบหน้าตาได้ง่ายๆเหมือนเว็บเป๊ะๆ (Responsive) จริงๆแล้วมันคือการเพิ่มโค้ดเล็กๆน้อยๆลงในเว็บ (ที่รองรับ Responsive) ของเราเพื่อเปิดใช้งานคุณสมบัตินี้มากกว่า
เบื้องต้นที่อยากให้รู้ก็มีเท่านี้แหละครับ ถ้าความสามารถแบบเต็มๆแนะนำให้อ่านที่ เว็บหลัก Progressive Web Apps เลย

Progressive Web Apps เป็นเทคโนโลยีของฝั่งบราวเซอร์ ซึ่งปัจจุบันบราวเซอร์หลักๆรองรับความสามารถทั้งหมดที่กล่าวมาแล้ว (Apple อาจไม่สนับสนุนทั้งหมด แต่ความสามารถหลักๆก็ครบถ้วน)

การทำงานของ Progressive Web Apps
เมื่อผู้ใช้เรียกหน้าเว็บในครั้งแรก บราวเซอร์จะทำการโหลดหน้าเว็บจาก Server ตามปกติ เมื่อได้ข้อมูลมาครบถ้วนแล้ว บราวเซอร์จะทำการแคชหน้าเว็บของเราเก็บไว้ในแคชของบราวเซอร์ (เป็นคนละตัวกันกับแคชปกติของบราวเซอร์นะครับ) ครั้งต่อไปเมื่อมีการเรียกหน้าเว็บเดิม บราวเซอร์จะไปทำการตรวจสอบจากแคชก่อน หากมีการเก็บข้อมูลหน้าเว็บไว้ บราวเซอร์ก็จะเรียกข้อมูลจากที่แคชไว้มาแสดงผลในทันที ซึ่งการทำงานตรงส่วนนี้จะเร็วมากเพราะข้อมูลอยู่บนเครื่องอยู่แล้ว ซึ่งหากไม่มีการแคชเก็บไว้ บราวเซอร์ถึงจะไปทำการติดต่อกับอินเตอร์เน็ตเพื่อขอข้อมูลจาก Server ซึ่งในกรณ๊ทีมีอินเตอร์เน็ตกระบวนการในขั้นตอนนี้ก็จะทำไปตามปกติ แต่หากไม่มีอินเตอร์เน็ต ก็จะแสดงหน้า 404 แทน

จากขั้นตอนการทำงานข้างต้นจะเห็นว่าบราวเซอร์จะทำงานได้รวดเร็วมากหากมีการแคชข้อมูลหน้าเว็บไว้ก่อนแล้ว เพราะไม่จำเป็นต้องขอข้อมูลมาจาก Server ซึ่งใช้เวลามากกว่า และทำนองเดียวกันหากหน้าที่ต้องการเรียกไม่ได้แคชเก็บไว้ โอกาสที่หน้าเว็บจะแสดง 404 ก็ยังมีอยู่หากไม่สามารถเชื่อมต่ออินเตอร์เน็ตได้ และ ผู้ใช้ยังไม่เคยเรียกใช้หน้าเว็บมาก่อน (เมื่อไม่เคยเรียกใช้การแคชย่อมไม่เกิดขึ้น) ซึ่งการแก้ปัญหานี้อาจทำได้โดยการ แคชทุกหน้าเว็บเก็บไว้ก่อน เมื่อมีการเรียกใช้เว็บในครั้งแรก ซึ่งแน่นอนครับว่าหากเว็บที่มีหน้าเว็บจำนวนน้อย เช่นเว็บ บริษัท หรือ Portfolio การแคชก็คงไม่มีปัญหาสักเท่าไร เพราะข้อมูลที่ต้องแคชอาจมีไม่มาก แต่หากเป็นเว็บประเภทบทความหรือเว็บร้านค้า วิธีแคชเก็บไว้ทั้งหมดอาจไม่เหมาะสมนัก

ในการแคชเราจะต้องทำการแคชเก็บไว้ทั้งหมดนะครับ ไม่ว่าจะเป็นไฟล์ Javascript CSS รูปภาพ VDO ฟ้อนต์ หรือไฟล์อื่นใดที่ต้องใช้ในการแสดงหน้าเว็บ เพราะการแคชหน้าเว็บเป็นเพียงการแคชโค้ด HTML หน้าเว็บเท่านั้น เมื่อมีการเรียกใช้แคชเกิดขึ้นไฟล์ทุกไฟล์ที่หน้าเว็บต้องใช้จะต้องสามารถเรียกใช้แบบ Offline จากแคชได้ด้วยเช่นกัน (บราวเซอร์ยังสามารถเรียกใช้แคชปกติของบราวเซอร์ได้อยู่นะครับ ในกรณีที่บราวเซอร์มีการแคชเก็บไว้)

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

ความสามารถอีกอย่างที่อยากจะพูดถึงก็คือ ความสามารถในการทำงานเมื่อเว็บกลับมาออนไลน์ได้อีกครั้ง ยกตัวอย่างเช่น เมื่อมีการทำธุรกรรมใดๆบนหน้าเว็บแบบออฟไลน์ไปแล้ว เช่นการสั่งซื้อของ ข้อมูลการทำธุรกรรมทั้งหมด ไม่ได้ถูกส่งไปยัง Server จริงๆ (เพราะมันยังต่ออินเตอร์เน็ตไม่ได้) แต่เป็นการกระทำบน Browser ซึ่งข้อมูลการทำธุรกรรมทั้งหมดจะถูกจัดเก็บบนบราวเซอร์แทน ซึ่งอาจจะเป็น IndexedDB ของบราวเซอร์เองก็ได้ และเมื่อมือถือกลับมาออนไลน์อีกครั้ง บราวเซอร์จะทำการส่งข้อมูลที่ทำรายการไว้ไปยัง Server อีกทีในเบื้องหลัง โดยที่ไม่จำเป็นต้องเปิดบราวเซอร์อยู่หรือเปิดหน้าที่ทำรายการทิ้งไว้

ความสามารถหลักอีกอย่างที่ไม่พูดถึงไม่ได้คือความสามารถในการเรียกใช้ Push Notifications หรือการส่งข้อความแจ้งเตือน ที่ส่วนแสดง Notifications บนมือถือ (นึกถึงการแสดงผลเมื่อมีข้อความใหม่ของไลน์นั่นแหละครับ) ยกตัวอย่างการใช้งานต่อจากการสั่งซื้อของนั่นแหละครับ เช่นเมื่อมีการส่งข้อมูลการสั่งซื้อไปยัง Server แล้ว ให้บราวเซอร์แสดงข้อมูลผ่าน Notifications ว่าการสั่งซื้อสำเร็จ เป็นต้น

เทคโนโลยี Progressive Web Apps อาศัยการทำงานของ Service Worker ซึ่งเป็นความสามารถหนึ่งของ Browser โดยอาศัยการเรียกใช้งานผ่าน Javascript โดยสามารถใช้งานได้กับทุกภาษาที่เขียนเว็บได้ ไม่ว่าจะเป็น PHP Go React Node.js เพราะสุดท้ายมันก็ต้องเป็นคำสั่ง Javascript เพื่อเรียกใช้งาน Service Worker อยู่ดี

การติดตั้งและเปิดใช้งาน Progressive Web Apps ต้องดำเนินการดังนี้
สร้างไฟล์ manifest.json ซึ่งเป็นไฟล์ JSON (จริงๆ นามสกุลอะไรก็ได้ แต่เนื้อหาต้องเป็นข้อมูล JSON) ซึ่งไฟล์นี้เป็นการกำหนดค่าของ Progressive Web Apps โดยมีเนื้อหาตามตัวอย่าง
{
  "name": "Somtum PHP micro Framework",
  "short_name": "Somtum",
  "scope": "/",
  "display": "browser",
  "start_url": "https://somtum.kotchasan.com/",
  "description": "เริ่มต้นการเรียนรู้ MVC ด้วย Framework ซึ่งมีองค์ประกอบไม่มากนัก ช่วยให้การเรียนรู้ง่ายขึ้น",
  "prefer_related_applications": false,
  "url": "https://somtum.kotchasan.com",
  "icons": [
     {
       "src": "skin/img/icons-192.png",
       "type": "image/png"
       "sizes": "192x192"  
     },
     {
       "src": "skin/img/icons-512.png",
       "type": "image/png"
       "sizes": "512x512"  
     }
  ],
  "theme_color": "#8a2be2"
}

อธิบายข้อมูลในไฟล์ manifest.json
  • name สำหรับระบุชื่อเว็บปกติ
  • short_name สำหรับระบุชื่อเว็บแบบสั้นๆ ซึ่งจะถูกใช้เมื่อมีการติดตั้งเว็บลงในหน้าจอหลักของมือถือ
  • scope ระบุขอบเขตของ URL ที่ต้องการให้แคช ปกติเป็น / หมายถึงทั้งหมดบนเว็บไซต์
  • display ระบุรูปแบบการแสดงผล เมื่อมีการเรียกแอปจากหน้าจอหลักของมือถือ ได้แก่ fullscreen standalone หรือ minimal-ui
  • start_url URL เริ่มต้นของเว็บไซต์ ปกติก็จะเป็นหน้าแรกของเว็บไซต์นั่นแหละครับ
  • description คำอธิบายเกี่ยวกับเว็บไซต์
  • prefer_related_applications ระบุเป็น false
  • icons เป็นแอเรย์ระบุไอคอนของเว็บที่จะใช้แสดงเป็นไอคอนเมื่อมีการติดตั้งบนมือถือ โดยต้องรวมไอคอนขนาด 192px และ 512px ด้วย
  • theme_color ระบุค่าสีของส่วน Address Bar ของบราวเซอร์ ในกรณีที่มีการแสดง Address Bar

ระบุที่อยู่ของไฟล์ manifest.json ภายใต้ tag head ของหน้าเว็บที่จะเรียกใช้ Progressive Web Apps
<link rel=manifest href="manifest.json">


ระบุค่าสีของส่วน Address Bar ของบราวเซอร์ ในกรณีที่มีการแสดง Address Bar จริงๆยังมีสีพื้นหลังอีกแต่ส่วนใหญ่เราไม่เห็นหรอกครับ เพราะมันจะแสดงเป็นพื้นหลังของบราวเซอร์อยู่แล้ว ภายใต้ tag head ของหน้าเว็บที่จะเรียกใช้ Progressive Web Apps เช่นกัน
<meta name="theme-color" content="#8a2be2">


เพิ่มคำสั่ง Javascript สำหรับลงทะเบียนใช้งาน Service Worker ใส่เอาไว้ในหน้าที่ต้องการให้ Service Worker ทำงาน
<script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js');
    }
</script>


เนื้อหาในไฟล์ sw.js ซึ่งการทำงานของ Service Worker จะถูกเรียกใช้จากที่นี่
var cacheName = 'hello-pwa';
var filesToCache = [
  '/',
  '/skin/gcss.css',
  '/skin/default/style.css',
  '/skin/fonts.css',
  '/skin/default/memory.png',
  '/skin/default/mmvc.png',
  '/skin/default/mvc.png',
  '/js/ddmenu.js',
  '/js/gajax.js',
  '/docs/',
  '/docs/load/',
  '/docs/somtum/',
  '/docs/router/',
  '/docs/base/',
  '/docs/config/',
  '/docs/template/',
  '/docs/view/',
  '/docs/controller/',
  '/docs/model/',
  '/docs/menu/',
  '/docs/request/',
  '/project/welcome/',
  '/project/crud/',
  '/mvc/'
];
self.addEventListener('install', function(e) {
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(filesToCache);
    })
  );
});
self.addEventListener('fetch', function(e) {
  e.respondWith(caches.match(e.request).then(function(response) {
    return response || fetch(e.request);
  }));
});


อธิบายการทำงานของไฟล์ sw.js
filesToCache ระบุชื่อไฟล์ทั้งหมดที่ต้องการแคช ในตัวอย่างจะเห็นว่ามีการแคช ไฟล์ javascript CSS และรูป รวมถึงหน้าเว็บทั้งหมดของเว็บไซต์ไว้ด้วย ซึ่งไฟล์ทั้งหมดทีสามารถแคชได้จะต้องอยู่ภายใต้ไดเร็คทอรี่เดียวกันกับที่ไฟล์ sw.js อยู่ด้วย ดังนั้นโดยทั่วๆไปเรามักจะวางไฟล์ sw.js ไว้ที่ root ของ Server

self.addEventListener('install', function(e) {
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(filesToCache);
    })
  );
});

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

self.addEventListener('fetch', function(e) {
  e.respondWith(caches.match(e.request).then(function(response) {
    return response || fetch(e.request);
  }));
});

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

ข้อเสียของ Progressive Web Apps
  1. จริงๆข้อเสียมันก็เป็นผลมาจากความสามารถในการแคชของ Progressive Web Apps นั่นแหละ เนื่องจากการแคชจะกระทำเพียงครั้งเดียวเมื่อมีการติดตั้ง Service Worker ในครั้งแรกเท่านั้น ดังนั้นหากข้อมูลมีการเปลี่ยนแปลงไปหลังจากการเรียกในครั้งแรก หรือหน้าเว็บมีข้อมูลที่ต้องเปลี่ยนแปลงบ่อย การออกแบบเว็บให้รองรับการเปลี่ยนแปลงจะยุ่งยากมาก ตลอดจนการแสดงผลในขณะออกแบบ หากเปิดการใช้งาน Service Worker แล้วอาจทำให้ไม่เห็นการเปลี่ยนแปลงได้ จนกว่าจะได้ทำการเคลียร์แคชก่อน แต่การเคลียร์แคชของ Service Worker เป็นคนละส่วนกับแคชของบราวเซอร์ซึ่งไม่สามารถเคลียร์แคชโดยการกด Ctrl+F5 ได้ อาจทำให้ผู้ใช้ทั่วไปไม่ค่อยเข้าใจนัก ซึ่งก็ต้องแลกกันระหว่างความสามารถในการทำงานแบบ Offline และการแคช กับปัญหาการใช้งานกับผู้ใช้ทั่วไป
  2. ข้อเสียอีกข้อคือ ความสามารถในการแคชอาจไม่สามารถทำได้กับทุกส่วนของเว็บไซต์ ยกตัวอย่างเช่น เว็บบอร์ด การแคชจะทำให้ไม่สามารถเห็นโพสต์ใหม่ของเว็บบอร์ดได้ ซึ่งการเลือกส่วนที่ต้องการแคช อาจเป็นจุดที่ยุ่งยากจุดหนึ่งในการออกแบบเลยทีเดียว 
  3. การรับส่งข้อมูลในเบื้องหลังอีกเช่นกัน อาจเป็นปัญหาหนึ่งได้เช่นกัน ยกตัวอย่างเช่น การสั่งซื้อสินค้าที่ต้องมีการตัดสต๊อค การสั่งซื้อไมไ่ด้เสร็จสมบูรณ์ในโหมด Offline เนื่องจากคำสั่งซื้อไม่ได้ส่งไปยัง Server ทำให้คนอื่นๆที่ใช้งานอยู่ในฝั่ง Server ไม่ได้รับรู้คำสั่งซื้อนี้และสินค้าไม่ได้ถูกตัดสต๊อคออกไปจริงๆ อาจมีปัญหาในจุดนี้ได้
สำหรับการแก้ปัญหาตามข้อที่ 1 ไม่ค่อยยุ่งยากเท่าไร ถ้าดูจากโค้ดด้านบนมีการใช้งานตัวแปร cacheName อยู่ ในกรณีที่เราต้องการอัปเดทแคชให้เราทำการเปลี่ยนชื่อ cacheName เป็นชื่ออื่นก็จะเป็นการบังคับให้ Service Worker ทำการแคชไฟล์ใหม่อีกครั้ง

ตัวอย่างการใช้งาน Progressive Web Apps ตามบทความนี้คือ https://somtum.kotchasan.com (View Source ดูได้เลยครับ)
^