كيفية تحسين تطبيقات الويب التقدمية: تجاوز الأساسيات
نشرت: 2022-03-10لا تزال تطبيقات الويب التقدمية (PWA) تحظى بشعبية كبيرة في عام 2020. ولم يكن ذلك مفاجئًا ، بالنظر إلى فوائد معدلات التحويل المرتفعة ، ومشاركة العملاء ، وانخفاض سرعة تحميل الصفحة ، وانخفاض تكاليف التطوير والنفقات العامة.
يمكننا أن نرى شركات محترمة تتمتع أيضًا بالنجاح مع تطبيقات الويب الخاصة بهم ، مثل Twitter و Uber و Tinder و Pinterest و Forbes. ويفخرون جميعًا بفوائد هائلة من تنفيذ التطبيقات التقدمية.
والخبر السار هو أن تطوير PWA ليس شيئًا تستطيع الشركات ذات الميزانية الكبيرة فقط تحمله. تخدم هذه التطبيقات الشركات الصغيرة والمتوسطة على قدم المساواة وليست معقدة في إنشائها.
يمكنك العثور على دليل شامل للمبتدئين لتطبيقات الويب التقدمية في مجلة Smashing التي تركز على بناء جوهر PWAs.
ومع ذلك ، دعنا نتخذ خطوة إلى الأمام ونتعلم كيفية نشر الصفات الحديثة في PWAs ، مثل الوظائف غير المتصلة بالإنترنت ، والتحسين المستند إلى الشبكة ، وتجربة المستخدم عبر الأجهزة ، وقدرات تحسين محركات البحث (SEO) ، والإشعارات والطلبات غير المتطفلة. ستجد أيضًا مثالاً على كود أو مراجع لأدلة أكثر تحديدًا حتى تتمكن من تنفيذ هذه النصائح على PWA الخاص بك.
نظرة عامة سريعة على تطبيقات الويب التقدمية (PWA)
دعنا لا نتخطى الأساسيات وننتقل بسرعة إلى قلب PWAs.
ما هو PWA؟
"تم تصميم تطبيقات الويب التقدمية (PWA) وتحسينها باستخدام واجهات برمجة التطبيقات الحديثة لتقديم إمكانات محسّنة وموثوقية وقابلية للتثبيت أثناء الوصول إلى أي شخص في أي مكان وعلى أي جهاز بقاعدة شفرة واحدة."
- مطورو جوجل
بمعنى آخر ، PWAs هي مواقع ويب يمكن للمستخدمين استخدامها كتطبيقات قائمة بذاتها. إنها تختلف عن التطبيقات الأصلية بشكل أساسي لأن تطبيقات الويب التقدمية (PWA) لا تتطلب التثبيت ويمكن استخدامها مع أجهزة مختلفة - التطبيقات الأصلية مصممة بشكل أساسي للأجهزة المحمولة.
كيف تعمل PWAs؟
يتكون جوهر PWA من ثلاثة مكونات: بيان تطبيق الويب ، وعمال الخدمة ، و shell التطبيق. يمكنك العثور على تعليمات مفصلة لبناء تلك في دليل المبتدئين المذكور أعلاه.
إليك ما تفعله هذه المكونات.
بيان تطبيق الويب
يعد بيان تطبيق الويب هو الأساس لإنشاء موقع ويب يعمل كتطبيق مستقل في وضع ملء الشاشة. يمكنك تحديد شكل PWA ، وتحسينه للأجهزة المختلفة ، وتعيين رمز يتم عرضه بعد تثبيت التطبيق.
عمال الخدمة
يمكّن عمال الخدمة الاستخدام غير المتصل لـ PWA عن طريق جلب البيانات المخزنة مؤقتًا أو إعلام المستخدم بغياب اتصال الإنترنت. يقوم عمال الخدمة أيضًا باسترداد أحدث البيانات بمجرد استعادة اتصال الخادم.
بنية غلاف التطبيق
غلاف التطبيق هو ما يراه المستخدمون عند الوصول إلى PWA. إنه الحد الأدنى من HTML و CSS وجافا سكريبت المطلوب لتشغيل واجهة المستخدم. عند تطوير PWA ، يمكنك تخزين موارد وأصول هيكل التطبيق مؤقتًا في المتصفح.
نشر الخصائص الحديثة في PWA الخاص بك
علاوة على الميزات الأساسية ، تتبنى PWAs الحديثة سمات إضافية تدفع مستخدميها نحو تجربة مستخدم أكثر استثنائية.
دعنا نلقي نظرة على بعض الخصائص الحديثة المحددة لـ PWA ونتعرف على كيفية إضافتها إلى PWA الخاص بك. تعتبر الصفات التالية إضافات رائعة إلى PWA الأساسي بواسطة مطوري Google.
يعمل التطبيق دون اتصال بالإنترنت كما يعمل عبر الإنترنت
عند إنشاء PWA الخاص بك ، يمكنك أيضًا تطوير صفحة مخصصة غير متصلة بالإنترنت كجزء من النواة. ومع ذلك ، يكون الأمر أكثر سهولة في الاستخدام إذا استمر تطبيق PWA الخاص بك في العمل حتى بدون اتصال بالإنترنت - حتى نقطة معينة حيث يصبح الاتصال ضروريًا. خلاف ذلك ، قد تكون تجربة المستخدم محبطة مثل محنة أنكيتا ماساند في طلب كعكة كما وصفتها في مقالها حول نقاط الألم في PWAs.
يمكنك تحقيق تجربة مستخدم أكثر أهمية باستخدام المحتوى المخزن مؤقتًا ومزامنة الخلفية والشاشات الهيكلية. دعونا نلقي نظرة على كل واحد.
المحتوى المخزن مؤقتًا باستخدام IndexedDB
IndexedDB
هو نظام تخزين NoSQL في المتصفح يمكنك استخدامه للتخزين المؤقت واسترداد البيانات المطلوبة لجعل PWA الخاص بك يعمل دون اتصال.
ومع ذلك ، لا تدعم جميع المتصفحات IndexedDB
، لذا فإن أول شيء تريد القيام به هو التحقق مما إذا كان متصفح المستخدم يدعمه.
if (!('indexedDB' in window)) { console.log('This browser doesn\'t support IndexedDB'); return; }
بعد ذلك ، يمكنك إنشاء محتوى مخزن مؤقتًا باستخدام IndexedDB API. هذا مثال من مطوري Google لفتح قاعدة بيانات وإضافة مخزن عناصر وإضافة عنصر إلى هذا المتجر.
var db; var openRequest = indexedDB.open('test_db', 1); openRequest.onupgradeneeded = function(e) { var db = e.target.result; console.log('running onupgradeneeded'); if (!db.objectStoreNames.contains('store')) { var storeOS = db.createObjectStore('store', {keyPath: 'name'}); } }; openRequest.onsuccess = function(e) { console.log('running onsuccess'); db = e.target.result; addItem(); }; openRequest.onerror = function(e) { console.log('onerror!'); console.dir(e); }; function addItem() { var transaction = db.transaction(['store'], 'readwrite'); var store = transaction.objectStore('store'); var item = { name: 'banana', price: '$2.99', description: 'It is a purple banana!', created: new Date().getTime() }; var request = store.add(item); request.onerror = function(e) { console.log('Error', e.target.error.name); }; request.onsuccess = function(e) { console.log('Woot! Did it'); }; }
مزامنة الخلفية
إذا كان PWA الخاص بك يقوم بمزامنة البيانات في الخلفية ، فيمكن للمستخدم اتخاذ إجراءات أثناء عدم الاتصال ، والتي يتم تنفيذها بعد ذلك عند استعادة اتصال الإنترنت. مثال بسيط هو تطبيق المراسلة. يمكن للمستخدم إرسال رسالة عند عدم الاتصال بالإنترنت دون الحاجة إلى الانتظار حتى يتم إرسالها - ترسل مزامنة الخلفية تلقائيًا الرسالة عند استعادة الاتصال.
فيما يلي مثال على كيفية تطوير ميزة مزامنة الخلفية بواسطة Jake Archibald.
// Register your service worker: navigator.serviceWorker.register('/sw.js'); // Then later, request a one-off sync: navigator.serviceWorker.ready.then(function(swRegistration) { return swRegistration.sync.register('myFirstSync'); });
ثم استمع إلى الحدث في /sw.js
:
self.addEventListener('sync', function(event) { if (event.tag == 'myFirstSync') { event.waitUntil(doSomeStuff()); } });
شاشات الهيكل العظمي
تتمثل إحدى المزايا الرئيسية لاستخدام الشاشات الهيكلية في أن المستخدمين يدركون أن التطبيق يعمل بدلاً من الجلوس في وضع الخمول. في حين أن المستخدم ليس لديه اتصال ، فإن الشاشة الهيكلية تسحب الواجهة بدون محتوى - والتي تملأ بعد ذلك بمجرد استعادة الاتصال.
يحتوي Code My UI على بعض مقتطفات التعليمات البرمجية الممتازة المتاحة التي يمكنك استخدامها لإنشاء شاشة هيكلية لـ PWA.

