إنشاء تطبيق ويب تقدّمي (PWA) في Google، الجزء 1

ما تعلّمه فريق تطبيق "قصص" عن عاملي الخدمة أثناء تطوير تطبيق ويب تقدّمي (PWA)

دوغلاس باركر
دوغلاس باركر
جويل رايلي
جويل رايلي
ديكلا كوهين
ديكلا كوهين

وهذه هي الخطوة الأولى من سلسلة من مشاركات المدونات حول الدروس التي تعلمها فريق Google Bulletin أثناء إنشاء تطبيق ويب تقدّمي (PWA) واجهة خارجية. سنشارك في هذه المشاركات بعض التحديات التي واجهناها، والأساليب التي اتبعناها للتغلب عليها، بالإضافة إلى نصائح عامة لتجنب الصعوبات. لا يعني ذلك بأي حال من الأحوال نظرة عامة كاملة على تطبيقات الويب التقدّمية (PWA). والهدف من ذلك هو مشاركة الدروس المستفادة من تجربة فريقنا.

في هذه المشاركة الأولى، سنتناول بعض المعلومات الأساسية أولاً ثم نتعمق في جميع الأشياء التي تعلمناها عن عمال الخدمة.

الخلفية

كان تطبيق "قصص" قيد التطوير النشط من منتصف عام 2017 إلى منتصف عام 2019.

أسباب اختيارنا إنشاء تطبيق ويب تقدّمي (PWA)

قبل أن ندخل في عملية التطوير، دعنا نفحص لماذا كان إنشاء تطبيق الويب التقدّمي (PWA) خيارًا جذابًا لهذا المشروع:

  • القدرة على التكرار بسرعة: يحظى تطبيق "قصص" بأهمية خاصة لأنه سيتم تجربة تطبيق "قصص" في أسواق متعددة.
  • قاعدة رموز واحدة: تم تقسيم مستخدمينا بالتساوي تقريبًا بين Android وiOS. يعني تطبيق الويب التقدّمي (PWA) أنه يمكننا إنشاء تطبيق ويب واحد يعمل على كلا النظامين الأساسيين. أدى ذلك إلى زيادة سرعة الفريق وتأثيره.
  • يتم تعديل البيانات بسرعة ومستقلة عن سلوك المستخدم. يمكن لتطبيقات الويب التقدّمية (PWA) تلقائيًا تحديث التطبيقات، ما يقلّل من عدد العملاء غير المهمين. تمكنا من إجراء تغييرات جذرية في الخلفية خلال فترة قصيرة جدًا من وقت الترحيل للعملاء.
  • سهولة الدمج مع تطبيقات الطرف الأول والتطبيقات التابعة لجهات خارجية كانت عمليات الدمج هذه من متطلبات التطبيق. ومع تطبيق PWA، غالبًا ما يعني ذلك فتح عنوان URL.
  • تم التغلّب على الصعوبات التي تتسبّب في تثبيت التطبيق.

إطار العمل الخاص بنا

بالنسبة إلى تطبيق "Bulletin"، استخدمنا بوليمر، ولكن سيعمل أي إطار عمل حديث ومتوافق بشكل جيد.

ما تعلمناه عن عاملي الخدمة

لا يمكنك استخدام تطبيق ويب تقدّمي (PWA) بدون عامل خدمة. يمنحك عاملو الخدمة الكثير من الإمكانيات، مثل استراتيجيات التخزين المؤقت المتقدمة، والإمكانيات بلا اتصال بالإنترنت، والمزامنة في الخلفية، وغير ذلك. وعلى الرغم من أن عاملي الخدمة يضيفون بعض التعقيد، فقد وجدنا أن مزاياهم تفوق التعقيد الإضافي.

يجب إنشاؤها إذا أمكن

تجنَّب كتابة نص برمجي لمشغّل الخدمات يدويًا. يتطلّب مشغّلو الخدمات كتابةً يدويًا إدارة الموارد المخزّنة مؤقتًا وإعادة منطق الكتابة، وهو أمر شائع في معظم مكتبات مشغّلي الخدمات، مثل Workbox.

مع ذلك، لم نتمكّن من استخدام مكتبة لإنشاء مشغّل الخدمات وإدارته، وذلك بسبب حزمة التكنولوجيا الداخلية. ستعكس الدروس المستفادة أدناه ذلك في بعض الأحيان. للحصول على المزيد من المعلومات، انتقِل إلى الصعوبات التي قد يواجهها عمال الخدمة التي لم يتم إنشاؤها.

بعض المكتبات غير متوافقة مع مشغّلي الخدمات

