ما بعد SemVer: هندسة تحديثات OTA مضادة للرصاص لتطبيقات React Native باستخدام Expo Fingerprint
توقف عن التخمين ما إذا كانت تحديثات JS bundle ستؤدي إلى انهيار تطبيقك الـ native. تعلّم كيفية هندسة pipeline أتمتة تحديثات OTA بمبدأ zero-trust باستخدام Expo Fingerprint و EAS.

لقد عشنا جميعاً قصة الرعب الخاصة بـ React Native. إنها الساعة 4:30 مساءً من يوم الخميس. تقوم بدفع تحديث Over-the-Air (OTA) عبر EAS لإصلاح bug بسيط في الـ UI. يشير الـ Semantic Versioning إلى أنه مجرد patch من (1.4.1 إلى 1.4.2). ولكن في أعماق الـ dependency tree، قامت transitive dependency بسحب native change بسيط.
بعد عشر دقائق، تضيء أداة الـ crash reporting لديك مثل شجرة عيد الميلاد. يحاول الـ JavaScript bundle استدعاء native method غير موجودة في الـ active binary على أجهزة المستخدمين. انهيار قاتل (fatal crash) عند التشغيل.
في العالم الهجين لـ React Native، تعتبر جدولة الإصدارات الدلالية (SemVer) كذبة. فكود الـ JS الخاص بك والـ Native runtime يعملان على دورات حياة مختلفة تماماً. لسنوات، قمنا بترقيع هذا الأمر باستخدام runtime versions يدوية أو سكربتات git-diff هشة.
في هذا المقال، أريد أن أشارك كيف حل فريقي هذه المشكلة مرة واحدة وإلى الأبد. لقد انتقلنا إلى zero-trust native pipeline باستخدام @expo/fingerprint. إليك كيف يمكنك بناء pipeline تحديثات OTA مضاد للرصاص يضمن برمجياً ألا تنهار تحديثاتك أبداً بسبب عدم تطابق في الـ native runtimes.
مشكلة الـ Native Runtime
لفهم سبب حاجتنا إلى Fingerprint، يجب أن ننظر إلى ما يشكل "native change" في مشروع Expo الحديث. الأمر لا يقتصر فقط على تعديل الملفات داخل /ios أو /android. يتم تحفيز الـ native change بواسطة:
- ترقية حزمة تحتوي على native code (مثل
react-native-reanimated). - تعديل ملف
app.json(Expo Config) بطريقة تغير من Expo Config Plugins (مثل تغيير الصلاحيات أو أيقونات التطبيق). - ترقية الـ Expo SDK نفسه.
- تعديل الـ native code المحلي أو قوالب الإعدادات (configuration templates).
إذا تغير أي من هذه العناصر، يجب على الـ JS bundle الخاص بك أن يستهدف native binary جديداً. إذا قمت بدفع تحديث OTA إلى binary تم بناؤه قبل هذه التغييرات، فسينهار التطبيق.
تاريخياً، حلت Expo هذه المشكلة باستخدام Runtime Versions المحددة في app.json:
لكن الـ manual runtime versioning يعتمد على الانضباط البشري. فالمطورون ينسون ترقية الإصدار (bump). أو يقومون بترقيته دون داعٍ، مما يجبر المستخدمين على تنزيل تحديث ضخم وجديد من متجر التطبيقات في حين أن تحديث OTA بسيط بحجم 100KB كان سيكفي.
ما هو Expo Fingerprint؟
@expo/fingerprint هي أداة طورها فريق Expo تقوم بتوليد hash حتمي (deterministic hash) للحالة الـ native الخاصة بمشروعك (native state).
حيث تقوم بتحليل ملف package.json وملفات الـ Lockfiles (yarn.lock و package-lock.json) و app.json و config plugins وأي مجلدات native خام (/ios و /android). وتتجاهل تماماً التغييرات التي تقتصر على JS/TS.
- إذا قمت بتغيير React component: فإن الـ fingerprint يبقى كما هو.
- إذا قمت بتثبيت
lucide-react-native(JS بحت): فإن الـ fingerprint يبقى كما هو. - إذا قمت بتثبيت
react-native-skia(native code): فإن الـ fingerprint يتغير.
يصبح هذا الـ hash الحتمي الوحيد هو الـ runtimeVersion الخاص بك. من خلال ضبط الـ runtime version على الـ fingerprint hash لبيئتك الـ native، فإنك تضمن أن تحديث EAS Update سيستهدف فقط ويتم تنزيله بواسطة الـ native binaries التي تشترك في نفس القدرات الـ native تماماً.
دليل التنفيذ خطوة بخطوة
دعنا نبني CI/CD pipeline مؤتماً يستخدم Expo Fingerprint لتحديد ما إذا كنا سنقوم بإرسال EAS Update (تغيير JS آمن) أو تحفيز EAS Build جديد (تم اكتشاف native change).
1. إعداد Expo لاستخدام Fingerprint Runtime Versions
أولاً، قم بتثبيت أداة الـ CLI في مشروعك:
بعد ذلك، نحتاج إلى توجيه Expo لاستخدام الـ fingerprint كـ runtime version الخاص بنا. قم بتحديث إعدادات app.json لاستخدام سياسة الـ fingerprint الديناميكية:
في الكواليس، عندما تقوم بتشغيل eas build أو eas update، سيقوم EAS تلقائياً بتوليد الـ fingerprint hash لمساحة العمل المحلية (local workspace) الخاصة بك ودمجه في الـ build configurations الخاصة بك.
2. صياغة الـ CI/CD Pipeline المضاد للرصاص
الآن، دعنا نصمم GitHub Actions workflow. هدفنا بسيط:
- عند كل push إلى فرع
main، قم بحساب الـ native fingerprint للـ commit الحالي. - جلب الـ fingerprint للإصدار الإنتاجي (production release) الحالي.
- إذا تطابقا: ادفع تحديث OTA عبر
eas updateفوراً. - إذا اختلفا: قم بتحفيز بناء native جديد عبر
eas buildوإرساله إلى المتاجر، لأن الـ native dependencies قد تغيرت.
إليك سكربت جاهز للإنتاج (scripts/check-native-diff.js) للتعامل برمجياً مع المقارنة باستخدام الـ API الخاص بـ @expo/fingerprint:
3. ربط كل شيء في GitHub Actions
الآن نكتب ملف الـ workflow (.github/workflows/deploy.yml) لتنظيم مصفوفة القرار هذه:
الطفرات المعمارية التي شهدناها
لقد ألغى تطبيق هذه البنية الهيكلية العديد من نقاط الألم التي كنا نفترض أنها مجرد "جزء لا يتجزأ من تطوير React Native":
1. ترقية الـ Dependencies بمبدأ Zero-Trust
قبل Fingerprint، كان المطورون مرعوبين من تحديث الحزم في الـ PRs الصغيرة. الآن، يمكن للمطور ترقية الـ package.json بكل ثقة. إذا قام عن طريق الخطأ بسحب native dependency، فإن الـ CI pipeline سيكتشف ذلك تلقائياً، ويحظر مسار الـ OTA، ويقوم بجدولة بناء كامل للـ native binary. لا حاجة لأي إشراف بشري.
2. عزلة الـ Monorepo
إذا كنت تعمل في monorepo، فأنت تعلم أن التغييرات في أدوات مساحة العمل المشتركة غالباً ما تؤدي إلى false positives في الـ pipelines البسيطة المعتمدة على git-diff. يتميز Expo Fingerprint بالذكاء؛ فهو يقوم فقط بعمل hash للملفات التي تؤثر فعلياً على خطوة الـ bundling والـ compilation في React Native، مما يعني أن تحديثات الـ UI packages أو الأدوات في أجزاء أخرى من الـ monorepo لن تحفز بناء native غير ضروري.
3. تخصيص إعدادات الـ Fingerprint الخاصة بك
في بعض الأحيان، قد ترغب في تجاهل ملفات معينة يتتبعها @expo/fingerprint بشكل افتراضي، أو تتبع شيء مخصص بشكل صريح (مثل ملف إعدادات SDK native خام). يمكنك بسهولة ضبط هذا بدقة عن طريق إنشاء ملف fingerprint.config.js:
القاعدة الذهبية لـ Expo Pipelines الحديثة
إذا خرجت بشيء واحد من هذا المقال، فليكن هذا: لا تترك إدارة مطابقة إصدارات الـ native للبشر.
لدينا compilers للتحقق من الـ type safety؛ ويجب أن يكون لدينا أدوات native hashing للتحقق من الـ runtime safety. من خلال اعتماد @expo/fingerprint وجعله يقود برمجياً استراتيجية نشر EAS الخاصة بك، فإنك تستعيد راحة بالك—وأمسيات أيام الخميس الخاصة بك.