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

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

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

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

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

الخلفية

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

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

قبل التعمّق في عملية التطوير، دعونا نتعرّف على سبب جاذبية إنشاء تطبيق ويب تقدّمي خيارًا لهذا المشروع وهو:

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

إطار العمل

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

المعلومات التي تعلّمناها عن مشغِّلي الخدمات

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

يتم إنشاؤه إذا استطعت

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

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

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

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

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

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

  1. لدى المستخدم تطبيق ويب يستخدِم الإصدار IndexedDB (IDB) N.
  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، لم يعد هذا الحل ضروريًا ضروريًا للمتصفحات التي تدعمه، حيث إنه يوفر إمكانية الوصول غير المتزامن إلى ملفات تعريف ارتباط المتصفح ويمكن استخدامها مباشرةً من قِبل عامل الخدمة.

المشاكل التي يواجهها مشغّلو الخدمات الذين لم يتم إنشاؤهم

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

يتمثل نمط تطبيق الويب التقدّمي الشائع في أن يثبت عامل الخدمات جميع ملفات التطبيق الثابتة أثناء مرحلة install، التي تتيح للعملاء النقر على ذاكرة التخزين المؤقت في Cache Storage API مباشرةً للجميع الزيارات اللاحقة . لا يتم تثبيت مشغِّلي الخدمات إلا عندما يكتشف المتصفِّح أن الخدمة حدوث تغييرات في النص البرمجي للمشغِّل بطريقة ما، لذا كان علينا التأكد من أن ملف النص البرمجي لمشغِّل الخدمات نفسه تغيرت بطريقة ما عند تغيير ملف مخبأ. وقد تم ذلك يدويًا من خلال تضمين تجزئة مجموعة ملفات موارد ثابتة في النص البرمجي لمشغِّل الخدمات، لذلك ينتج كل إصدار غلاف لمشغل الخدمات. مثل مكتبات مشغّل الخدمات يعمل 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، انتقل إلى الملفات الشخصية للمؤلفين لمعرفة كيفية التواصل معنا: