تطوير تطبيقات الويب التقدّمية 02.0: دليل البدء السريع في وضع عدم الاتصال بالإنترنت

هذا الدرس التطبيقي حول الترميز هو جزء من الدورة التدريبية "تطوير تطبيقات الويب التقدّمية" التي طوّرها فريق التدريب في "مطوّرو Google". ستستفيد إلى أقصى حدّ من هذه الدورة التدريبية إذا عملت على إكمال دروس الترميز بالتسلسل.

للاطّلاع على التفاصيل الكاملة حول الدورة التدريبية، يُرجى الانتقال إلى نظرة عامة على تطوير تطبيقات الويب التقدّمية.

مقدمة

في هذا التمرين العملي، ستستخدم Lighthouse لتدقيق موقع إلكتروني وفقًا لمعايير تطبيقات الويب التقدّمية (PWA). ستضيف أيضًا وظائف بلا إنترنت باستخدام Service Worker API.

أهداف الدورة التعليمية

  • كيفية تدقيق المواقع الإلكترونية باستخدام Lighthouse
  • كيفية إضافة إمكانات بلا إنترنت إلى تطبيق

ما يتعين عليك معرفته

  • أساسيات HTML وCSS وJavaScript
  • الإلمام بالوعود في ES2015

المتطلبات

  • جهاز كمبيوتر يمكن الوصول إلى واجهة النظام أو المحطة الطرفية فيه
  • الاتصال بالإنترنت
  • متصفّح Chrome (لاستخدام Lighthouse)
  • محرّر نصوص
  • اختياري: Chrome على جهاز Android

نزِّل مستودع pwa-training-labs أو استنسِخه من GitHub وثبِّت إصدار LTS من Node.js، إذا لزم الأمر.

انتقِل إلى الدليل offline-quickstart-lab/app/ وابدأ خادم تطوير محليًا:

cd offline-quickstart-lab/app
npm install
node server.js

يمكنك إيقاف الخادم في أي وقت باستخدام Ctrl-c.

افتح المتصفّح وانتقِل إلى localhost:8081/. من المفترض أن يظهر الموقع الإلكتروني كصفحة ويب بسيطة وثابتة.

ملاحظة: ألغِ تسجيل أي مشغّلات خدمات وامحُ جميع ذاكرات التخزين المؤقت لمشغّلات الخدمات في localhost لكي لا تتداخل مع المختبر. في "أدوات مطوّري البرامج في Chrome"، يمكنك إجراء ذلك من خلال النقر على محو بيانات الموقع الإلكتروني من قسم محو مساحة التخزين في علامة التبويب التطبيق.

افتح المجلد offline-quickstart-lab/app/ في محرِّر النصوص الذي تفضّله. المجلد app/ هو المكان الذي ستنشئ فيه المختبر.

يحتوي هذا المجلد على ما يلي:

  • يحتوي المجلد images/ على صور نموذجية
  • styles/main.css هو ورقة الأنماط الرئيسية
  • index.html هي صفحة HTML الرئيسية للموقع الإلكتروني النموذجي
  • تتتبّع package-lock.json وpackage.json تبعيات التطبيق (التبعيات الوحيدة في هذه الحالة هي لخادم التطوير المحلي).
  • server.js هو خادم تطوير محلي للاختبار
  • service-worker.js هو ملف عامل الخدمة (فارغ حاليًا)

قبل البدء بإجراء تغييرات على الموقع الإلكتروني، لنُجرِ تدقيقًا باستخدام Lighthouse لمعرفة ما يمكن تحسينه.

ارجع إلى التطبيق (في Chrome) وافتح علامة التبويب عمليات التدقيق في أدوات المطوّرين. من المفترض أن يظهر لك رمز Lighthouse وخيارات الإعداد. اختَر "الجهاز الجوّال" في الجهاز، واختَر جميع عمليات التدقيق، واختَر أحد خيارات التقييد، ثم اختَر محو مساحة التخزين:

انقر على تنفيذ عمليات التدقيق. تستغرق عمليات التدقيق بضع لحظات حتى تكتمل.

الشرح

من المفترض أن يظهر لك تقرير يتضمّن النتائج في "أدوات المطوّرين" بعد اكتمال التدقيق. يجب أن يعرض النتائج على النحو التالي (قد لا تكون النتائج متطابقة تمامًا):

