على مدار العامين الماضيين، عمل فريق مهندسي Goodnotes على مشروع لتوفير تطبيق تدوين الملاحظات الناجح على iPad على منصات أخرى. تتناول هذه الدراسة الحالة كيفية حصول تطبيق iPad لعام 2022 على الويب وChromeOS وAndroid وWindows باستخدام تكنولوجيات الويب و WebAssembly مع إعادة استخدام رمز Swift نفسه الذي عمل عليه الفريق لأكثر من عشر سنوات.
سبب توفُّر تطبيق Goodnotes على الويب وأجهزة Android وأجهزة Windows
في عام 2021، كان تطبيق Goodnotes متاحًا فقط لأجهزة iOS وiPad. واجه فريق الهندسة في Goodnotes تحديًا فنيًا كبيرًا: إنشاء إصدار جديد من Goodnotes متوافق مع أنظمة تشغيل ومنصات إضافية. يجب أن يكون المنتج متوافقًا تمامًا مع تطبيق iOS وأن يعرض الملاحظات نفسها. يجب أن تكون أي ملاحظة تم وضعها على ملف PDF أو أي صورة مرفقة مماثلة وأن تعرض الخطوط نفسها التي يعرضها تطبيق iOS. يجب أن تكون أيّ ضربة مضافة مماثلة لتلك التي يمكن لمستخدمي iOS إنشاؤها، بغض النظر عن الأداة التي كان يستخدمها المستخدم، مثل القلم أو قلم التحديد أو قلم الحبر أو الأشكال أو الممحاة.
استنادًا إلى المتطلبات وخبرة الفريق الهندسي، خلص الفريق سريعًا إلى أنّ إعادة استخدام قاعدة بيانات Swift هو أفضل مسار للعمل، نظرًا لأنّه سبق أن تم كتابته واختباره جيدًا على مدار عدة سنوات. ولكن لماذا لا يتم نقل تطبيق iOS/iPad الحالي إلى منصة أو تقنية أخرى مثل Flutter أو Compose Multiplatform؟ إنّ الانتقال إلى منصة جديدة سيتطلّب إعادة كتابة Goodnotes. قد يؤدي ذلك إلى بدء سباق تطوير بين تطبيق iOS الذي تم تنفيذه وتطبيق جديد يتم إنشاؤه من الصفر ، أو قد يؤدي إلى إيقاف التطوير الجديد للتطبيق الحالي إلى أن يتم الانتهاء من قاعدة البيانات الجديدة. إذا كان بإمكان فريق Goodnotes إعادة استخدام رمز Swift، كان بإمكانه الاستفادة من الميزات الجديدة التي نفّذها فريق iOS بينما كان الفريق المعني بالأنظمة الأساسية المختلفة يعمل على أساسيات التطبيق وتحقيق التكافؤ في الميزات.
سبق أن حلّ المنتج عددًا من التحديات المثيرة للاهتمام في نظام التشغيل iOS لإضافة ميزات مثل:
- عرض الملاحظات
- مزامنة المستندات والملاحظات
- حلّ التعارض في الملاحظات باستخدام أنواع البيانات المكرّرة التي لا تتضمّن تعارضًا
- تحليل البيانات لتقييم نماذج الذكاء الاصطناعي
- البحث عن المحتوى وفهرسة المستندات
- تجربة التمرير المخصّصة والرسومات المتحرّكة
- عرض تنفيذ النموذج لجميع طبقات واجهة المستخدم
سيكون من الأسهل تنفيذ كل هذه الميزات على الأنظمة الأساسية الأخرى إذا تمكّن فريق الهندسة من إتاحة قاعدة بيانات iOS لتطبيقات iOS وiPad وتنفيذها كجزء من مشروع يمكن أن تطرحه Goodnotes كتطبيقات لنظام التشغيل Windows أو Android أو تطبيقات ويب.
حزمة التكنولوجيا في Goodnotes
لحسن الحظ، كانت هناك طريقة لإعادة استخدام رمز Swift الحالي على الويب، وهي WebAssembly (Wasm). أنشأت Goodnotes نموذجًا أوليًا باستخدام Wasm من خلال المشروع المفتوح المصدر الذي يديره المجتمع SwiftWasm. باستخدام SwiftWasm، تمكّن فريق Goodnotes من إنشاء ملف ثنائي Wasm باستخدام كل رمز Swift الذي تم تنفيذه من قبل. يمكن تضمين هذا البرنامج الثنائي في صفحة ويب يتم شحنها كأحد تطبيقات الويب التقدّمية لنظام التشغيل Android وWindows وChromeOS وكل نظام تشغيل آخر.
كان الهدف هو إصدار تطبيق Goodnotes كتطبيق متوافق مع الأجهزة الجوّالة (PWA) والتمكن من إدراجه في كل متجر للنظام الأساسي. بالإضافة إلى Swift، لغة البرمجة المستخدَمة سابقًا لنظام التشغيل iOS وWebAssembly المستخدَم لتنفيذ رمز Swift على الويب، استخدَم المشروع التقنيات التالية:
- TypeScript: هي لغة البرمجة الأكثر استخدامًا في تكنولوجيات الويب.
- React وWebpack: إطار العمل وبرنامج تجميع الحِزم الأكثر شيوعًا على الويب
- تطبيقات الويب التقدّمية وخدمات Worker: هما عاملان مهمان في هذا المشروع لأنّ الفريق تمكّن من شحن تطبيقنا كتطبيق يعمل بلا إنترنت يعمل مثل أي تطبيق آخر على iOS ويمكنك تثبيته من المتجر أو المتصفّح نفسه.
- PWABuilder: هو المشروع الرئيسي الذي يستخدمه Goodnotes لتحويل تطبيق الويب إلى تطبيق ثنائي لنظام التشغيل Windows الأصلي كي يتمكّن الفريق من توزيع تطبيقنا من Microsoft Store.
- أنشطة الويب الموثوق بها: أهم تكنولوجيا Android التي تستخدمها الشركة لتوزيع تطبيق الويب التقدّمي (PWA) كتطبيق أصلي
يعرِض الشكل التالي ما تم تنفيذه باستخدام TypeScript وReact الكلاسيكيَين، وما تم تنفيذه باستخدام SwiftWasm وJavaScript العادية وSwift و WebAssembly. يستخدم هذا الجزء من المشروع JSKit، وهي مكتبة تفاعل لـ JavaScript لـ Swift وWebAssembly يستخدمها الفريق في معالجة DOM في شاشة المحرِّر من رمز Swift عند الحاجة أو حتى استخدام بعض واجهات برمجة التطبيقات الخاصة بالمتصفّح.
لماذا يجب استخدام WebAssembly والويب؟
على الرغم من أنّ Apple لا توفّر واجهة برمجة التطبيقات Wasm رسميًا، إليك الأسباب التالية التي جعلت فريق Goodnotes الهندسي يرى أنّ هذا النهج هو القرار الأفضل:
- إعادة استخدام أكثر من 100 ألف سطر من الرموز البرمجية
- إمكانية مواصلة تطوير المنتج الأساسي مع المساهمة أيضًا في التطبيقات المتوافقة مع جميع المنصات
- إمكانية الوصول إلى كل منصّة في أقرب وقت ممكن باستخدام عملية تطوير تكراري
- التحكّم في عرض المستند نفسه بدون تكرار كل منطق العمل، وتقديم اختلافات في عمليات التنفيذ
- الاستفادة من جميع تحسينات الأداء التي تم إجراؤها على كل منصّة في الوقت نفسه (وجميع إصلاحات الأخطاء التي تم تنفيذها على كل منصّة)
كان من الضروري إعادة استخدام أكثر من 100 ألف سطر من الرموز البرمجية ومنطق النشاط التجاري وتنفيذ مسار المعالجة للعرض. وفي الوقت نفسه، يتيح لهم جعل رمز Swift متوافقًا مع سلاسل الأدوات الأخرى إعادة استخدام هذا الرمز في منصّات مختلفة في المستقبل إذا لزم الأمر.
تطوير المنتجات بشكل متكرّر
اتّبع الفريق نهجًا متكرّرًا لإتاحة الميزة للمستخدمين بأسرع وقت ممكن. بدأ تطبيق Goodnotes بإصدار للقراءة فقط من المنتج حيث يمكن للمستخدمين الحصول على أي مستند مشترَك وقراءته من أي نظام أساسي. وباستخدام رابط فقط، سيتمكّن من الوصول إلى الملاحظات نفسها التي كتبها من جهاز iPad وقراءتها. في المرحلة التالية، تمت إضافة ميزات التعديل، لجعل الإصدارات المتوافقة مع جميع الأنظمة الأساسية مساوية لإصدار iOS.
استغرق تطوير الإصدار الأول من المنتج المخصّص للقراءة فقط ستة أشهر، وتم تخصيص الأشهر التسعة التالية لتطوير المجموعة الأولى من ميزات التعديل وشاشة واجهة المستخدم التي يمكنك من خلالها الاطّلاع على جميع المستندات التي أنشأتها أو شاركها معك أحد الأشخاص. بالإضافة إلى ذلك، كان من السهل نقل الميزات الجديدة لمنصّة iOS إلى المشروع المتوافق مع جميع المنصات بفضل SwiftWasm Toolchain. على سبيل المثال، تم إنشاء نوع جديد من الأقلام وتنفيذه بسهولة على جميع المنصات من خلال إعادة استخدام آلاف أسطر الرموز البرمجية.
لقد كانت تجربة إنشاء هذا المشروع رائعة، وتعلمت Goodnotes الكثير منه. لهذا السبب، ستتمحور الأقسام التالية حول نقاط فنية مهمة تتعلّق بتطوير الويب واستخدام WebAssembly وأحد اللغات مثل Swift.
العقبات الأولية
كان العمل على هذا المشروع صعبًا للغاية من عدة نقاط نظر مختلفة. كان العائق الأول الذي واجهه الفريق مرتبطًا بمجموعة أدوات SwiftWasm. كانت سلسلة الأدوات عاملاً مساعدًا كبيرًا للفريق، ولكن لم يكن كل رمز iOS متوافقًا مع Wasm. على سبيل المثال، الرمز البرمجي المرتبط بعمليات الإدخال/الإخراج أو واجهة المستخدم، مثل تنفيذ طرق العرض أو عملاء واجهات برمجة التطبيقات أو الوصول إلى قاعدة البيانات، لم يكن قابلاً لإعادة الاستخدام، لذلك كان على الفريق بدء إعادة هندسة أجزاء معيّنة من التطبيق للتمكّن من إعادة استخدامها من الحلّ المتوافق مع جميع الأنظمة الأساسية. إنّ معظم طلبات إعادة النظر التي أنشأها الفريق كانت عبارة عن عمليات إعادة صياغة للتبعيات المجردة ليتمكّن الفريق من استبدالها في وقت لاحق باستخدام ميزة "حقن التبعيات" أو استراتيجيات أخرى مشابهة. كان رمز iOS البرمجي يجمع في الأصل بين منطق النشاط التجاري الأساسي الذي يمكن تنفيذه في Wasm ورمز برمجي مسؤول عن الإدخال/الإخراج وواجهة المستخدم التي لا يمكن تنفيذها في Wasm لأنّه لا يتوافق مع أيّ منهما. لذلك، كان من الضروري مجددًا تنفيذ رمز إدخال البيانات وإخراجها ورمز واجهة المستخدم في TypeScript بعد أن أصبح منطق النشاط التجاري في Swift جاهزًا لإعادة استخدامه بين المنصات.
المشاكل التي تم حلّها في الأداء
بعد أن بدأت Goodnotes العمل على المحرِّر، رصد الفريق بعض المشاكل في تجربته، وواجهنا قيودًا تقنية صعبة في خارطة المسار. كانت المشكلة الأولى متعلّقة بالأداء. JavaScript هي لغة تستخدم سلسلة محادثات واحدة. وهذا يعني أنّه يحتوي على حزمة مكالمات واحدة وكومة ذاكرة واحدة. ويعمل هذا المعالج على تنفيذ الرمز البرمجي بالترتيب، ويجب أن ينتهي من تنفيذ قطعة من الرمز البرمجي قبل الانتقال إلى القطعة التالية. يكون هذا الإجراء متزامنًا، ولكن قد يكون ضارًا في بعض الأحيان. على سبيل المثال، إذا كانت إحدى الدوال تستغرق بعض الوقت لتنفيذها أو كان عليها الانتظار لحين اكتمال عملية ما، يؤدي ذلك إلى تجميد كل العمليات في الوقت الحالي. وهذا هو بالضبط ما كان على المهندسين حلّه. واجه الفريق مشكلة في تقييم بعض المسارات المحدّدة في قاعدة بياناتنا المرتبطة بطبقة المعالجة أو خوارزميات معقدة أخرى، لأنّ هذه الخوارزميات كانت متزامنة، وكان تنفيذها يعرقل السلسلة الرئيسية للمعالجة. أعاد فريق Goodnotes كتابة هذه الوظائف لجعلها أسرع، وأعاد صياغة بعض منها لجعلها غير متزامنة. وقدّموا أيضًا استراتيجية لتحقيق الأرباح حتى يتمكّن التطبيق من إيقاف تنفيذ الخوارزمية ومواصلة تنفيذها لاحقًا، ما يتيح للمتصفّح تعديل واجهة المستخدم وتجنُّب إسقاط اللقطات. لم تكن هذه مشكلة في تطبيق iOS لأنّه يمكنه استخدام سلاسل المهام وتقييم هذه الخوارزميات في الخلفية بينما تعمل سلسلة مهام iOS الرئيسية على تعديل واجهة المستخدم.
كان على الفريق الهندسي أيضًا حلّ مشكلة نقل واجهة مستخدم مستندة إلى عناصر HTML مرتبطة بـ DOM إلى واجهة مستخدم مستندة إلى لوحة canvas في وضع ملء الشاشة. بدأ المشروع بعرض كل الملاحظات والمحتوى المرتبط بأحد المستندات كجزء من بنية DOM باستخدام عناصر HTML كما هو الحال في أي صفحة ويب أخرى، ولكن في مرحلة ما تم نقله إلى لوحة عرض ملء الشاشة لتحسين الأداء على الأجهزة المنخفضة المستوى من خلال تقليل الوقت الذي يستغرقه المتصفّح في تعديلات DOM.
حدّد الفريق الهندسي التغييرات التالية على أنّها أمور كان من الممكن أن تقلّل من بعض المشاكل التي واجهها الفريق، لو تم إجراؤها في بداية المشروع.
- يمكنك تفريغ سلسلة المهام الرئيسية بشكل أكبر من خلال استخدام مهام Web Worker بشكل متكرر مع كثافة الخوارزميات.
- استخدِم الدوالّ المُصدَّرة و الدوالّ المستورَدة بدلاً من مكتبة التشغيل التفاعلي بين JavaScript وSwift منذ البداية حتى تتمكّن من تقليل تأثير الأداء الناتج عن الخروج من سياق Wasm. إنّ مكتبة التشغيل التفاعلي لبرنامج JavaScript هذه مفيدة للوصول إلى DOM أو المتصفّح، ولكنها أبطأ من الدوالّ الأصلية التي تم تصديرها باستخدام Wasm.
- تأكَّد من أنّ الرمز البرمجي يسمح باستخدام
OffscreenCanvas
في الخلفية حتى يتمكّن التطبيق من تفريغ سلسلة المهام الرئيسية ونقل كل استخدام لـ Canvas API إلى عامل ويب يحقّق أفضل أداء للتطبيقات عند كتابة الملاحظات. - نقل جميع عمليات التنفيذ ذات الصلة بـ Wasm إلى عامل ويب أو حتى مجموعة من عوامل الويب حتى يتمكّن التطبيق من تقليل عبء العمل في السلسلة الرئيسية
محرِّر النصوص
كانت هناك مشكلة أخرى مثيرة للاهتمام مرتبطة بأداة معيّنة، وهي محرِّر النصوص.
يستند تنفيذ هذه الأداة على نظام التشغيل iOS إلى
NSAttributedString
،
مجموعة أدوات صغيرة تستخدم
RTF
في الخلفية. ومع ذلك، لا يتوافق هذا التنفيذ مع SwiftWasm، لذلك اضطر فريق التطوير المتعدد المنصات إلى إنشاء
أداة تحليل مخصّصة تستند إلى قواعد لغة RTF أولاً، ثم تنفيذ تجربة التعديل من خلال تحويل RTF إلى HTML و
بالعكس. في هذه الأثناء، بدأ فريق iOS العمل على التنفيذ الجديد
لهذه الأداة، واستبدل استخدام تنسيق RTF بنموذج مخصّص حتى يتمكّن التطبيق من
عرض النص المنسَّق بطريقة مناسبة لجميع المنصات التي تشترك في رمز Swift نفسه.
كان هذا التحدي أحد النقاط الأكثر إثارة للاهتمام في خارطة طريق المشروع، لأنّه تم حلّه بشكل متكرّر استنادًا إلى احتياجات المستخدم. كانت هذه مشكلة هندسية تم حلّها باستخدام نهج يركز على المستخدم، حيث احتاج الفريق إلى إعادة كتابة جزء من الرمز البرمجي ليتمكّن من عرض النص، لذلك فعّل تعديل النص في الإصدار الثاني.
الإصدارات المتكرّرة
لقد شهد المشروع تطورًا مذهلاً خلال العامين الماضيين. بدأ الفريق العمل على إصدار للقراءة فقط من المشروع، وبعد بضعة أشهر، أطلق إصدارًا جديدًا تمامًا يتضمّن الكثير من إمكانات التعديل. لطرح التغييرات في الرموز البرمجية بشكلٍ متكرّر في قناة الإصدار العلني، قرّر الفريق استخدام علامات ميزات بشكلٍ مكثّف. في كل إصدار، يمكن للفريق تفعيل ميزات جديدة وإصدار تغييرات في الرموز البرمجية لتنفيذ ميزات جديدة تظهر للمستخدم بعد أسابيع. ومع ذلك، يرى الفريق أنّه كان بإمكانه تحسين بعض الأمور. ويعتقدون أنّه كان من الممكن تسريع العملية من خلال استخدام نظام ديناميكي لميزة "التسمية"، لأنّه سيزيل الحاجة إلى إعادة النشر لتغيير قيم العلامة. سيمنح ذلك Goodnotes مرونة أكبر، وسيسرِّع أيضًا عملية نشر الميزة الجديدة، لأنّ Goodnotes لن تحتاج إلى ربط عملية نشر المشروع بإصدار المنتج.
العمل بلا إنترنت
ومن الميزات الرئيسية التي عمل عليها الفريق هي إتاحة استخدام التطبيق بلا اتصال بالإنترنت. إنّ إمكانية تعديل مستنداتك هي إحدى الميزات التي تتوقّعها من أي تطبيق مماثل. ومع ذلك، هذه ليست ميزة بسيطة لأنّ Goodnotes تتيح التعاون. وهذا يعني أنّ جميع التغييرات التي يجريها مستخدمون مختلفون على أجهزة مختلفة ستظهر على كل جهاز بدون أن يُطلب من المستخدمين حلّ أي تعارضات. حلّت Goodnotes هذه المشكلة منذ فترة طويلة باستخدام السجلّات الموزّعة للتعديلات في الخلفية. بفضل "أنواع البيانات المكرّرة" التي لا تؤدي إلى حدوث تعارض، يمكن لتطبيق Goodnotes دمج جميع التغييرات التي أجراها أي مستخدم على أي مستند ودمجها بدون أي تعارض في الدمج. كان استخدام IndexedDB ومساحة التخزين المتاحة لمتصفّحات الويب عاملاً مهمًا في إتاحة تجربة التعاون بلا إنترنت على الويب.
بالإضافة إلى ذلك، يؤدي فتح تطبيق Goodnotes على الويب إلى تكلفة تنزيل أولية تبلغ حوالي 40 ميغابايت بسبب حجم ملف Wasm الثنائي. في البداية، استهدى فريق Goodnotes بذاكرة التخزين المؤقت العادية للمتصفّح لحِزمة التطبيق نفسها ومعظم نقاط نهاية واجهة برمجة التطبيقات التي يستخدمها، ولكن بعد النظر في الأمر، كان بإمكانه الاستفادة من Cache API وخدمات workers الأكثر موثوقية في وقت سابق. في البداية، تجنّب الفريق تنفيذ هذه المهمة بسبب التعقيد المفترض لها، ولكن في النهاية، أدرك أنّ Workbox سهّل تنفيذها.
اقتراحات عند استخدام Swift على الويب
إذا كان لديك تطبيق iOS يحتوي على الكثير من الرموز البرمجية التي تريد إعادة استخدامها، استعد لأنّك على وشك بدء رحلة رائعة. إليك بعض النصائح التي قد تجدها مثيرة للاهتمام قبل البدء.
- تحقّق من الرمز الذي تريد إعادة استخدامه. إذا تم تنفيذ منطق النشاط التجاري لتطبيقك على جانب الخادم، من المرجّح أن ترغب في إعادة استخدام رمز واجهة المستخدم، ولن يساعدك Wasm في ذلك. اطّلَع الفريق بشكلٍ موجز على Tokamak، وهو إطار عمل متوافق مع SwiftUI لإنشاء تطبيقات المتصفّحات باستخدام WebAssembly، ولكنّه لم يكن متقدّمًا بما يكفي لتلبية احتياجات التطبيق. ومع ذلك، إذا كان تطبيقك يتضمّن منطق عمل أو خطوات حسابية قوية تم تنفيذها كجزء من رمز العميل، سيكون تنسيق Wasm هو أفضل حلّ لديك.
- تأكَّد من جاهزية قاعدة نصوص Swift البرمجية. ستكون أنماط تصميم البرامج لطبقة واجهة المستخدم أو بنى محددة تُحدِّد فصلاً قويًا بين منطق واجهة المستخدم ومنطق نشاطك التجاري مفيدة جدًا لأنّه لن تتمكّن من إعادة استخدام تنفيذ طبقة واجهة المستخدم. ستكون بنية البرامج البسيطة أو مبادئ البنية السداسية أساسية أيضًا، لأنّك ستحتاج إلى إدخال وتوفير التبعيات لجميع الرموز البرمجية ذات الصلة بعمليات الإدخال/الإخراج، وسيكون من الأسهل إجراء ذلك إذا كنت تتّبع هذه البنى التي يتم فيها تعريف تفاصيل التنفيذ على أنّها تصنيفات مجردة ويتم استخدام مبدأ عكس التبعية بكثرة.
- لا يوفّر تنسيق Wasm رمز واجهة المستخدم. لذلك، حدِّد إطار عمل واجهة المستخدم الذي تريد استخدامه على الويب.
- سيساعدك JSKit في دمج رمز Swift مع JavaScript، ولكن عليك مراعاة أنّه إذا كان لديك مسار سريع، قد يكون استخدام جسر JS-Swift باهظًا وعليك استبداله بدوال مُصدَّرة. يمكنك الاطّلاع على مزيد من المعلومات حول آلية عمل JSKit في المستندات الرسمية وموضوع Dynamic Member Lookup in Swift, a hidden gem! (البحث الديناميكي عن الأعضاء في Swift، جوهرة مخفية).
- يعتمد ما إذا كان بإمكانك إعادة استخدام البنية على البنية التي يتّبعها تطبيقك ومكتبة آلية تنفيذ الرمز غير المتزامن التي تستخدمها. ستساعدك الأنماط، مثل MVVP أو البنية القابلة للتجميع، على إعادة استخدام نماذج العرض وجزء من منطق واجهة المستخدم بدون ربط التنفيذ بتبعيات UIKit التي لا يمكنك استخدامها مع Wasm. قد لا تكون مكتبة RXSwift وغيرها من مكتبات متوافقة مع Wasm، لذا عليك مراعاة ذلك لأنّه عليك استخدام OpenCombine و async/await وعمليات البث في رمز Swift الخاص بتطبيق Goodnotes.
- اضغط ملف Wasm الثنائي باستخدام gzip أو brotli. يُرجى العِلم أنّ حجمملف المعالجة المبرمَجة سيكون كبيرًا جدًا لتطبيقات الويب الكلاسيكية.
- حتى إذا كان بإمكانك استخدام Wasm بدون تطبيق الويب التقدّمي، تأكَّد من تضمين عامل خدمات على الأقل، حتى إذا لم يكن تطبيق الويب يتضمّن بيانًا أو إذا كنت لا تريد أن يثبّته المستخدِم. سيحفظ عامل الخدمة ملف Wasm الثنائي ويعرضه مجانًا، بالإضافة إلى جميع موارد التطبيق كي لا يحتاج المستخدم إلى تنزيلها في كل مرة يفتح فيها مشروعك.
- يُرجى العِلم أنّ عملية التوظيف قد تكون أصعب مما تتوقّع. قد تحتاج إلى توظيف مطوّري ويب بارعين لديهم بعض الخبرة في Swift أو مطوّري Swift بارعين لديهم بعض الخبرة في الويب. سيكون من الرائع العثور على مهندسين عامّين لديهم بعض المعرفة بكلا النظامَين الأساسيَين.
الاستنتاجات
إنّ إنشاء مشروع ويب باستخدام حِزمة تقنية معقّدة أثناء العمل على منتجٍ مليء بالتحديات هو تجربة رائعة. سيكون الأمر صعبًا، ولكنه يستحق العناء. لم يكن بإمكان فريق Goodnotes إصدار إصدار لنظام التشغيل Windows وAndroid وChromeOS والويب أثناء العمل على ميزات جديدة لتطبيق iOS بدون استخدام هذا النهج. بفضل حِزمة التكنولوجيا هذه وفريق مهندسي Goodnotes، أصبح تطبيق Goodnotes متاحًا الآن في كل مكان، والفريق جاهز لمواصلة العمل على مواجهة التحديات التالية. إذا أردت معرفة المزيد من المعلومات عن هذا المشروع، يمكنك مشاهدة محاضرة قدّمها فريق Goodnotes في NSSpain 2023. ننصحك بتجربة Goodnotes للويب.