تضع بعض مكتبات JavaScript افتراضات لا تعمل على النحو المتوقّع عند تشغيلها من قِبل مشغّل خدمات. مثلاً، بافتراض توفُّر window أو document، أو استخدام واجهة برمجة تطبيقات غير متاحة لمشغِّلي الخدمات (XMLHttpRequest أو مساحة التخزين المحلية أو غير ذلك). تأكد من أن أي مكتبات مهمة تحتاجها للتطبيق متوافقة مع عامل الخدمة. بالنسبة إلى تطبيق الويب التقدّمي هذا تحديدًا، أردنا استخدام gapi.js للمصادقة، ولكن تعذّر ذلك لأنّه لم يكن متوافقًا مع مشغّلي الخدمة. وعلى مؤلفي المكتبة أيضًا تقليل أو إزالة الافتراضات غير الضرورية حول سياق JavaScript متى أمكن ذلك لدعم حالات استخدام مشغّل الخدمة، مثل تجنُّب واجهات برمجة التطبيقات غير المتوافقة مع مشغّل الخدمات وتجنُّب الحالة العامة.

تجنُّب الوصول إلى IndexedDB أثناء عملية الإعداد

لا تقرأ IndexedDB عند تهيئة النص البرمجي لمشغِّل الخدمات، وإلّا قد تواجهك في هذا الموقف غير المرغوب فيه:

  1. يمتلك المستخدم تطبيق ويب بالإصدار N من قاعدة البيانات المفهرسة (IDB).
  2. تم إطلاق تطبيق ويب جديد بإصدار N+1 من IDB
  3. يزور المستخدم تطبيق الويب التقدّمي (PWA)، ما يؤدّي إلى بدء تنزيل مشغّل الخدمات الجديد
  4. يقرأ مشغّل الخدمات الجديد من IDB قبل تسجيل معالج أحداث install، ما يؤدي إلى بدء دورة ترقية IDB للانتقال من N إلى N+1.
  5. نظرًا لأن المستخدم لديه عميل قديم يحمل الإصدار N، يتم تعليق عملية ترقية عامل الخدمة لأنّ الاتصالات النشطة لا تزال مفتوحة للإصدار القديم من قاعدة البيانات.
  6. عامل الخدمة يتوقّف عن العمل ولا يتم تثبيته مطلقًا

وفي هذه الحالة، تم إبطال صلاحية ذاكرة التخزين المؤقت عند تثبيت مشغّل الخدمات، وبالتالي إذا لم يتم تثبيت التطبيق المحدَّث من قبل عامل الخدمة، يعني ذلك أنّ المستخدمين لم يتلقّوا التطبيق المحدَّث.

المرونة

على الرغم من أنّ النصوص البرمجية لمشغِّلي الخدمات تعمل في الخلفية، يمكن أيضًا إنهاؤها في أي وقت، حتى حتى عندما تكون في منتصف عمليات الإدخال والإخراج (الشبكة أو IDB أو غير ذلك). ينبغي أن تكون أي عملية طويلة الأمد قابلة للاستئناف في أي وقت.

وفي حالة عملية المزامنة التي حمّلت ملفات كبيرة إلى الخادم وحفظها في IDB، كان الحل الذي كنّا نقدّمه لعمليات التحميل الجزئية التي تمت مقاطعتها هو الاستفادة من نظام التخزين القابل للاستئناف في مكتبة التحميل الداخلية، وحفظ عنوان URL القابل للاستئناف للتحميل في IDB قبل التحميل واستخدام عنوان URL هذا لاستئناف عملية التحميل إذا لم يكتمل التحميل في المرة الأولى. وكذلك قبل أي عملية إدخال/إخراج تستغرق فترة طويلة، تم حفظ الولاية في IDB للإشارة إلى مكان كل سجل في العملية.

لا تعتمد على الحالة العامة

نظرًا لوجود عمال الخدمة في سياق مختلف، فإن العديد من الرموز التي قد تتوقع وجودها غير موجودة. تم تشغيل الكثير من الرموز في كلّ من سياق window وسياق مشغّل الخدمات (مثل التسجيل والعلامات والمزامنة وما إلى ذلك). يجب أن يكون الرمز دفاعيًا بشأن الخدمات التي يستخدمها، مثل التخزين المحلي أو ملفات تعريف الارتباط. يمكنك استخدام globalThis للإشارة إلى الكائن العمومي بطريقة تعمل في جميع السياقات. استخدِم أيضًا البيانات المخزّنة في المتغيرات العمومية باعتدال، لأنّه ليس هناك ما يضمن وقت إنهاء النص البرمجي وإخراج الحالة.

التنمية المحلية