ملاحظة: إنّ نتائج Lighthouse هي نتائج تقريبية ويمكن أن تتأثر ببيئتك (على سبيل المثال، إذا كان لديك عدد كبير من نوافذ المتصفّح مفتوحة). قد لا تكون نتائجك مطابقة تمامًا للنتائج المعروضة هنا.

يجب أن يبدو قسم تطبيق الويب التقدّمي على النحو التالي:

يتضمّن التقرير نتائج ومقاييس في خمس فئات:

  • تطبيق ويب تقدّمي
  • الأداء
  • تسهيل الاستخدام
  • أفضل الممارسات
  • تحسين محركات البحث

كما ترى، حصل تطبيقنا على تقييم ضعيف في فئة تطبيقات الويب التقدّمية (PWA). لنحسّن نتيجتنا!

خصِّص بعض الوقت للاطّلاع على قسم "تطبيقات الويب التقدّمية" في التقرير ومعرفة العناصر الناقصة.

تسجيل مشغّل الخدمات

أحد الأخطاء المُدرَجة في التقرير هو عدم تسجيل أي عامل خدمة. لدينا حاليًا ملف فارغ لبرنامج عامل الخدمة في app/service-worker.js.

أضِف النص البرمجي التالي إلى أسفل index.html، قبل علامة الإغلاق </body> مباشرةً:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js')
      .then(reg => {
        console.log('Service worker registered! 😎', reg);
      })
      .catch(err => {
        console.log('😥 Service worker registration failed: ', err);
      });
  });
}
</script>

الشرح

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

التخزين المؤقت المسبق للموارد

من بين حالات الفشل الأخرى المُدرَجة في التقرير أنّ التطبيق لا يستجيب باستخدام رمز الحالة 200 عند عدم الاتصال بالإنترنت. لحلّ هذه المشكلة، علينا تعديل عامل الخدمة.

أضِف الرمز التالي إلى ملف عامل الخدمة (service-worker.js):

const cacheName = 'cache-v1';
const precacheResources = [
  '/',
  'index.html',
  'styles/main.css',
  'images/space1.jpg',
  'images/space2.jpg',
  'images/space3.jpg'
];

self.addEventListener('install', event => {
  console.log('Service worker install event!');
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => {
        return cache.addAll(precacheResources);
      })
  );
});

self.addEventListener('activate', event => {
  console.log('Service worker activate event!');
});

self.addEventListener('fetch', event => {
  console.log('Fetch intercepted for:', event.request.url);
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});

ارجع الآن إلى المتصفّح وأعِد تحميل الموقع الإلكتروني. تحقَّق من وحدة التحكّم للتأكّد من أنّ عامل الخدمة:

  • مسجَّل
  • مثبَّت
  • منشط

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

بعد ذلك، أوقِف خادم التطوير المحلي في سطر الأوامر عن طريق تنفيذ Ctrl + c. أعِد تحميل الموقع الإلكتروني مرة أخرى ولاحظ أنّه يتم تحميله حتى إذا كان الخادم غير متصل بالإنترنت.

ملاحظة: قد يظهر لك خطأ في وحدة التحكّم يشير إلى أنّه يتعذّر جلب عامل الخدمة: An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED. يظهر هذا الخطأ لأنّ المتصفّح لم يتمكّن من جلب نص عامل الخدمة (لأنّ الموقع الإلكتروني غير متصل بالإنترنت)، وهذا أمر متوقّع لأنّه لا يمكننا استخدام عامل الخدمة لتخزين نفسه مؤقتًا. وإلا سيبقى المتصفّح الخاص بالمستخدم عالقًا مع عامل الخدمة نفسه إلى الأبد.

الشرح

بعد أن يتم تسجيل مشغّل الخدمات من خلال نص التسجيل البرمجي في index.html، يحدث حدث install لمشغّل الخدمات. أثناء هذا الحدث، تفتح أداة معالجة الأحداث install ذاكرة تخزين مؤقت مسماة، وتخزّن الملفات المحدّدة باستخدام الطريقة cache.addAll. يُطلق على هذه العملية اسم "التخزين المؤقت المسبق" لأنّها تحدث أثناء الحدث install، وهو عادةً أول مرّة يزور فيها المستخدم موقعك الإلكتروني.

