بناء PWA مع Webpack و Workbox
نشرت: 2022-03-10تطبيق الويب التقدمي (PWA) هو موقع يستخدم التكنولوجيا الحديثة لتقديم تجارب شبيهة بالتطبيقات على الويب. إنه مصطلح شامل للتقنيات الجديدة مثل "بيان تطبيق الويب" و "عامل الخدمة" والمزيد. عند ضمها معًا ، تتيح لك هذه التقنيات تقديم تجارب مستخدم سريعة وجذابة مع موقع الويب الخاص بك.
هذه المقالة عبارة عن برنامج تعليمي خطوة بخطوة لإضافة عامل خدمة إلى موقع ويب موجود من صفحة واحدة. سيسمح لك عامل الخدمة بجعل موقع الويب الخاص بك يعمل دون اتصال بالإنترنت مع إخطار المستخدمين لديك بتحديثات موقعك. يرجى ملاحظة أن هذا يعتمد على مشروع صغير مرفق مع Webpack ، لذلك سنستخدم المكون الإضافي Workbox Webpack (Workbox v4).
يعد استخدام أداة لإنشاء عامل الخدمة هو الأسلوب الموصى به لأنه يتيح لك إدارة ذاكرة التخزين المؤقت بكفاءة. سنستخدم Workbox - مجموعة من المكتبات التي تجعل من السهل إنشاء رمز عامل الخدمة الخاص بك - لإنشاء عامل الخدمة لدينا في هذا البرنامج التعليمي.
بناءً على مشروعك ، يمكنك استخدام Workbox بثلاث طرق مختلفة:
- تتوفر واجهة سطر أوامر تتيح لك دمج صندوق العمل في أي تطبيق لديك ؛
- تتوفر وحدة Node.js والتي تتيح لك دمج صندوق العمل في أي أداة بناء Node مثل gulp أو grunt ؛
- يتوفر ملحق webpack الذي يتيح لك التكامل بسهولة مع مشروع تم إنشاؤه باستخدام Webpack.
Webpack عبارة عن وحدة تجميع. للتبسيط ، يمكنك التفكير في الأمر على أنه أداة تدير تبعيات JavaScript الخاصة بك. يسمح لك باستيراد كود JavaScript من المكتبات وتجميع JavaScript معًا في ملف واحد أو عدة ملفات.
للبدء ، قم باستنساخ المستودع التالي على جهاز الكمبيوتر الخاص بك:
git clone [email protected]:jadjoubran/workbox-tutorial-v4.git cd workbox-tutorial-v4 npm install npm run dev
بعد ذلك ، انتقل إلى https://localhost:8080/
. يجب أن تكون قادرًا على رؤية تطبيق العملة الذي سنستخدمه خلال هذا البرنامج التعليمي:
ابدأ بـ App Shell
غلاف التطبيق (أو "غلاف التطبيق") هو نمط مستوحى من التطبيقات الأصلية. سيساعد ذلك في منح تطبيقك مظهرًا أصليًا أكثر. إنه ببساطة يوفر للتطبيق تخطيطًا وبنية بدون أي بيانات - شاشة انتقالية تهدف إلى تحسين تجربة تحميل تطبيق الويب الخاص بك.
فيما يلي بعض الأمثلة على أغلفة التطبيقات من التطبيقات الأصلية:
وإليك أمثلة على أغلفة التطبيقات من PWAs:
يحب المستخدمون تجربة تحميل أغلفة التطبيقات لأنهم يحتقرون الشاشات الفارغة. شاشة فارغة تجعل المستخدم يشعر أن موقع الويب لا يتم تحميله. يجعلهم يشعرون كما لو أن موقع الويب قد توقف.
تحاول أصداف التطبيقات رسم بنية التنقل للتطبيق في أسرع وقت ممكن ، مثل شريط التنقل وشريط علامات التبويب بالإضافة إلى أداة تحميل تشير إلى أنه يتم تحميل المحتوى الذي طلبته.
إذًا ، كيف تُنشئ تطبيق شل؟
يعطي نمط غلاف التطبيق الأولوية لتحميل HTML و CSS و JavaScript التي سيتم عرضها أولاً. هذا يعني أننا بحاجة إلى إعطاء هذه الموارد الأولوية الكاملة ، ومن ثم يتعين عليك تضمين تلك الأصول. لذلك لإنشاء غلاف تطبيق ، عليك ببساطة تضمين HTML و CSS و JavaScript المسؤولة عن غلاف التطبيق. بالطبع ، لا يجب تضمين كل شيء ، بل يجب أن تظل في حدود حوالي 30 إلى 40 كيلو بايت إجمالاً.
يمكنك رؤية غلاف التطبيق المضمن في index.html . يمكنك فحص الكود المصدري عن طريق التحقق من ملف index.html ، ويمكنك معاينته في المتصفح عن طريق حذف عنصر <main>
في أدوات dev:
هل يعمل دون اتصال؟
لنحاكي عدم الاتصال بالإنترنت! افتح DevTools ، وانتقل إلى علامة تبويب الشبكة وحدد مربع الاختيار "غير متصل". عندما تعيد تحميل الصفحة ، سترى أننا سنحصل على الصفحة غير المتصلة بالمتصفح.
هذا لأن الطلب الأولي لـ /
(الذي سيحمل ملف index.html ) سيفشل لأن الإنترنت غير متصل بالإنترنت. الطريقة الوحيدة للتعافي من فشل هذا الطلب هي الاستعانة بعامل خدمة.
دعنا نتخيل الطلب بدون عامل خدمة:
عامل الخدمة هو وكيل شبكة قابل للبرمجة ، مما يعني أنه يقع بين صفحة الويب الخاصة بك والإنترنت. يتيح لك هذا التحكم في طلبات الشبكة الواردة والصادرة.
هذا مفيد لأنه يمكننا الآن إعادة توجيه هذا الطلب الفاشل إلى ذاكرة التخزين المؤقت (على افتراض أن لدينا المحتوى في ذاكرة التخزين المؤقت).
عامل الخدمة هو أيضًا نوع من عامل الويب ، مما يعني أنه يعمل بشكل منفصل عن صفحتك الرئيسية ولا يمكنه الوصول إلى كائن window
أو document
.
Precache The App Shell
من أجل جعل تطبيقنا يعمل في وضع عدم الاتصال ، سنبدأ بإدخال هيكل التطبيق مسبقًا.
لنبدأ بتثبيت المكون الإضافي Webpack Workbox:
npm install --save-dev workbox-webpack-plugin
ثم سنفتح ملف index.js الخاص بنا ونسجل عامل الخدمة:
if ("serviceWorker" in navigator){ window.addEventListener("load", () => { navigator.serviceWorker.register("/sw.js"); }) }
بعد ذلك ، افتح ملف webpack.config.js ودعنا نقوم بتهيئة المكون الإضافي Workbox webpack:
//add at the top const WorkboxWebpackPlugin = require("workbox-webpack-plugin"); //add inside the plugins array: plugins: [ … , new WorkboxWebpackPlugin.InjectManifest({ swSrc: "./src/src-sw.js", swDest: "sw.js" }) ]
سيؤدي هذا إلى توجيه Workbox لاستخدام ملف ./src/src-sw.js الخاص بنا كقاعدة. سيُطلق على الملف الذي تم إنشاؤه اسم sw.js وسيكون في مجلد dist
.
ثم قم بإنشاء ملف ./src/src-sw.js بالمستوى الجذر واكتب ما يلي بداخله:
workbox.precaching.precacheAndRoute(self.__precacheManifest);
ملاحظة : سيتم استيراد المتغير self.__precacheManifest
من ملف سيتم إنشاؤه ديناميكيًا بواسطة Workbox.
أنت الآن جاهز لإنشاء الكود الخاص بك باستخدام npm run build
بإنشاء ملفين داخل مجلد dist
:
- بيان مخبأ .66cf63077c7e4a70ba741ee9e6a8da29.js
- sw.js
يستورد sw.js مربع العمل من CDN بالإضافة إلى البيان المؤقت. [chunkhash] .js .
//precache-manifest.[chunkhash].js file self.__precacheManifest = (self.__precacheManifest || []).concat([ "revision": "ba8f7488757693a5a5b1e712ac29cc28", "url": "index.html" }, "url": "main.49467c51ac5e0cb2b58e.js" ]);
يسرد البيان المؤقت أسماء الملفات التي تمت معالجتها بواسطة حزمة الويب والتي ينتهي بها الأمر في مجلد dist
الخاص بك. سنستخدم هذه الملفات في تخزينها مسبقًا في المتصفح. هذا يعني أنه عندما يتم تحميل موقع الويب الخاص بك في المرة الأولى ويسجل عامل الخدمة ، فإنه سيخزن هذه الأصول مؤقتًا بحيث يمكن استخدامها في المرة القادمة.
يمكنك أيضًا ملاحظة أن بعض الإدخالات بها "مراجعة" في حين أن البعض الآخر لا يحتوي على ذلك. هذا لأنه يمكن أحيانًا الاستدلال على المراجعة من chunkhash من اسم الملف. على سبيل المثال ، دعنا نلقي نظرة فاحصة على اسم الملف main.49467c51ac5e0cb2b58e.js . لديها نسخة في اسم الملف ، وهو chunkhash 49467c51ac5e0cb2b58e .
يسمح هذا لـ Workbox بفهم متى تتغير ملفاتك بحيث يقوم فقط بتنظيف أو تحديث الملفات التي تم تغييرها ، بدلاً من تفريغ كل ذاكرة التخزين المؤقت في كل مرة تنشر فيها إصدارًا جديدًا من عامل الخدمة الخاص بك.
في المرة الأولى التي تقوم فيها بتحميل الصفحة ، سيقوم عامل الخدمة بالتثبيت. يمكنك رؤية ذلك في DevTools. أولاً ، يُطلب ملف sw.js الذي يطلب بعد ذلك جميع الملفات الأخرى. يتم تمييزها بوضوح برمز الترس.
لذلك سيتم تهيئة Workbox وسيقوم مؤقتًا بجميع الملفات الموجودة في البيان المسبق. من المهم أن تتحقق جيدًا من عدم وجود أي ملفات غير ضرورية في ملف البيان المؤقت مثل ملفات الخريطة أو الملفات التي ليست جزءًا من غلاف التطبيق.
في علامة تبويب الشبكة ، يمكننا رؤية الطلبات الواردة من عامل الخدمة. والآن إذا حاولت عدم الاتصال بالإنترنت ، فسيكون هيكل التطبيق مُسبقًا مسبقًا لذا فهو يعمل حتى لو كنا في وضع عدم الاتصال!
طرق التخزين المؤقت الديناميكية
هل لاحظت أنه عندما توقفنا عن الاتصال بالإنترنت ، فإن هيكل التطبيق يعمل ولكن لا يعمل مع بياناتنا؟ ذلك لأن استدعاءات واجهة برمجة التطبيقات هذه ليست جزءًا من هيكل التطبيق المُجهز مسبقًا . في حالة عدم وجود اتصال بالإنترنت ، ستفشل هذه الطلبات ولن يتمكن المستخدم من رؤية معلومات العملة.
ومع ذلك ، لا يمكن إجراء هذه الطلبات مسبقًا لأن قيمتها تأتي من واجهة برمجة التطبيقات. علاوة على ذلك ، عندما تبدأ في الحصول على صفحات متعددة ، فأنت لا تريد تخزين جميع طلبات واجهة برمجة التطبيقات مؤقتًا دفعة واحدة. بدلاً من ذلك ، تريد تخزينها مؤقتًا عندما يزور المستخدم تلك الصفحة.
نحن نسمي هذه "البيانات الديناميكية". غالبًا ما تتضمن استدعاءات واجهة برمجة التطبيقات بالإضافة إلى الصور والأصول الأخرى التي يتم طلبها عندما يقوم المستخدم بإجراء معين على موقع الويب الخاص بك (على سبيل المثال عندما يتصفحون صفحة جديدة).
يمكنك تخزينها مؤقتًا باستخدام وحدة التوجيه الخاصة بـ Workbox. إليك الطريقة:
//add in src/src-sw.js workbox.routing.registerRoute( /https:\/\/api\.exchangeratesapi\.io\/latest/, new workbox.strategies.NetworkFirst({ cacheName: "currencies", plugins: [ new workbox.expiration.Plugin({ maxAgeSeconds: 10 * 60 // 10 minutes }) ] }) );
سيؤدي هذا إلى إعداد التخزين المؤقت الديناميكي لأي عنوان URL لطلب يطابق عنوان URL https://api.exchangeratesapi.io/latest
.
تسمى استراتيجية التخزين المؤقت التي استخدمناها هنا NetworkFirst
؛ هناك نوعان آخران يتم استخدامهما غالبًا:
-
CacheFirst
-
StaleWhileRevalidate
CacheFirst
عنه في ذاكرة التخزين المؤقت أولاً. إذا لم يتم العثور عليه ، فسيحصل عليه من الشبكة. StaleWhileRevalidate
إلى الشبكة وذاكرة التخزين المؤقت في نفس الوقت. أعد استجابة ذاكرة التخزين المؤقت إلى الصفحة (أثناء وجودها في الخلفية) ، ستستخدم استجابة الشبكة الجديدة لتحديث ذاكرة التخزين المؤقت في المرة التالية التي يتم استخدامها فيها.
بالنسبة لحالة الاستخدام الخاصة بنا ، كان علينا استخدام NetworkFirst
لأننا نتعامل مع أسعار العملات التي تتغير كثيرًا. ومع ذلك ، عندما ينتقل المستخدم إلى وضع عدم الاتصال ، يمكننا على الأقل عرض الأسعار كما كانت قبل 10 دقائق - ولهذا السبب استخدمنا المكون الإضافي لانتهاء الصلاحية مع تعيين maxAgeSeconds
على 10 * 60
ثانية.
إدارة تحديثات التطبيق
في كل مرة يقوم المستخدم بتحميل صفحتك ، سيقوم المتصفح بتشغيل الكود navigator.serviceWorker.register
بالرغم من أن عامل الخدمة مثبت بالفعل ويعمل. يسمح هذا للمتصفح باكتشاف ما إذا كان هناك إصدار جديد لعامل الخدمة. عندما يلاحظ المستعرض أن الملف لم يتغير ، فإنه يتخطى فقط مكالمة التسجيل. بمجرد تغيير هذا الملف ، يفهم المتصفح أن هناك إصدارًا جديدًا لعامل الخدمة ، وبالتالي يقوم بتثبيت عامل الخدمة الجديد بالتوازي مع عامل الخدمة قيد التشغيل حاليًا .
ومع ذلك ، فإنه يتوقف مؤقتًا في مرحلة installed/waiting
لأنه لا يمكن تنشيط سوى عامل خدمة واحد في نفس الوقت.
فقط عندما يتم إغلاق جميع نوافذ المتصفح التي يتحكم فيها عامل الخدمة السابق ، يصبح من الآمن تنشيط عامل الخدمة الجديد.
يمكنك أيضًا التحكم في ذلك يدويًا عن طريق استدعاء skipWaiting()
(أو self.skipWaiting()
لأن self
هو سياق التنفيذ العام في عامل الخدمة). ومع ذلك ، في معظم الأوقات ، يجب ألا تفعل ذلك إلا بعد سؤال المستخدم عما إذا كان يريد الحصول على آخر تحديث.
لحسن الحظ ، تساعدنا workbox-window
على تحقيق ذلك. إنها مكتبة نافذة جديدة تم تقديمها في Workbox v4 والتي تهدف إلى تبسيط المهام الشائعة على جانب النافذة.
لنبدأ بتثبيته بما يلي:
npm install workbox-window
بعد ذلك ، قم باستيراد Workbox
في الجزء العلوي من ملف index.js :
import { Workbox } from "workbox-window";
ثم سنقوم باستبدال رمز التسجيل الخاص بنا بما يلي:
if ("serviceWorker" in navigator) { window.addEventListener("load", () => { const wb = new Workbox("/sw.js"); wb.register(); }); }
سنجد بعد ذلك زر التحديث الذي يحتوي على المعرفworkbox-waiting
:
//add before the wb.register() const updateButton = document.querySelector("#app-update"); // Fires when the registered service worker has installed but is waiting to activate. wb.addEventListener("waiting", event => { updateButton.classList.add("show"); updateButton.addEventListener("click", () => { // Set up a listener that will reload the page as soon as the previously waiting service worker has taken control. wb.addEventListener("controlling", event => { window.location.reload(); }); // Send a message telling the service worker to skip waiting. // This will trigger the `controlling` event handler above. wb.messageSW({ type: "SKIP_WAITING" }); }); });
سيعرض هذا الرمز زر التحديث عندما يكون هناك تحديث جديد (لذلك عندما يكون عامل الخدمة في حالة انتظار) وسيرسل رسالة SKIP_WAITING
إلى عامل الخدمة.
سنحتاج إلى تحديث ملف عامل الخدمة والتعامل مع حدث SKIP_WAITING
بحيث يستدعي skipWaiting
:
//add in src-sw.js addEventListener("message", event => { if (event.data && event.data.type === "SKIP_WAITING") { skipWaiting(); });
الآن قم بتشغيل npm run dev
ثم أعد تحميل الصفحة. انتقل إلى التعليمات البرمجية الخاصة بك وقم بتحديث عنوان شريط التنقل إلى "Navbar v2". أعد تحميل الصفحة مرة أخرى ، وستتمكن من رؤية أيقونة التحديث.
تغليف
يعمل موقعنا الآن في وضع عدم الاتصال ويمكنه إخبار المستخدم بالتحديثات الجديدة. ومع ذلك ، يرجى أن تضع في اعتبارك أن العامل الأكثر أهمية عند إنشاء PWA هو تجربة المستخدم. ركز دائمًا على بناء تجارب يسهل على المستخدمين استخدامها. نحن ، كمطورين ، نميل إلى أن نكون متحمسين للغاية بشأن التكنولوجيا وغالبًا ما ينتهي بنا الأمر بنسيان مستخدمينا.
إذا كنت ترغب في اتخاذ هذه الخطوة إلى الأمام ، فيمكنك إضافة بيان تطبيق الويب الذي سيسمح للمستخدمين بإضافة الموقع إلى شاشتهم الرئيسية. وإذا كنت ترغب في معرفة المزيد عن Workbox ، فيمكنك العثور على الوثائق الرسمية على موقع ويب Workbox.
مزيد من القراءة على SmashingMag:
- هل يمكنك كسب المزيد من المال باستخدام تطبيق جوال أو PWA؟
- دليل شامل لتطبيقات الويب التقدمية
- Native و PWA: خيارات ، وليس منافسين!
- بناء PWA باستخدام الزاوية 6