PWA چیست؟
در سال 2015 عبارت Progressive Web App یا همان PWA توسط گوگل معرفی شد. PWAها وب اپلیکیشنهایی هستند که با استفاده از تکنولوژیهای (JavaScript، HTML، CSS، WebAssembly) ساخته میشوند و تجربهای مشابه با Native App را در کاربران ایجاد میکنند. PWAها با وجود داشتن یک Codebase، میتوانند نصب شوند و روی تمام دستگاهها (Windows, macOS, Android, iOS) اجرا شوند و همچنین کاربران می توانند آن را به home screen دستگاه خود اضافه کنند.
مزایای PWA:
با افزایش حمایت سیستم عاملها(OS) و مرورگرها(browser) از این وب تکنولوژی، PWAها بهترین ویژگیهای وبسایتها و Native Appها را دارند:
home screen دستگاه اضافه شود، Push Notification دارند و میتوان آنها را در Store منتشر کرد.
محدودیتهای PWA:
با وجود حمایت سیستم عاملها، همچنان محدودیتهایی برای PWA در سیستم عامل iOS وجود دارد:
البته لازم به ذکر است این محدودیتها ممکن است در ورژنهای جدیدتر سیستم عامل iOS برطرف شود.
یک PWA خوب چه ویژگیهایی دارد؟
PWA از چه کامپوننتهایی تشکیل میشود؟
PWA از این سه مولفه اصلی تشکیل میشوند:
1- HTTPS: پروتکل HTTPS یا HyperText Transfer Protocol Secure نسخه ایمن HTTP است که تبادلات بین Client و Server در Web App را رمزگذاری میکند. PWA باید حتما روی پروتکل HTTPS پیادهسازی شود و وجود HTTPS برای Service Workerها الزامی است.
2- Service Worker:
Service Workerها نوعی از Web Workerها هستند که در Thread جداگانه کار میکنند و با استفاده از Fetch API میتوانند هر Network Request را رهگیری، تغییر و پاسخ دهند. همچنین Service Workerها با استفاده از Cache API میتوانند به Cache سمت Client و Asynchronous Store سمت Client (مانند IndexedDB) برای ذخیره اطلاعات (برای استفاده در مواقعی که کاربر آفلاین است) دسترسی دارد.
Service Workerها باعث میشود PWA قابل اعتماد و Network-Independent باشد به طوری که تجربه کاربری موثری برای زمانی که کاربر آفلاین است یا اینترنت ضعیف دارد، فراهم میآورد.
Service Workerها چطور کار میکنند؟
برای اینکه بدانیم Service Workerها چطور کار میکنند، نیاز داریم دو مفهوم زیر را بدانیم:
ثبت سرویس ورکر (Service Worker Registration):
برای ثبت Service Worker از کد زیر استفاده میکنیم و این کد را در تگ script و در انتهای قسمت body در صفحه html قرار میدهیم:
if('serviceWorker' in navigator) { // Register the service worker navigator.serviceWorker.register('/sw.js', { scope: '/' }); }
از آنجایی که Service Workerها فقط میتوانند تمام Requestهای صفحاتی که در Scope فایل Service Worker قرار میگیرند را رهگیری و مدیریت کنند، باید فایل Service Worker را در root برنامه قرار دهیم.
چرخه حیات سرویس ورکر (Service Worker Lifecycle):
قسمت Lifecycle Event شامل این سه فاز میباشد:
self.addEventListener('install', function( event ){ console.log( 'WORKER: install event in progress.' ); });
self.addEventListener( 'activate', function( event ){ console.log( 'WORKER: activation event in progress.' ); clients.claim(); console.log( 'WORKER: all clients are now controlled by me!' ); });
با استفاده از clients.claim میتوان نسخه قبلی را فورا با نسخه جدید جایگزین کرد.
توضیح در مورد Functional Eventهای سرویس ورکر:
self.addEventListener('fetch', function( event ){ console.log( 'WORKER: Fetching', event.request ); });
self.addEventListener('push', function( event ){ console.log( 'WORKER: Received notification', event.data ); });
3- Manifest:
برای اینکه PWA مانند Native Appها قابلیت نصب داشته باشند از Manifest استفاده میکنیم. فایل Manifest یک فایل با فرمت JSON میباشد که با استفاده از پراپرتیهای (key-value pair) ظاهر و رفتار PWA را با تعریف نام، توضیحات درباره برنامه، icon و …. مشخص میکند.
برای ایجاد فایل Manifest فقط کافیست اطلاعات لازم را بصورت پراپرتی (key-value) در یک فایل JSON قرار داده و لینک آن را در قسمت head صفحه html قرار دهیم.
<link rel='manifest' href='/manifest.json'>
برای فایل Manifest حداقل باید این سه پراپرتی را قرار دهیم:
{ 'name': 'My PWA', 'lang': 'en-US', 'start_url': '/' }
که به ترتیب شامل نام برنامه، زبان آن و URL ای که باید موقع launch برنامه به آن navigate شود، میباشد.
منظور از Offline Support بودن PWA چیست؟
تفاوت کلیدی بین Native App و Web وابستگی به Network است. وقتی که Network در دسترس نیست، Web کارایی ندارد. این درحالیست که در PWA با استفاده از قابلیت Caching که توسط Service Workerها از طریق Request Intercepting پیادهسازی میشود، تجربه کاربری مناسب به کاربر ارائه میشود.
Service Workerها برای Cache کردن اطلاعات روی دستگاه کابر دو گزینه دارد:
برای cache کردن با سه سوال مواجه میشویم:
استراتژی های مختلف برای cache کردن وجود دارد که چند نمونه آن را به همراه کد بررسی میکنیم:
// named cache in Cache Storage const CACHE_NAME = 'devtools-tips-v3'; // list of requests whose responses will be pre-cached at install const INITIAL_CACHED_RESOURCES = [ '/', '/offline/', '/assets/style.css', '/assets/share.js', '/assets/logo.png', ]; // install event handler (note async operation) // opens named cache, pre-caches identified resources above self.addEventListener('install', event => { event.waitUntil((async () => { const cache = await caches.open(CACHE_NAME); cache.addAll(INITIAL_CACHED_RESOURCES); })()); });
// We have a cache-first strategy, // where we look for resources in the cache first // and only on the network if this fails. self.addEventListener('fetch', event => { event.respondWith((async () => { const cache = await caches.open(CACHE_NAME); // Try the cache first. const cachedResponse = await cache.match(event.request); if (cachedResponse !== undefined) { // Cache hit, let's send the cached resource. return cachedResponse; } else { // Nothing in cache, let's go to the network. return fetch(event.request) } } }
self.addEventListener('fetch', event => { event.respondWith( fetch(event.request) .catch(error => { return caches.match(event.request) ; }) ); });
self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(cachedResponse => { const networkFetch = fetch(event.request).then(response => { // update the cache with a clone of the network response caches.open(CACHE_NAME).then(cache => { cache.put(event.request, response.clone()); }); }); // prioritize cached response over network return cachedResponse || networkFetch; } ) ) });
نحوه تبدیل یک وبسایت معمولی به PWA:
برای این کار من یک پروژه که شامل (html, css, JavaScript) است و حدود سه سال پیش برای تمرین html و css انجام داده بودم را به PWA تبدیل میکنم و مراحل آن را توضیح میدهم. لازم به ذکر است که PWA هیچ وابستگی به UI Framework مثل Vue.js ،Reactjs و یا Angular ندارد و تنها کافیست کامپوننتهای مذکور موجود باشند. (لینک گیتهاب پروژه: https://github.com/NaserAhadi/food-recipe)
مرحله اول: ایجاد فایل Manifest
ابتدا یک فایل Manifest به فرمت json در root پروژه ایجاد میکنیم و پراپرتیهای آن را تعریف میکنیم. فایل Manifest پروژه مذکور به قرار زیر است:
{ 'lang': 'en-us', 'name': 'Food Recipes App', 'short_name': 'Food-Recipes', 'description': 'A basic Food Recipes application Which show how cook delicious food', 'start_url': '/', 'background_color': '#2f3d58', 'theme_color': '#2f3d58', 'orientation': 'any', 'display': 'standalone', 'icons': [ { 'src': './images/chef-icon.png', 'sizes': '600x600' } ] }
لینک فایل Manifest را در صفحه html در قسمت تگ head قرار میدهیم.
<link rel='manifest' href='/manifest.json'>
مرحله دوم: ایجاد Service Worker و Register آن
همانطور که گفته شد فایل Service Worker را با نام sw.js در root پروژه ایجاد میکنیم تا تمام Requestهای صفحاتی که در Scope خود قرار دارند را رهگیری و مدیریت کند. فایل sw.js در install event صفحه html، فایل main.js، فایل style.css و عکسها را cache میکند. همچنین با fetch event هر بار که request به سمت Server فرستاده میشود آن را Intercept میکند و با استفاده از استراتژی cache-first که در کد پیادهسازی شده، Service Worker اطلاعات Cache شده را برمیگرداند، بنابراین برنامه در مواقع آفلاین تجربه کاربری مناسب ارائه میدهد.
const CACHE_NAME = `food-recipes`; // Use the install event to pre-cache all initial resources. self.addEventListener('install', event => { event.waitUntil((async () => { const cache = await caches.open(CACHE_NAME); cache.addAll([ '/', '/main.js', '/style.css', '/images/chef-favicon.png', '/images/chef-icon.png', '/images/curry.png', '/images/noodles.png', '/images/stew.png', ]); })()); }); self.addEventListener('fetch', event => { event.respondWith((async () => { const cache = await caches.open(CACHE_NAME); // Get the resource from the cache. const cachedResponse = await cache.match(event.request); if (cachedResponse) { return cachedResponse; } else { try { // If the resource was not in the cache, try the network. const fetchResponse = await fetch(event.request); // Save the resource in the cache and return it. cache.put(event.request, fetchResponse.clone()); return fetchResponse; } catch (e) { // The network failed. } } })()); });
برای ثبت Service Worker کد زیر را در انتهای تگ body در صفحه html قرار میدهیم:
if('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js', { scope: '/' }); }
مرحله سوم: استفاده از local web server
من برای این برنامه به جای Deploy آن روی Web Server از http-server لایبرری Node.js که local web server است، استفاده کردم. برنامه در URL هایی local web server در اختیار ما میگذارد قابل دسترس است. برای اجرای برنامه از دستور زیر استفاده میکنیم.
npx http-server
برنامه در http://localhost:8080 و دیگر URL هایی که به ما میدهد قابل اجراست. برای هدف Debugging مرورگرها اجازه میدهند localhost web server از PWA استفاده کنند حتی بدون پروتکل HTTPS.
درخواست نصب برنامه به محض اجرای برنامه در مرورگر:
اجرای برنامه نصب شده در دسکتاپ (بدون تب مرورگر):
اجرای برنامه به صورت اپلیکیشن نصب شده در موبایل:
تبدیل PWA به TWA:
قبل از هر چیز لازم به ذکر است Trusted Web Activities یا TWA راهی است برای انتشار PWA به عنوان اپلیکیشن APK در App Store. برای ایجاد اپلیکیشن APK از PWA ما میتوانیم از ابزار آنلاین استفاده کنیم که آماده برای انتشار آن در App Store میباشد و نیازی به نصب Android Studio روی سیستم نداریم.
انتشار PWA در App Store قابلیت اکتشاف برنامه را بالا میبرد بطوریکه کاربر علاوه بر دسترسی به برنامه با URL در مرورگرها، میتواند آن را با جستجو در App Storeها پیدا و نصب کند.
مراحل ایجاد TWA:
برای انتشار TWA در App Store، فایل assetlinks.json را در وبسایت در مسیر زیر Deploy کنید.
<HOST_URL>/.well-known/assetlinks.json
این فایل با انطباق fingerprints نشان میدهد شما صاحب PWA و TWA هستید و همچنین اجازه میدهد TWA در حالت fullscreen و بدون browser UI اجرا شود. نمونهای از فایل assetlinks.json :
[{ 'relation': ['delegate_permission/common.handle_all_urls'], 'target' : { 'namespace': 'android_app', 'package_name': 'app.webboard.twa', 'sha256_cert_fingerprints': ['92:1C:08:E0:A6:4D:29:87:DF:70:3E:B4:0F:E9:0C:6D:D1:70:D0:8F:AD:97:29:64:EA:0A:69:A2:3F:27:C7:06'] } } ]
بررسی تاثیر (PWA, TWA) در جذب کاربر برای استفاده از برنامه:
برای بررسی این مورد یک پروژه شخصی که توسط یکی از همکارانم در شرکت وندار، آقای حامد استادی، توسعه داده شده است را مورد مطالعه قرار میدهیم. این پروژه با نام تجاری "همسادهها"، یک نرمافزار مدیریت ساختمان و پرداخت شارژ آنلاین است که با آن میتوانید ثبت واحد، پرداخت قبوض و پرداخت شارژ ساختمان را به صورت آنلاین انجام دهید.
وبسایت پروژه در تاریخ June 1, 2021 (معادل ۱۱ خرداد سال ۱۴۰۰) انتشار یافته است و تا تاریخ October 19, 2022 (معادل ۲۷ مهر سال ۱۴۰۱) به (PWA, TWA) تبدیل نشده است که حدودا در عرض ۱۶ ماه مطابق تصویر زیر ۵۵۵ کاربر از این وبسایت استفاده میکردند.
از تاریخ October 20, 2022 (معادل ۲۸ مهر سال ۱۴۰۱) این وبسایت به PWA و TWA تبدیل و در بازار منتشر شده است. همانطور که مطابق تصویر زیر میبینیم، تعداد کاربران تا به امروز، در عرض حدودا ۴ ماه، از ۵۵۵ کاربر به ۱۱۲۹ کاربر رسیده است که نشان دهنده رشد دو برابری در مدت زمان کم میباشد.
منابع:
1- 30 days of pwa - blog series - ( win-student-devs)
2- Get started with Progressive Web Apps - Microsoft Learn
4- Progressive web app - Wikipedia
5- Publishing your PWA in the Play Store in a couple of minutes using PWA Builder
6- Publishing Trusted Web Application on Bazaar
7- Publishing a web app to the Play Store using Trusted Web Activities (TWA)
8-Current Progressive Web App Limitations To iOS Users