تحويل تطبيق Flutter إلى PWA مقاوم لمشاكل التخزين المؤقت وService Worker

٢٤ نوفمبر ٢٠٢٥
Close-up of a monarch butterfly perched on a bright orange zinnia bloom, highlighting its striking wings.

مقدمة: لماذا هذا الدليل مهم للمطوّرين العرب؟

عند نشر تطبيق Flutter كـ PWA تظهر مشكلة شائعة: المستخدمون لا يحصلون على الإصدار المحدّث فور النشر لأن المتصفّح أو شبكة التوزيع (CDN) تعيد المحتوى المخبّر (cached) لنفس الروابط. سيوضح هذا الدليل السبب الفني ويقدّم حلولاً عملية قابلة للتنفيذ على بيئات استضافة شائعة.

التركيز هنا عملي: خطوات قابلة للتطبيق، شيفرات أمثلة للتعديل بعد البناء (post‑build)، وخيارات للتعامل مع CDN أو الحفاظ على Service Worker مع تجنّب تسليم محتوى قديم.

فهم المشكلة التقنية بسرعة

إصدار Flutter الافتراضي لـ PWA يستخدم قائمة موارد (RESOURCES) مع تجزئات (hashes) ليحدّد ما تغيّر ويُعتمد في منطق Service Worker، بينما تعمل معظم شبكات CDN على مبدأ "نفس الرابط = نفس المحتوى" (URL‑based caching). عند تحديث ملف مثل main.dart.js بنفس الاسم، قد يستمر CDN في تقديم النسخة القديمة مما يجعل Service Worker يتلقّى محتوى قديم رغم أن دليل الموارد تغيّر.

ببساطة: Service Worker يعتمد على محتوى‑هاش بينما CDN يعتمد على URL، وهذه اللاموافقة تسبب ما نسمّيه cache‑busting failure. النتائج العملية: تحديثات لا تظهر للمستخدمين إلا بعد انتهاء صلاحية الكاش أو بعد تنفيذ عملية تطهير (purge) في الـ CDN أو قيام المستخدم بعمل hard refresh.

استراتيجيات عملية لحل المشكلة (خطوات وخيارات)

الخيار A — تعطيل Service Worker واستخدام cache‑busting بالروابط

الفكرة: اجعل كل نشر يستخدم روابط مُميّزة (مثلاً: main.dart.js?v=20251124T1200) ثم فعّل التخزين الحادّ عبر CDN (long max‑age, immutable). هذا يبسط التوافق مع الشبكات الحافة ويجعل التحديث فوري بعد تحميل الـ index.html الجديد. مثال أمر بناء مع تعطيل استراتيجية PWA في Flutter:

flutter build web --pwa-strategy=none

كذلك يمكنك بعد البناء تعديل build/web/flutter_bootstrap.js وindex.html لإضافة مُعرف إصدار (timestamp أو git hash) على استيراد main.dart.js. مجرّد ملاحظة: يجب التعامل مع تخزين index.html نفسه لأن مضيفيّات (hosts) قد تخزّنه بفترات أطول.

الخيار B — الإبقاء على Service Worker وتهيئته للتنسيق مع CDN

إذا رغبت بالحفاظ على قدرات الـ SW (مثل سلوك stale‑while‑revalidate أو العمل دون اتصال)، فهناك خيارات:

  • أجعل CDN يعيد التحقق من الأصل (no‑cache / must‑revalidate) لملفات مهمّة تُحدّث غالباً، أو نفّذ عملية invalidation/ purge تلقائية بعد كل نشر.
  • استخدم أسماء ملفّات مرقّمة أو محتوى‑هاش في اسم الملف (مثلاً أثناء pipeline توليد نسخة مع fingerprint في الاسم) بدلاً من الاعتماد على نفس الاسم. عندها يتوافق CDN وService Worker لأن كل إصدار له URL مختلف.

الخيار C — إدارة دورة حياة Service Worker: إشعار المستخدم وتطبيق التحديث فوراً

يمكنك كتابة منطق في الـ Service Worker ليستخدم self.skipWaiting() وself.clients.claim() لتفعيل النسخة الجديدة فور تثبيتها، أو استخدام مكتبات مثل Workbox / workbox‑window لإظهار إشعار للمستخدم بأن إصداراً جديداً متاح ويمكنه تحديث الصفحة. عند استخدام skipWaiting() احرص على اختبار سيناريوهات التوافق لأنك قد تفعّل Service Worker جديد مع تطبيق قديم مُحمّل في الذاكرة.

مثال مبسّط لجزء من SW يطبّق skipWaiting وبعدها clients.claim:

self.addEventListener('install', (event) => {
  event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', (event) => {
  event.waitUntil(self.clients.claim());
});

وصفة تطبيقية موصّى بها (Checklist) للمطوّر العربي

  1. قرّر إن كنت تريد ميزات الـ PWA الكاملة (offline, cache control) أم تريد بساطة التوزيع مع CDN. كل خيار يوجّه نحو حلول مختلفة.
  2. إن اخترت البساطة: قم بـ flutter build web --pwa-strategy=none، ثم أضف query parameter يعتمد على build timestamp إلى استدعاءات main.dart.js وflutter_bootstrap.js في index.html. هذا يضمن توافق CDN.
  3. إن رغبت بالـ SW: استخدم آلية إصدار اسماء ملفات (fingerprinting) أو اجعل CDN يتيح purge آلي بعد كل نشر، وطبّق إشعارات للمستخدم عبر workbox‑window لطلب إعادة التحميل عندما يتوفّر إصدار جديد.
  4. اختبر على عدة متصفّحات وبيئات (شبكة بطيئة، وضع الطيران) وتأكد من سلوك التحديث بعد تنصيب SW وإلغاء التسجيل (unregister) عند الحاجة).

ملاحظة أخيرة: بعض فرق التطوير قررت إزالة Service Worker الافتراضي من Flutter أو تغيير سلوكه بسبب تناقضه مع البنى القائمة على CDN؛ راجع قرار فريقك التقني وقوّم تبعات الأداء قبل التعديل الجذري.

خاتمة

لا توجد "حلّة واحدة" تناسب كل الحالات. إن كان هدفك توزيع سريع عبر CDN مع أقل تعقيد — استخدم تعطيل الـ SW + cache‑busting على مستوى URL. أما إن كنت تريد تجربة offline ومرونة عالية، فاضبط Service Worker بعناية (skipWaiting/clients.claim/Workbox) وتكامل مع سياسة purge للـ CDN. الاختبار المتكرر ومراقبة المستخدمين بعد النشر هما مفتاح النجاح.