يعد التخزين المؤقت للموارد محليًا من المكونات الرئيسية للعاملين في الخدمة. مع ذلك، أثناء التطوير، يكون هذا عكس ما تريده بالضبط، خاصةً عند إجراء التحديثات بطريقة بطيئة. ستظل بحاجة إلى تثبيت عامل الخادم حتى تتمكن من تصحيح الأخطاء المرتبطة به أو العمل مع واجهات برمجة التطبيقات الأخرى مثل المزامنة في الخلفية أو الإشعارات. في Chrome، يمكنك تحقيق ذلك من خلال "أدوات مطوري البرامج في Chrome" من خلال تفعيل مربّع الاختيار تجاوز الشبكة (لوحة التطبيق > جزء عاملو الخدمة) بالإضافة إلى تفعيل مربّع الاختيار إيقاف ذاكرة التخزين المؤقت في لوحة الشبكة لإيقاف ذاكرة التخزين المؤقت للذاكرة أيضًا. ولتغطية المزيد من المتصفحات، اخترنا حلاً مختلفًا من خلال تضمين علامة لإيقاف التخزين المؤقت في مشغّل الخدمات الذي يتم تفعيله تلقائيًا على إصدارات المطوّرين. ويضمن ذلك حصول المطوّرين دائمًا على أحدث التغييرات بدون أي مشاكل في التخزين المؤقت. من المهم تضمين العنوان Cache-Control: no-cache أيضًا لمنع المتصفِّح من التخزين المؤقت لأي مواد عرض.

منارة

توفِّر Lighthouse عددًا من أدوات تصحيح الأخطاء المفيدة لتطبيقات الويب التقدّمية (PWA). يفحص التطبيق موقعًا إلكترونيًا وينشئ تقارير تتناول تطبيقات الويب التقدّمية (PWA) والأداء وتسهيل الاستخدام وتحسين محركات البحث وأفضل الممارسات الأخرى. نقترح عليك تشغيل Lighthouse على عملية الدمج المستمر لتنبيهك في حال عدم استيفاء أحد المعايير لتصبح تطبيق ويب تقدّمي (PWA). لقد حدث هذا لنا في الواقع ذات مرة، حيث لم يكن عامل الخدمة يقوم بتثبيت ولم ندرك ذلك قبل دفع الإنتاج. إنّ استخدام Lighthouse كجزء من CI كان سيمنع ذلك.

استخدام التسليم المستمر

وبما أنّه يمكن للعاملين إجراء التحديث تلقائيًا، لا يستطيع المستخدمون الحد من الترقيات. ويقلل ذلك بشكل كبير من عدد العملاء غير المهمين. عندما فتح المستخدم تطبيقنا، كان عامل الخدمة يخدم العميل القديم بينما يقوم بتنزيل البرنامج الجديد بطريقة كسولة. بمجرد تنزيل البرنامج الجديد، ستطلب من المستخدم إعادة تحميل الصفحة للوصول إلى الميزات الجديدة. حتى لو تجاهل المستخدم هذا الطلب، في المرة التالية التي يقوم فيها بتحديث الصفحة، سيتلقى الإصدار الجديد من العميل. ونتيجة لذلك، يصعب على المستخدم رفض التحديثات بنفس الطريقة التي يمكنه بها رفض التحديثات على تطبيقات iOS/Android.

تمكّنا من إجراء تغييرات جذرية في الخلفية خلال مدة زمنية قصيرة جدًا لعملية نقل بيانات للعملاء. عادةً ما نمنح المستخدمين شهرًا للتحديث إلى العملاء الجدد قبل إجراء تغييرات جذرية. ونظرًا لأن التطبيق سيظل يعمل عندما يكون قديمًا، فقد كان من الممكن للعملاء الأكبر سنًا وجوده في البرية إذا لم يفتح المستخدم التطبيق لفترة طويلة. أمّا في نظام iOS، فيتم طرد مشغِّلي الخدمات بعد أسبوعين، لذلك لا يحدث ذلك. بالنسبة إلى Android، يمكن التخفيف من حدة هذه المشكلة من خلال عدم عرض المحتوى القديم أو انتهاء صلاحيته يدويًا بعد بضعة أسابيع. من الناحية العملية، لم نواجه أبدًا مشكلات من العملاء القدامى. يعود مدى صرامة فريق معيّن إلى حالة الاستخدام الخاصة به، لكن تطبيقات الويب التقدّمية (PWA) توفّر مرونة أكبر بكثير من تطبيقات iOS/Android.

الحصول على قيم ملفات تعريف الارتباط في عامل خدمات