بعد تثبيت مشغّل الخدمات، وإذا لم يكن مشغّل خدمات آخر يتحكّم حاليًا في الصفحة، يتم "تفعيل" مشغّل الخدمات الجديد (يتم تشغيل معالج الحدث activate في مشغّل الخدمات) ويبدأ في التحكّم في الصفحة.

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

يسمح تخزين الموارد مؤقتًا للتطبيق بالعمل بلا إنترنت من خلال تجنُّب طلبات الشبكة. أصبح بإمكان تطبيقنا الآن الاستجابة باستخدام رمز الحالة 200 عند عدم الاتصال بالإنترنت.

ملاحظة: لا يتم استخدام حدث التفعيل لأي غرض آخر غير تسجيل الدخول في هذا المثال. تم تضمين الحدث للمساعدة في تصحيح أخطاء مشاكل مراحل نشاط عامِل الخدمة.

اختياري: يمكنك أيضًا الاطّلاع على الموارد المخزّنة مؤقتًا في علامة التبويب التطبيق ضمن "أدوات المطوّرين" من خلال توسيع القسم مساحة تخزين ذاكرة التخزين المؤقت:

أعِد تشغيل خادم التطوير باستخدام node server.js وأعِد تحميل الموقع الإلكتروني. بعد ذلك، افتح علامة التبويب عمليات التدقيق في "أدوات المطوّرين" مرة أخرى، وأعِد تشغيل عملية التدقيق في Lighthouse من خلال النقر على تدقيق جديد (علامة الجمع في أعلى يسار الصفحة). عند انتهاء التدقيق، من المفترض أن تلاحظ أنّ نتيجة تطبيق الويب التقدّمي قد تحسّنت بشكل كبير، ولكن لا يزال من الممكن تحسينها. سنواصل تحسين نتيجتنا في القسم التالي.

ملاحظة: هذا القسم اختياري لأنّ اختبار بانر تثبيت تطبيق الويب لا يندرج ضمن نطاق المختبر. يمكنك تجربة ذلك بنفسك باستخدام تصحيح الأخطاء عن بُعد.

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

إنشاء ملف بيان

أنشئ ملفًا في app/ باسم manifest.json، وأضِف الرمز التالي:

{
  "name": "Space Missions",
  "short_name": "Space Missions",
  "lang": "en-US",
  "start_url": "/index.html",
  "display": "standalone",
  "theme_color": "#FF9800",
  "background_color": "#FF9800",
  "icons": [
    {
      "src": "images/touch/icon-128x128.png",
      "sizes": "128x128"
    },
    {
      "src": "images/touch/icon-192x192.png",
      "sizes": "192x192"
    },
    {
      "src": "images/touch/icon-256x256.png",
      "sizes": "256x256"
    },
    {
      "src": "images/touch/icon-384x384.png",
      "sizes": "384x384"
    },
    {
      "src": "images/touch/icon-512x512.png",
      "sizes": "512x512"
    }
  ]
}

يتم توفير الصور المُشار إليها في ملف البيان في التطبيق.

بعد ذلك، أضِف رمز HTML التالي إلى أسفل العلامة <head> في index.html:

<link rel="manifest" href="manifest.json">

<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">

ارجع إلى الموقع الإلكتروني. في علامة التبويب التطبيق ضمن "أدوات المطوّرين"، اختَر قسم محو مساحة التخزين، ثم انقر على محو بيانات الموقع الإلكتروني. بعد ذلك، أعِد تحميل الصفحة. اختَر الآن قسم Manifest. من المفترض أن تظهر لك الرموز وخيارات الإعدادات التي تم ضبطها في ملف manifest.json. إذا لم تظهر لك التغييرات، افتح الموقع الإلكتروني في نافذة تصفُّح متخفّي وتحقّق منه مجددًا.

الشرح

يخبر ملف manifest.json المتصفّح بكيفية تصميم وتنسيق بعض الجوانب التقدّمية لتطبيقك، مثل إطار المتصفّح ورمز الشاشة الرئيسية وشاشة البداية. ويمكن أيضًا استخدامها لضبط تطبيق الويب لفتحه في وضع standalone، كما يفعل التطبيق الأصلي (أي خارج المتصفّح).

