قامت إحدى فرق الواجهة الأمامية بإطلاق طبقة محاكاة API آمنة النوع (type-safe) تعمل فقط في بيئة التطوير. باستخدام Axios interceptors و Vite’s tree-shaking، تظل حزمة الإنتاج (production bundle) دون تغيير. يقوم المهندسون بجلب البيانات بأنماطهم المعتادة أثناء انتظار نقاط نهاية الـ backend، ثم يقومون بتبديل علامة بيئة واحدة للوصول إلى الـ API الحقيقي.

لماذا احتاج الفريق إلى طريقة أفضل للمحاكاة

يواجه مطورو الواجهة الأمامية عقبة عندما يكون مسار الـ backend غير مكتمل. الحل السريع — كتابة استجابة ثابتة (hard-coding) داخل المكون أو نثر كتل if (process.env.NODE_ENV === 'development') في جميع أنحاء واجهة المستخدم — يبقي التطبيق مستمراً ولكنه يترك ديوناً تقنية (technical debt). تصبح كائنات المحاكاة (mock objects) هذه جزءاً من منطق المكون، مما يزيد من خطر إرسال بيانات وهمية إلى الإنتاج، ويجعل الكود أصعب في القراءة والاختبار.

أراد الفريق نقل كل عملية محاكاة خارج شجرة المكونات، وفرض عقد (contract) بين الواجهتين الأمامية والخلفية، وضمان عدم وصول أي شيء إضافي إلى بناء الإنتاج.

العملية المكونة من ثلاث خطوات التي يتبعها الفريق

  1. اجتماع العقد – يجلس مهندسو الواجهة الأمامية والخلفية ويقومون بإدراج كل طلب، ورابطه (URL)، وطريقته (method)، والحمولة (payload) المتوقعة.
  2. عقد محدد النوع – يحولون القائمة إلى واجهة TypeScript تصبح المصدر الوحيد للحقيقة (single source of truth) لأشكال الطلبات والاستجابات.
  3. ربط الـ Interceptor – يقوم Axios interceptor بفحص كل طلب صادر. إذا تطابق الرابط مع محاكاة مسجلة، يعيد الـ interceptor بيانات المحاكاة؛ وإلا يستمر الطلب إلى الخادم الحي.

نظرًا لأن الـ interceptor هو المكان الوحيد الذي يوجد فيه منطق المحاكاة، يظل كود المكونات دون تغيير. يستمر المطورون في استخدام خطافات جلب البيانات (data-fetching hooks) العادية الخاصة بهم — مثل useQuery — دون إضافة أي منطق شرطي.

كيف يتم تجنب تضخم الإنتاج

وضعت الفرق ثلاث ضمانات تسمح لـ Rollup (أداة التجميع المستخدمة بواسطة Vite) بإزالة كود المحاكاة تماماً عند البناء للإنتاج:

  • يتم حل import.meta.env.DEV إلى false في بناء الإنتاج، لذا تختفي وحدة الـ interceptor بالكامل أثناء عملية الـ tree-shaking.
  • يتم ضبط متغير MODE على قيمة أخرى غير test عند تشغيل اختبارات الوحدة (unit tests)، مما يبقي الكود الخاص بالاختبارات فقط منفصلاً.
  • يتم ضبط علامة مخصصة، VITE_ENABLE_MSW ، على false افتراضياً ويجب تشغيلها صراحةً لتفعيل المحاكاة.

عندما تكون جميع الشروط الثلاثة خاطئة (false)، لا يدخل سجل المحاكاة (mock registry) أبداً في الحزمة النهائية.

تنظيم ملفات المحاكاة

يتبع المستودع (repo) تخطيطاً متمحوراً حول الميزات (feature-centric):

  • interfaces/ – يحتوي على تعريفات TypeScript الناتجة عن اجتماع العقد.
  • scenarios.ts – يحتوي على أمثلة ملموسة للاستجابات الناجحة وحالات الخطأ لكل نقطة نهاية (endpoint).
  • devHandlers.ts – يعمل كسجل مركزي يربط الروابط (URLs) ببيانات السيناريو ويقوم بتوصيل الـ interceptor بـ Axios.

يمكن لسكربت هيكلة (scaffolding script) صغير إنشاء هذه الملفات تلقائياً: أعطه رابطاً (URL) والواجهة المطابقة، وسيقوم بإنشاء ملفات الـ stub وتسجيل المحاكاة. يعيش السكربت خارج مسار كود الإنتاج، لذا فهو لا يؤثر على حجم الحزمة.

ما الذي اكتسبه الفريق

  • صفر عمليات محاكاة داخل المكونات – تعيش جميع البيانات الوهمية في طبقة مخصصة، مما يحافظ على نظافة كود واجهة المستخدم.
  • أمان النوع من البداية إلى النهاية – تتوافق بيانات المحاكاة مع واجهات TypeScript نفسها المستخدمة في الاستجابات الحقيقية، لذا يتم اكتشاف عدم التطابق في وقت التجميع (compile time).
  • لا يوجد وزن إضافي في الإنتاج – تقوم عملية الـ tree-shaking بإزالة الـ interceptor وبيانات المحاكاة تماماً، مما يترك حجم الحزمة دون تغيير.
  • سيناريوهات مشتركة للتطوير والاختبار – تستخدم نفس تعريفات المحاكاة في كل من التطوير المحلي والاختبارات الآلية، مما يقلل من التكرار.

المقايضات والقيود

لا يحل هذا النهج محل الـ backend الحقيقي. إذا اختلف عقد المحاكاة عن الـ API الحي، سيكتشف المطورون عدم التطابق فقط بعد تبديل علامة البيئة.

ما يجب مراقبته لاحقاً

  • تكامل الأدوات
  • الاعتماد الأوسع
  • مراقبة الأداء

الخلاصة واضحة: نقل منطق المحاكاة إلى طبقة محددة النوع ومحمية ببيئة العمل يسمح لفرق الواجهة الأمامية بالحفاظ على نظافة المكونات، والبقاء في أمان النوع، وإرسال عمليات بناء الإنتاج دون حمولات محاكاة مخفية. الحفاظ على عقد مشترك هو ثمن سير عمل تطوير أكثر سلاسة وكود برمجي أكثر نظافة.