وفي بعض الأحيان، يكون من الضروري الوصول إلى قيم ملفات تعريف الارتباط في سياق مشغّل الخدمات. في حالتنا، نحتاج إلى الوصول إلى قيم ملفات تعريف الارتباط لإنشاء رمز مميّز لمصادقة طلبات واجهة برمجة التطبيقات الخاصة بالطرف الأول. في مشغّل الخدمات، لا تتوفر واجهات برمجة تطبيقات متزامنة، مثل document.cookies. يمكنك متى شئت إرسال رسالة إلى العملاء النشطين (الإطارين) من مشغّل الخدمات لطلب قيم ملفات تعريف الارتباط، مع العلم أنّه من الممكن أن يعمل عامل الخدمة في الخلفية بدون توفُّر أي برامج تعمل بنوافذ، كما هو الحال أثناء المزامنة في الخلفية. ولحل هذه المشكلة، أنشأنا نقطة نهاية على خادم الواجهة الأمامية لدينا والتي أعادت ببساطة قيمة ملف تعريف الارتباط إلى العميل. وقدَّم عامل الخدمة طلب شبكة إلى نقطة النهاية هذه وقرأ الرد للحصول على قيم ملف تعريف الارتباط.

مع إطلاق واجهة برمجة التطبيقات Cookie Store API، من المفترض ألّا يكون هذا الحل البديل ضروريًا للمتصفّحات المتوافقة معه، لأنّه يوفّر إمكانية الوصول غير المتزامن إلى ملفات تعريف ارتباط المتصفّح ويمكن استخدامه مباشرةً من قِبل مشغّل الخدمة.

الصعوبات التي تعترض عاملي الخدمات الذين لم يتم إنشاؤهم

التأكّد من تغيير النص البرمجي لمشغِّل الخدمات في حال تغيّر أي ملف ثابت مُخزَّن مؤقتًا

أحد الأنماط الشائعة لتطبيق الويب التقدّمي (PWA) هو قيام مشغّل الخدمات بتثبيت جميع ملفات التطبيقات الثابتة خلال مرحلة install، ما يتيح للعملاء الوصول إلى ذاكرة التخزين المؤقت لواجهة برمجة التطبيقات Cache Storage API مباشرةً لجميع الزيارات اللاحقة . لا يتم تثبيت مشغّلي الخدمات إلا عندما يكتشف المتصفِّح أنّ النص البرمجي لمشغّل الخدمات قد تغيّر بطريقة ما، لذا كان علينا التأكّد من أنّ ملف النص البرمجي لمشغِّل الخدمات نفسه قد تغيّر بطريقة ما عند تغيير ملف مخزَّن مؤقتًا. وقد تم إجراء ذلك يدويًا من خلال تضمين تجزئة من مجموعة ملفات الموارد الثابتة في النص البرمجي لمشغِّل الخدمات، بحيث أنتج كل إصدار ملف JavaScript مميّزًا لمشغِّل الخدمات. وتعمل مكتبات مشغّلي الخدمات مثل Workbox على تنفيذ هذه العملية تلقائيًا بالنيابة عنك.

اختبار الوحدات

تعمل واجهات برمجة التطبيقات لمشغِّل الخدمات من خلال إضافة أدوات معالجة الأحداث إلى العنصر العمومي. مثال:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

قد تصعب هذه التجربة لأنّك تحتاج إلى محاكاة عامل تشغيل الحدث، وهو كائن الحدث، وانتظار معاودة الاتصال بـ respondWith() ثم انتظار الوعد قبل التأكيد في النهاية على النتيجة. تتمثل إحدى الطرق الأسهل لهيكلة ذلك في تفويض جميع عمليات التنفيذ إلى ملف آخر، والذي يسهل اختباره.

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

بسبب الصعوبات في اختبار النص البرمجي لمشغّل الخدمات، حافظنا على هذا النص البرمجي لمشغّل الخدمات الأساسي كمثال بسيط قدر الإمكان، ما أدّى إلى تقسيم معظم عمليات التنفيذ إلى وحدات أخرى. ولأن تلك الملفات كانت مجرد وحدات JavaScript عادية، يمكن اختبارها بسهولة أكبر باستخدام مكتبات الاختبار القياسية.

ترقَّب الجزء 2 و3 من

سنتحدّث في الجزءَين الثاني والثالث من هذه السلسلة عن إدارة الوسائط والمشاكل المتعلّقة بنظام التشغيل iOS. إذا كنت تريد أن تطلب منا المزيد من المعلومات حول إنشاء تطبيق ويب تقدّمي (PWA) في Google، يُرجى الانتقال إلى الملفات الشخصية للمؤلفين للتعرّف على كيفية التواصل معنا: