ما وراء الـ Runtime: هندسة WebAssembly عالية الأداء لتطبيقات Expo الشاملة
غوص عميق في تجاوز اختناقات JavaScript من خلال دمج WebAssembly المدعوم بلغة Rust في تطبيقات Expo الشاملة. تعلم كيفية بناء جسر ذاكرة zero-copy يعمل عبر الويب، iOS، و Android.

ما وراء الـ Runtime: هندسة WebAssembly عالية الأداء لتطبيقات Expo الشاملة
لقد قضيت معظم العقد الماضي في دفع حدود الممكن في المتصفح وعلى تطبيقات الجوال. إذا كان هناك شيء واحد تعلمته، فهو أنه على الرغم من تعدد استخدامات JavaScript المذهلة، إلا أن لها سقفاً تقنياً. عندما تقوم ببناء تطبيق Expo شامل يحتاج إلى التعامل مع مهام شاقة — مثل التركيب الصوتي في الوقت الفعلي (real-time audio synthesis)، أو التشفير المعقد، أو معالجة الفيديو إطاراً بإطار — فإن جسر الـ JS يصبح عنق زجاجة (bottleneck) لا يمكنك تجاهله.
مؤخراً، تعمقت في جحر الأرنب لإدخال WebAssembly (WASM) إلى منظومة Expo، ليس فقط كميزة مخصصة للويب، ولكن كمحرك أداء عابر للمنصات (cross-platform). إليك كيف قمت بهندسة حل يردم الفجوة بين قوة Rust الخام وتجربة المطور الشاملة التي يوفرها Expo.
معضلة الـ "Universal"
حلم Expo هو "اكتب مرة واحدة، وشغّل في كل مكان". لكن WASM في الأصل هو مواطن ويب. في الويب، لديك WebAssembly global. أما في iOS و Android (داخل محركات Hermes أو JSC)، فإن هذا الـ global غير موجود بشكل أصلي بنفس الطريقة.
لتحقيق تكافؤ حقيقي في الأداء، كان عليّ تجاوز مجرد استخدام الـ polyfills البسيطة. كنت بحاجة إلى طريقة لتنفيذ ملفات binary منخفضة المستوى على الجوال تضاهي سرعة WASM الأصلي في المتصفح.
المعمارية: لغة Rust كمصدر للحقيقة
اخترت Rust للمنطق البرمجي الخلفي (backend logic). لماذا؟ لأن wasm-pack وسلامة الذاكرة (memory safety) المتأصلة فيها تجعلها المعيار الذهبي لبناء modules عالية الأداء.
سير العمل الخاص بي يبدو كالتالي:
- المنطق الأساسي (Core Logic): مكتوب بلغة Rust.
- الربط (Binding): استخدام
wasm-bindgenلتصدير الدوال. - الترجمة (Compilation): استهداف
wasm32-unknown-unknown.
لكن هنا تكمن الصعوبة الهندسية. في بيئة Expo، لا يمكنك ببساطة عمل fetch() لملف .wasm على الجوال كما تفعل في الويب.
الاختراق التقني: جسر ذاكرة Zero-Copy
أكبر قاتل للأداء في تداخل WASM-JS هو تسلسل البيانات (data serialization). إذا كنت تمرر مصفوفات كبيرة ذهاباً وإياباً كـ JSON أو كائنات منسوخة، فقد خسرت معركة الأداء بالفعل.
كان اختراقي التقني هو تنفيذ مخزن ذاكرة مؤقت مشترك (shared memory buffer). من خلال تمرير ArrayBuffer من جانب JavaScript إلى نسخة WASM، يمكن لكود Rust تعديل تلك الذاكرة مباشرة.
حل مشكلة الجوال (iOS و Android)
لتشغيل هذا على Expo mobile، استخدمت react-native-wasm بالاشتراك مع Expo module مخصص. في جانب الويب، يستخدم التنفيذ واجهة برمجة تطبيقات المتصفح الأصلية (native browser API). أما في الجوال، فنحن نستخدم محركاً خفيف الوزن يغلف ملف WASM binary.
الحيلة تكمن في استخدام واجهة تحميل موحدة (Unified Loader Interface). قمت بإنشاء غلاف (wrapper) يكتشف المنصة ويستبدل استراتيجية التحميل:
- Web: يستخدم
WebAssembly.instantiateStreaming. - Mobile: يقرأ ملف
.wasmكسلسلة Base64 (أو عبر URI لملف محلي) ويمررها إلى الجسر الأصلي (native bridge) الأساسي.
نتائج الأداء
في اختبار ضغط حديث — حساب مجموعات Mandelbrot المعقدة في الوقت الفعلي — بلغ متوسط تنفيذ JS حوالي 45ms لكل إطار. أما تنفيذ WASM؟ 3.2ms.
هذا هو الفرق بين تطبيق يبدو "بطيئاً" وتطبيق يبدو "أصلياً" (native). من خلال ترحيل المهام الكثيفة على المعالج إلى WASM، يظل مسار JS (JS thread) حراً للقيام بما هو أفضل فيه: تحديثات واجهة المستخدم وتفاعل المستخدم.
دروس هندسية مستفادة
- لا تبالغ في التجريد (Don't over-abstract): اجعل واجهة WASM نحيفة. كلما زادت عمليات العبور بين الحدود، زاد العبء الإضافي (overhead).
- سلامة الأنواع (Type Safety) هي صديقك: استخدم
ts-rsلمزامنة هياكل Rust (structs) مع واجهات TypeScript. هذا يمنع جحيم "الفشل الصامت" الذي غالباً ما يصيب تداخل البيانات الثنائية. - حجم الحزمة (Bundle Size) مهم: يمكن أن تصبح ملفات WASM binary ضخمة. استخدم دائماً
wasm-optلضغط كل بايت ممكن من إصدارات الإنتاج قبل شحنها في حزمة Expo الخاصة بك.
مستقبل Expo هو متعدد اللغات (Polyglot)
نحن نتحرك نحو مستقبل لا يكون فيه React Native مجرد إطار عمل لـ JavaScript؛ بل هو منسق لبيئات تشغيل (runtimes) متعددة. لقد أظهرت لي الهندسة باستخدام WASM في Expo أنه يمكننا الحصول على أفضل ما في العالمين: سرعة تطوير React مع سرعة التنفيذ الخام لـ C++ أو Rust.
إذا كنت تواجه جدران الأداء، توقف عن محاولة تحسين حلقات التكرار (loops) في JS. لقد حان الوقت للمضي إلى ما وراء الـ runtime.