التحسين على أساس استخدام الشبكة
تتمثل الميزة المركزية لـ PWA في أنها توفر تجربة أسرع للمستخدمين. يمكنك زيادة تحسين سرعة التحميل من خلال جعل PWA يستخدم شبكات ذاكرة التخزين المؤقت أولاً ، وتحديد أولويات الموارد ، واستخدام التحميل التكيفي بناءً على جودة الشبكة.
لنلقِ نظرة على كيفية تطويرها لـ PWA الخاص بك.
ذاكرة التخزين المؤقت أولاً ، ثم الشبكة
يؤدي استخدام المحتوى المخزن مؤقتًا أولاً إلى تمكين PWA الخاص بك من العمل في وضع عدم الاتصال ويمهد الطريق للمستخدمين للوصول إلى المحتوى حتى في مناطق تغطية الشبكة المنخفضة. يمكنك القيام بذلك عن طريق إنشاء عامل خدمة لتخزين المحتوى مؤقتًا ثم جلبه.
هذا مثال من Jeff Posnick حول التخزين المؤقت لـ HTML ثابت باستخدام عمال الخدمة.
self.addEventListener('fetch', event => { if (event.request.mode === 'navigate') { // See /web/fundamentals/getting-started/primers/async-functions // for an async/await primer. event.respondWith(async function() { // Optional: Normalize the incoming URL by removing query parameters. // Instead of https://example.com/page?key=value, // use https://example.com/page when reading and writing to the cache. // For static HTML documents, it's unlikely your query parameters will // affect the HTML returned. But if you do use query parameters that // uniquely determine your HTML, modify this code to retain them. const normalizedUrl = new URL(event.request.url); normalizedUrl.search = ''; // Create promises for both the network response, // and a copy of the response that can be used in the cache. const fetchResponseP = fetch(normalizedUrl); const fetchResponseCloneP = fetchResponseP.then(r => r.clone()); // event.waitUntil() ensures that the service worker is kept alive // long enough to complete the cache update. event.waitUntil(async function() { const cache = await caches.open('my-cache-name'); await cache.put(normalizedUrl, await fetchResponseCloneP); }()); // Prefer the cached response, falling back to the fetch response. return (await caches.match(normalizedUrl)) || fetchResponseP; }()); } });
تحديد أولويات الموارد
بشكل افتراضي ، تتمتع PWAs بأداء أكبر على التطبيقات الأصلية المماثلة بسبب طبيعتها الخفيفة. علاوة على ذلك ، نظرًا لأن PWAs تستخدم ذاكرة التخزين المؤقت للمتصفح ، فمن الممكن الإشارة إلى الموارد التي لها الأولوية وتحتاج إلى عرضها حتى قبل استخدامها. يعمل هذا بشكل أساسي مع العناصر الثابتة حيث يحتاج المحتوى الديناميكي إلى التحديث قبل جلبه.
يمكنك تحديد أولوية العناصر باستخدام سلسلة <link>
في HTML. يمكنك أيضًا تحديد ملفات خادم الطرف الثالث باستخدام rel=”preconnect”
و rel=”dns-prefetch.”
يعطي Maximiliano Firtman مثالاً بسيطًا على ذلك من خلال إعطاء الأولوية لخطوط الويب داخل محرك المتصفح:
<link rel=”preload” as=”font” href=”font.woff” crossorigin>
تنفيذ التحميل التكيفي
لا يمكن الوصول إلى سرعات الإنترنت لشبكات WiFi و 4G في كل مكان ، ولا يزال المستخدمون يتصلون بالإنترنت من خلال اتصالات 2G و 3G. نظرًا لأنك تريد أن يكون PWA الخاص بك متاحًا لأكبر عدد ممكن من الأشخاص ، فقد ترغب في تحسينه لأداء سرعات إنترنت منخفضة أيضًا.
يمكنك تحقيق ذلك من خلال تنفيذ التحميل التكيفي ، والذي يقوم بتحميل عناصر PWAs بناءً على نوع الاتصال الذي يمتلكه المستخدم.


الطريقة الأكثر مباشرة هي استخدام أداة Google Workbox ، والتي تتضمن العديد من المكونات الإضافية الجاهزة لاستراتيجيات التخزين المؤقت.
افترض أنك تريد تحديد إستراتيجية مخصصة للتخزين المؤقت. إليك كيفية القيام بذلك كمثال من Demian Renzulli و Jeff Posnick:
const adaptiveLoadingPlugin = { requestWillFetch: async ({request}) => { const urlParts = request.url.split('/'); let imageQuality; switch ( navigator && navigator.connection ? navigator.connection.effectiveType : '' ) { //... case '3g': imageQuality = 'q_30'; break; //... } const newUrl = urlParts .splice(urlParts.length - 1, 0, imageQuality) .join('/') .replace('.jpg', '.png'); const newRequest = new Request(newUrl.href, {headers: request.headers}); return newRequest; }, };
بعد ذلك ، قم بتمرير المكون الإضافي إلى إستراتيجية cacheFirst
التي تحتوي على تعبير عادي لمطابقة عناوين URL للصور (على سبيل المثال /img/
):
workbox.routing.registerRoute( new RegExp('/img/'), workbox.strategies.cacheFirst({ cacheName: 'images', plugins: [ adaptiveLoadingPlugin, workbox.expiration.Plugin({ maxEntries: 50, purgeOnQuotaError: true, }), ], }), );
تجربة مستخدم ممتازة على جميع المنصات
يعمل PWA الممتاز بسلاسة على المتصفحات والجوال والكمبيوتر اللوحي. أثناء استخدام جهاز Android هو الطريقة الأكثر شيوعًا (بحصة سوقية تبلغ 38.9٪) للوصول إلى الإنترنت ، فإن تحسين تطبيقك لجميع الأنظمة الأساسية يعد جزءًا من تطوير وظائف PWA الأساسية.
يمكنك اتخاذ مزيد من الخطوات لزيادة قابلية الاستخدام وتقديم تجربة مستخدم رائعة ، مثل تقليل القفز عند تحميل PWA والتأكد من أن PWA يعمل مع أي طريقة إدخال.
إليك كيفية التعامل مع كل جانب من هذه الجوانب.
تقليل تحميل المحتوى "السريع"
حتى مع الإنترنت عالي السرعة ، يمكن أن يتغير محتوى الموقع أثناء التحميل لأن عناصر الموقع يتم تحميلها بالترتيب. يزداد هذا التأثير سوءًا مع سرعات اتصال أبطأ ويضر بشدة بتجربة المستخدم.
العناصر الأكثر شيوعًا التي تتسبب في تحول المحتوى أثناء التحميل هي الصور نظرًا لأنها أكبر بشكل عام وليست ذات أولوية عند تحميل المحتوى. يمكنك معالجة هذه المشكلة من خلال "التحميل البطيء" باستخدام صور عناصر نائبة أصغر يمكنك تحديد أولوياتها بعد عرض بنية HTML.
إليك مثال بواسطة مطوري Mozilla حول كيفية إضافة صورة خفيفة الوزن يتم تحميلها أولاً قبل الصورة الفعلية في JavaScript:
<img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'>
يعالج ملف app.js
سمات data-src مثل:
let imagesToLoad = document.querySelectorAll('img[data-src]'); const loadImages = (image) => { image.setAttribute('src', image.getAttribute('data-src')); image.onload = () => { image.removeAttribute('data-src'); }; };
ثم قم بإنشاء حلقة:
imagesToLoad.forEach((img) => { loadImages(img); });
يمكنك أيضًا الاطلاع على دليل شامل في مجلة Smashing حول تقليل المحتوى الذي يقفز مع العناصر الأخرى.
يعمل PWA مع أي طريقة إدخال
لقد قمنا بتغطية كيفية عمل PWAs مع مجموعة متنوعة من الأجهزة المختلفة. لاتخاذ خطوة إلى الأمام ، تحتاج أيضًا إلى حساب طرق الإدخال الأخرى التي يمكن للمستخدمين استخدامها على هذه الأجهزة ، مثل اللمس والماوس والقلم.
تؤدي إضافة Pointer Events API إلى PWA إلى حل هذا السؤال بشكل أساسي. إليك كيفية التعامل معها وفقًا لمطوري Google.
أولاً ، تحقق مما إذا كان المستعرض يدعم أحداث المؤشر:
if (window.PointerEvent) { // Yay, we can use pointer events! } else { // Back to mouse and touch events, I guess. }
بعد ذلك ، يمكنك تحديد الإجراءات التي يمكن أن تتخذها طرق الإدخال المختلفة:
switch(ev.pointerType) { case 'mouse': // Do nothing. break; case 'touch': // Allow drag gesture. break; case 'pen': // Also allow drag gesture. break; default: // Getting an empty string means the browser doesn't know // what device type it is. Let's assume mouse and do nothing. break; }
نظرًا لأن معظم المتصفحات تحتوي بالفعل على ميزات تعمل باللمس ، فلن تحتاج إلى إضافة أي شيء آخر.
يمكن اكتشافه من خلال البحث
تتمثل إحدى الفوائد الرئيسية لـ PWAs على تطبيق أصلي في أن PWA هو موقع ويب بطبيعته ويمكن لمحركات البحث فهرسته. يتيح لك ذلك نشر استراتيجيات تحسين محركات البحث (SEO) لجعل محتوى PWA الخاص بك أكثر قابلية للاكتشاف.
يمكنك البدء بالتأكد من أن كل عنوان URL في PWA الخاص بك يحتوي على عنوان وصفي فريد ووصف تعريفي ، وهو الأساس لأي نشاط تحسين تحسين محركات البحث (SEO).
لنلقِ نظرة على بعض الخطوات الأخرى التي يمكنك اتخاذها لجعل ملف PWA الخاص بك قابلاً للبحث.
تحليل إمكانية العثور على PWA الخاص بك
تمتلك Google أداة ممتازة في Search Console الخاصة بها تقوم بتحليل موقعك (PWA) وإعداد تقارير بالنتائج. يمكنك استخدامه لإجراء فحص أساسي لموقعك واكتشاف أي نقاط ضعف يمكنك بعد ذلك البدء في إصلاحها.
بدلاً من ذلك ، يمكنك استخدام Lighthouse في متصفح Chrome لإجراء تدقيق لتحسين محركات البحث.
أولاً ، انتقل إلى عنوان URL المستهدف. ثم اضغط على Control+Shift+J
(أو Command+Option+J
على نظام Mac) الذي يفتح قائمة أدوات المطور. اختر علامة التبويب Lighthouse ، وحدد مربع فئة SEO ، وقم بإنشاء التقرير.

استخدم البيانات المنظمة
يستخدم محرك بحث Google البيانات المنظمة لفهم الغرض من المحتوى على صفحة الويب الخاصة بك.
البيانات المهيكلة هي تنسيق موحد لتوفير معلومات حول الصفحة وتصنيف محتوى الصفحة ؛ على سبيل المثال ، في صفحة الوصفات ، ما هي المكونات ووقت الطهي ودرجة الحرارة والسعرات الحرارية وما إلى ذلك.
- غوغل
قبل أن تبدأ في الترميز ، قامت Google أيضًا بتجميع قائمة مفيدة بأخطاء البيانات المنظمة الشائعة والإرشادات ذات الصلة لإصلاحها. يجب أن تمنحك دراسة هذه المواد أساسًا جيدًا لما يجب تجنبه.
كتب فريدريك أوبراين دليلاً ممتازًا عن مجلة Smashing ، خَبز البيانات المهيكلة في عملية التصميم ، والذي يصف كيفية إنشاء بيانات منظمة من البداية.
إخطارات سهلة الاستخدام وطلبات الإذن
أخيرًا وليس آخرًا ، يمكنك زيادة تجربة المستخدم من خلال تحسين الإشعارات وطلبات الأذونات ، بحيث تخدم المستخدمين - بدلاً من كونها مربكة ومزعجة.
بينما يمكنك الاعتماد بشكل عام على الفطرة السليمة ، هناك نصائح عملية يمكنك تنفيذها أيضًا ، مثل إنشاء إشعارات دفع غير تدخلية وإعطاء المستخدم خيارًا لإلغاء الاشتراك من الرسائل.
طلبات إذن خفية للاتصال
هناك طريقتان حديثتان لأتمتة الاتصال بين موقع الويب والمستخدم - روبوتات المحادثة والإشعارات.
في سياق تطبيقات الويب التقدمية (PWA) ، تتمثل الميزة الرئيسية لروبوت المحادثة في أنه لا يتطلب إذن المستخدم للتفاعل مع المستخدم. ومع ذلك ، بناءً على تطبيق chatbot الذي تستخدمه ، قد يفوت المستخدم الرسالة الدقيقة. من ناحية أخرى ، تتطلب الإشعارات إذن المستخدم ولكنها أكثر وضوحًا.
نظرًا لأنه يمكنك إضافة chatbot كتطبيق منفصل تابع لجهة خارجية ، فلنركز على إنشاء إشعار دفع سهل الاستخدام. إذا كنت بحاجة إلى دليل حول كيفية إنشاء إشعار دفع في المقام الأول ، فإليك دليلًا رائعًا من Indrek Lasn.
الطريقة الأكثر مباشرة لإنشاء طلب إذن غير تدخلي هي استخدام طلب مزدوج. هذا يعني أنك تقوم بتضمين تفاعل مخصص لموقعك فوق التفاعل الافتراضي من نظام تشغيل المستخدم.
يقدم Matt Gaunt رسومات توضيحية مثالية لهذا التأثير بالصور التالية.
إليك طلب إذن الإعلام الافتراضي الذي لا يوفر أي سياق:

وإليك التفاعل المخصص الذي تمت إضافته قبل إذن الإعلام الافتراضي الموضح أعلاه:

من خلال إضافة تنبيه مخصص قبل التنبيه الافتراضي لنظام التشغيل ، يمكنك وصف الغرض من الإشعار بشكل أكثر وضوحًا للمستخدم. يؤدي هذا إلى زيادة فرصة قيام المستخدم بالاشتراك في إشعارات موقعك.
تمكين المستخدم من الانسحاب من الإخطارات
بالنسبة للمستخدم ، يعد تعطيل إشعارات الدفع الخاصة بالموقع أمرًا مزعجًا للغاية بغض النظر عن الجهاز الذي يستخدمه. لذلك ، فإن منح المستخدم خيار الانسحاب من الرسائل يقطع شوطًا طويلاً من حيث تجربة المستخدم.
إليك مثال من Matt Gaunt حول كيفية تنفيذ وظيفة إلغاء الاشتراك في التعليمات البرمجية وواجهة المستخدم:
أولاً ، حدد pushButton
:
pushButton.addEventListener('click', function() { pushButton.disabled = true; if (isSubscribed) { unsubscribeUser(); } else { subscribeUser(); } });
ثم أضف وظيفة جديدة:
function unsubscribeUser() { swRegistration.pushManager.getSubscription() .then(function(subscription) { if (subscription) { // TODO: Tell application server to delete subscription return subscription.unsubscribe(); } }) .catch(function(error) { console.log('Error unsubscribing', error); }) .then(function() { updateSubscriptionOnServer(null); console.log('User is unsubscribed.'); isSubscribed = false; updateBtn(); }); }
إليك كيفية ظهورها في وحدة التحكم بعد تنفيذ زر الاشتراك بنجاح لتعطيل الإشعارات إذا اختار المستخدم ذلك.

Console.log
لوظيفة تمكين / تعطيل الإشعارات الناجحة بواسطة Matt Gaunt. (معاينة كبيرة)خاتمة
يمكن أن تكون تطبيقات الويب التقدمية (PWA) قوية جدًا لزيادة حركة المرور على موقعك وتوسيع تجربة المستخدم. لزيادة تحسين PWA الخاص بك ، يمكنك استخدام هذه الحلول الحديثة الموضحة في هذه المقالة لتحسين الأداء والوظائف.
أنت أيضًا لا تحتاج إلى تنفيذ كل شيء مرة واحدة. لا تتردد في اختيار الخيارات الأكثر صلة لأن كل من هذه الاقتراحات تساعدك على تعزيز PWA.
مزيد من الموارد
- تدريب تقدمي على تطبيقات الويب من Google
- تطبيقات الويب التقدمية بواسطة web.dev
- تطبيقات الويب التقدمية (PWAs) من Mozilla