لا تزال بعض المتصفّحات قيد التطوير في وقت كتابة هذا المقال، وتعمل علامات <meta> على ضبط مجموعة فرعية من هذه الميزات لبعض المتصفّحات التي لا تتوافق معها بشكل كامل بعد.

كان علينا محو بيانات الموقع الإلكتروني لإزالة النسخة القديمة المخزّنة مؤقتًا من index.html (لأنّ تلك النسخة لم تتضمّن رابط البيان). جرِّب إجراء عملية تدقيق أخرى باستخدام Lighthouse واطّلِع على مدى تحسُّن نتيجة تطبيق الويب التقدّمي.

تفعيل طلب التثبيت

الخطوة التالية لتثبيت تطبيقنا هي عرض طلب التثبيت للمستخدمين. كان الإصدار 67 من Chrome يطلب من المستخدمين التثبيت تلقائيًا، ولكن بدءًا من الإصدار 68 من Chrome، يجب تفعيل طلب التثبيت آليًا استجابةً لإجراء من المستخدم.

أضِف زرًا وبانر "تثبيت التطبيق" إلى أعلى index.html (بعد علامة <main> مباشرةً) باستخدام الرمز التالي:

<section id="installBanner" class="banner">
    <button id="installBtn">Install app</button>
</section>

بعد ذلك، صمِّم البانر من خلال إضافة الأنماط التالية إلى styles/main.css:

.banner {
  align-content: center;
  display: none;
  justify-content: center;
  width: 100%;
}

احفظ الملف. أخيرًا، أضِف علامة النص البرمجي التالية إلى index.html:

  <script>
    let deferredPrompt;
    window.addEventListener('beforeinstallprompt', event => {

      // Prevent Chrome 67 and earlier from automatically showing the prompt
      event.preventDefault();

      // Stash the event so it can be triggered later.
      deferredPrompt = event;

      // Attach the install prompt to a user gesture
      document.querySelector('#installBtn').addEventListener('click', event => {

        // Show the prompt
        deferredPrompt.prompt();

        // Wait for the user to respond to the prompt
        deferredPrompt.userChoice
          .then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
              console.log('User accepted the A2HS prompt');
            } else {
              console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
          });
      });

      // Update UI notify the user they can add to home screen
      document.querySelector('#installBanner').style.display = 'flex';
    });
  </script>

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

الشرح

يضيف رمز HTML وCSS بانرًا وزرًا مخفيَين يمكننا استخدامهما للسماح للمستخدمين بتفعيل طلب التثبيت.

بعد تشغيل الحدث beforeinstallprompt، نمنع التجربة التلقائية (التي يطلب فيها الإصدار 67 من Chrome والإصدارات الأقدم من المستخدمين تلقائيًا تثبيت التطبيق) ونسجّل beforeinstallevent في المتغيّر العام deferredPrompt. بعد ذلك، يتم ضبط الزر "تثبيت التطبيق" لعرض الطلب باستخدام طريقة beforeinstallevent's prompt(). بعد أن يتّخذ المستخدم قرارًا (بالتثبيت أو عدمه)، يتم تنفيذ الوعد userChoice وفقًا لقرار المستخدم (outcome). وأخيرًا، نعرض زر التثبيت بعد أن يصبح كل شيء جاهزًا.

لقد تعلّمت كيفية تدقيق المواقع الإلكترونية باستخدام Lighthouse، وكيفية تنفيذ أساسيات وظيفة العمل بلا إنترنت. إذا أكملت الأقسام الاختيارية، تكون قد تعرّفت أيضًا على كيفية تثبيت تطبيقات الويب على الشاشة الرئيسية.

مزيد من الموارد

‫Lighthouse هي أداة مفتوحة المصدر. يمكنك إنشاء نسخة من الرمز وإضافة اختباراتك الخاصة وتسجيل الأخطاء. تتوفّر Lighthouse أيضًا كأداة سطر أوامر يمكن دمجها مع عمليات الإنشاء.

للاطّلاع على جميع دروس البرمجة في دورة تدريب تطبيقات الويب التقدّمية، راجِع درس البرمجة الترحيبي للدورة التدريبية.