تجربة ترجمة مشروع كبير من Flow إلى TypeScript

شعار Directum

جافا سكريبت هي واحدة من اللغات مع الكتابة الديناميكية. هذه اللغات ملائمة للتطوير السريع للتطبيق ، لكن عندما تتولى عدة فرق تطوير مشروع كبير ، من الأفضل اختيار واحدة من أدوات التحقق من النوع من البداية.

يمكنك البدء في تطوير كود TypeScript أو تضمينه في مشروع Flow. TypeScript هو إصدار مترجم من JavaScript تم تطويره بواسطة Microsoft. التدفق ، على عكس TypeScript ، ليس لغة ، لكنه أداة تتيح لك تحليل الشفرة والتحقق من الأنواع. يمكنك العثور على العديد من المقالات ومقاطع الفيديو على الشبكة حول هذه الأساليب ، بالإضافة إلى دليل حول كيفية البدء في استخدام الكتابة. في هذه المقالة ، نود أن نخبرك لماذا لم يناسب Flow لنا ، وكيف بدأنا التحول إلى Typescript.

قليلا من التاريخ


في عام 2016 ، بدأنا في تطوير عميل ويب يستند إلى React / Redux لنظام ECM الخاص بنا. تم اختيار Flow لاختبار الكتابة للأسباب التالية:

  1. React و Flow هي منتجات تابعة لشركة Facebook نفسها.
  2. تدفق تطوير أكثر نشاطا.
  3. يتم دمج التدفق بسهولة في المشروع.

لكن المشروع نما ، زاد عدد فرق التطوير ، وظهر عدد من المشكلات عند استخدام Flow:

  1. التحقق من نوع الخلفية يستخدم التدفق الكثير من موارد الكمبيوتر. نتيجة لذلك ، قام بعض المطورين بإيقاف تشغيله وبدأوا في التدقيق حسب الحاجة.
  2. كانت هناك مواقف عندما استغرق الأمر الكثير من الوقت لجعل الشفرة متوافقة مع Flow مثل كتابة الكود نفسه.
  3. بدأت الشفرة تظهر في المشروع ، وهي ضرورية فقط لاجتياز اختبار التدفق. على سبيل المثال ، التحقق المزدوج من وجود قيمة خالية:

    foo() { if (this.activeFormContainer == null) { return; } // to do something if (this.activeFormContainer != null) // only for Flow this.activeFormContainer.style.minWidth = '100px'; } 
  4. استخدم معظم المطورين محرر كود Visual Studio ، حيث لا يتوفر لدى Flow دعمًا جيدًا مثل TypeScript. الإكمال التلقائي (التحسس الذكي) لا يعمل دائمًا أثناء التطوير ، وكان التنقل بالكود غير مستقر. أرغب في الحصول على نفس راحة التطوير كما هو الحال عند الكتابة في C # في Visual Studio.

كان لدى بعض المطورين فكرة محاولة التبديل إلى TypeScript. من أجل اختبار فكرة الانتقال وإقناع الإدارة ، قررنا تجربة النموذج الأولي.

النموذج


أردنا اختبار فكرتين على نماذج أولية:

  1. حاول ترجمة المشروع بالكامل.
  2. قم بإعداد المشروع بحيث يمكنك استخدام كل من Flow و Typescript في نفس الوقت.

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

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

على الرغم من أننا تخلينا عن هذا الخيار ، فقد اكتسبنا خبرة مفيدة بشأنه. أصبح من الواضح الحجم التقريبي للعمل الذي يتعين القيام به لترجمة كل ملف. إليك ما تبدو عليه ترجمة مكون React البسيط.
مقارنة بين التدفق و CodeScript


كما ترون ، لا توجد الكثير من التغييرات. أساسا ، هم على النحو التالي:

  • إزالة // @ التدفق ؛
  • استبدال النوع بواجهة أكثر دراية ؛
  • إضافة معدلات الوصول ؛
  • استبدال الأنواع بأنواع من مكتبات ts (من المثال في الصورة: معالجات الأحداث والأحداث نفسها).

من شأن التنفيذ على الفكرة الثانية أن يسمح بمزيد من التطوير ، ولكن بالفعل على TypeScript وفي الخلفية يترجم ببطء قاعدة الشفرة الحالية. قدم هذا العديد من المزايا:

  1. سهلة الترجمة ، دون خوف من فقدان شيء.
  2. سهل الاختبار.
  3. من السهل دمج التغييرات.

ولكن لم يكن من الواضح تمامًا ما إذا كان يمكن تهيئة المشروع للعمل مع نوعين من الكتابة بالتوازي. البحث على شبكة الإنترنت لم يؤد إلى أي شيء ملموس ، لذلك بدأوا في حلها بأنفسهم. من الناحية النظرية ، يفحص محلل التدفق فقط الملفات ذات امتداد js / jsx ويحتوي على تعليق:

 //@flow  /* @flow */ 

بالنسبة لبرنامج التحويل البرمجي لـ TypeScript ، يجب أن تحتوي الملفات على ملحق ts / tsx. من خلال ذلك ، يجب أن تعمل كلتا الطريقتين في الكتابة في وقت واحد ولا تتداخل مع بعضها البعض. بناءً على ذلك ، أنشأنا بيئة المشروع. باستخدام تجربة النموذج الأولي الأول ، ترجمنا بعض الملفات. جمعت المشروع ، أطلقت العميل - كل شيء يعمل كما كان من قبل!

ضوء أخضر


ويوم واحد - وهو يوم التخطيط السريع ، لدى فريقنا قصة مستخدم "بدء التبديل إلى TypeScript" في الأعمال المتأخرة ، مع قائمة الأعمال التالية:

  1. إعداد webpack.
  2. تكوين tslint.
  3. قم بإعداد بيئة اختبار.
  4. ترجمة الملفات إلى TypeScript.

إعداد Webpack


الخطوة الأولى هي تعليم حزمة الويب كيفية التعامل مع الملفات ذات الملحق ts / tsx. للقيام بذلك ، أضفنا قاعدة إلى قسم القواعد في ملف التكوين. تستخدم في الأصل ts-loader:

 // webpack.config.js const rules = [ ... { test: /\.(ts|tsx)?$/, loader: 'ts-loader', options: { transpileOnly: true } } ]; 

لتسريع التجميع ، تم إيقاف تشغيل تدقيق النوع: transpileOnly: true ، لأن يشير IDE بالفعل إلى الأخطاء أثناء كتابة التعليمات البرمجية.

ولكن عندما بدأنا في ترجمة إجراءات Redux الخاصة بنا ، أصبح من الواضح أنهم يحتاجون إلى المكون الإضافي babel-plugin-convert-class-display-name- work لكي يعمل. يضيف هذا البرنامج المساعد خاصية اسم العرض ثابتة لجميع الفئات. بعد الترجمة ، تمت معالجة إجراءات ts-loader فقط ، وهذا لم يسمح بتطبيق إضافات babel عليها. نتيجة لذلك ، تخلينا عن ts-loader وقمنا بتوسيع القاعدة الحالية لـ js / jsx عن طريق إضافة babel / preset-typescript:

 // webpack.config.js const rules = [ { test: /\.(ts|tsx|js|jsx)?$/, exclude: /node_modules|lib/, loader: 'babel-loader?cacheDirectory=true' }, ... ]; 

 // .babelrc.js const presets = [ [ "@babel/preset-env", { "modules": !isTest ? false : 'commonjs', "useBuiltIns": false } ], "@babel/typescript", "@babel/preset-react", ]; 

لكي يعمل برنامج التحويل البرمجي لـ TypeScript بشكل صحيح ، تحتاج إلى إضافة ملف التكوين tsconfig.json ، لقد تم نقله من الوثائق.

تكوين Tslint


بالإضافة إلى ذلك ، تم فحص الكود المكتوب باستخدام Flow باستخدام eslint. TypeScript له نظيره ، tslint. في البداية ، أردت نقل جميع القواعد من eslint إلى tslint. حدثت محاولة لمزامنة القواعد من خلال البرنامج المساعد tslint-eslint-rules ، لكن معظم القواعد غير مدعومة. من الممكن أيضًا استخدام eslint للتحقق من ملفات ts باستخدام typescript-eslint-parser. ولكن ، لسوء الحظ ، يمكن توصيل محلل واحد فقط بـ eslint. إذا كنت تستخدم محلل ts فقط لجميع أنواع الملفات ، فسيظهر الكثير من الأخطاء الغريبة في كل من ملفات js و ts. نتيجةً لذلك ، استخدمنا مجموعة القواعد الموصى بها ، والتي تم توسيعها وفقًا لمتطلباتنا:

 // tslint.json "extends": ["tslint:recommended", "tslint-react"] 

ترجمة ملف إلى TypeScript


كل شيء جاهز الآن ، ويمكنك البدء في ترجمة الملفات. بادئ ذي بدء ، قررنا نقل مكون React صغير ، والذي يتم استخدامه خلال المشروع. وقع الاختيار على مكون "الزر".

أزرار في المشروع

واجهنا مشكلة أثناء عملية الترجمة: ليست كل مكتبات الأطراف الثالثة بها كتابة TypeScript ، على سبيل المثال ، bem-cn-lite. على مورد TypeSearch من Microsoft ، تعذر العثور على مكتبة الأنواع الخاصة به. بالنسبة إلى جميع المكتبات الضرورية تقريبًا ، وجدنا مكتبات الأنواع ts ومتصلة بها. أحد الحلول هو الاتصال عبر:

 const b = require('bem-cn-lite'); 

ولكن في الوقت نفسه ، لم تحل مشكلة عدم وجود أنواع. لذلك ، أنشأنا "كعب" لأنواع أنفسنا ، وذلك باستخدام الأداة المساعدة dts-gen :

 dts-gen -m bem-cn-lite 

الأداة المساعدة إنشاء ملف بالملحق * .d.ts. تم وضع الملف في مجلدtypes وتكوينه tsconfig.json:

 // tsconfig.json "typeRoots": [ "./@types", "./node_modules/@types" ] 

بعد ذلك ، عن طريق القياس مع النموذج الأولي ، قمنا بترجمة المكون. جمعت المشروع ، أطلقت العميل - كل شيء يعمل! لكن الاختبارات اندلعت.

إعداد بيئة الاختبار


لاختبار التطبيق ، نستخدم Storybook و Mocha.

يستخدم Storybook لاختبار الانحدار البصري ( المادة ). مثل المشروع نفسه ، تم تصميمه باستخدام webpack ولديه ملف التكوين الخاص به. لذلك ، للعمل مع ملفات ts / tsx ، كان لابد من تهيئتها عن طريق القياس مع تكوين المشروع نفسه.

بينما استخدمنا ts-loader لإنشاء المشروع ، توقفنا عن إجراء اختبارات Mocha. لحل هذه المشكلة ، أضف ts-node إلى بيئة الاختبار:

 // mocha.opts --require @babel/polyfill --require @babel/register --require test/index.js --require tsconfig-paths/register --require ts-node/register/transpile-only --recursive --reporter mochawesome --reporter-options reportDir=../../bin/TestResults,reportName=js-test-results,inlineAssets=true --exit 

ولكن بعد التحول إلى بابل ، يمكنك التخلص منه.

المشاكل


في عملية الترجمة ، واجهنا عددًا كبيرًا من المشكلات بدرجات متفاوتة من التعقيد. كانت مرتبطة بشكل أساسي بنقص خبرتنا مع TypeScript. هؤلاء قليل منهم:

  1. استيراد مكونات / وظائف من أنواع الملفات المختلفة.
  2. ترجمة المكونات العليا.
  3. فقدان التغيير التاريخ.

استيراد مكونات / وظائف من أنواع الملفات المختلفة


عند استخدام مكونات / وظائف من أنواع مختلفة من الملفات ، أصبح من الضروري تحديد امتداد الملف:

 import { foo } from './utils.ts' 

يتيح لك ذلك إضافة ملحقات صالحة لملفات تكوين webpack و eslint:

 // webpack.config.js resolve: { … extensions: [ '.tsx', '.ts', '.js' ] } 

 // .eslintrc.js "import/resolver": { "node": { "extensions": [ ".js", ".jsx", ".ts", ".tsx", ".json" ] } } 

ترجمة المكونات العليا


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

 const MyComponentWithSeletedItem = withSelectedItem(MyComponent); 

أو الاتصال الأكثر شهرة ، من مكتبة Redux. كتابة هذه الوظائف ليس تافها ويتطلب توصيل مكتبة إضافية للعمل مع الأنواع. لن أصف عملية الترجمة بالتفصيل ، حيث يمكنك العثور على العديد من الأدلة حول الموضوع على الشبكة. باختصار ، المشكلة هي أن هذه الوظيفة مجردة: يمكن لأي عنصر مع أي مجموعة من الخصائص قبول الإدخال. يمكن أن يكون مكون زر مع خصائص العنوان و onClick أو مكون صورة مع خصائص alt و imgUrl. مجموعة هذه الخصائص غير معروفة لنا مقدمًا ؛ فقط تلك الخصائص التي تضيفها الوظيفة نفسها معروفة. لكي لا يقوم برنامج التحويل البرمجي لـ TypeScript بأداء القسم عند استخدام المكونات التي تم الحصول عليها بمساعدة مثل هذه الوظائف ، من الضروري "قص" الخصائص التي تضيفها الوظيفة من نوع الإرجاع.

للقيام بذلك ، تحتاج إلى:

  1. اسحب هذه الخصائص إلى الواجهة:

     interface IWithSelectItem { selectedItem: number; handleSelectedItemChange: (id: number) => void; } 
  2. قم بإزالة كافة الخصائص التي تدخل واجهة IWithSelectItem من واجهة المكون. للقيام بذلك ، يمكنك استخدام عملية Diff <T، U> من مكتبة أنواع الأدوات المساعدة .

     React.ComponentType<Diff<TPropsComponent, IWithSelectItem>> 

فقدان التغيير التاريخ


للعمل مع المصادر ، على سبيل المثال ، مراجعة الكود ، نستخدم Team Foundation Server. عند ترجمة الملفات ، واجهنا ميزة واحدة غير سارة. بدلاً من ملف واحد تم تغييره ، يظهر اثنان في تجمع الطلبات:

  • عن بعد - الإصدار القديم من الملف ؛
  • تم إنشاؤه - نسخة جديدة.

    كيف يبدو في طلب السحب

يتم ملاحظة هذا السلوك إذا كان هناك العديد من التغييرات في الملف (التشابه <50٪) ، على سبيل المثال ، للملفات الصغيرة. لحل هذه المشكلة ، حاولنا استخدام:

  • بوابة القيادة ام
  • تنفيذ اثنين من الالتزام: الأول هو تغيير امتداد الملف ، والثاني هو مع تصحيحات فورية.

لكن لسوء الحظ ، لم يساعدنا كلا النهجين.

النتائج


استخدم Flow أو TypeScript - حيث يقرر كل شخص بنفسه ، فكلاهما لهما إيجابيات وسلبيات. اخترنا TypeScript لأنفسنا. وكنت مقتنعا من تجربتك الخاصة: إذا اخترت أحد الأساليب وأدركت فجأة ، حتى بعد مرور ثلاث سنوات على أنها لا تناسبك ، يمكنك دائمًا تغييرها. ولتحويل أكثر سلاسة ، يمكنك تكوين المشروع ، مثلنا ، للعمل بشكل متوازٍ.

في وقت كتابة هذا التقرير ، لم نتحول بعد بشكل كامل إلى TypeScript ، لكننا أعدنا بالفعل كتابة الجزء الرئيسي - "جوهر" المشروع. في قاعدة الكود ، يمكنك العثور على أمثلة لترجمة جميع أنواع الملفات ، من مكون رد فعل بسيط إلى مكونات ذات ترتيب أعلى. كما تم إجراء التدريب بين جميع فرق التطوير ، والآن يقوم كل فريق ، كجزء من مهمته ، بنقل جزء من المشروع إلى تلك الواجبات.

نحن نخطط لإكمال الانتقال قبل نهاية العام ، وترجمة الاختبارات وكتاب القصص ، وربما حتى كتابة بعض قواعد tslint الخاصة بنا.

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

Source: https://habr.com/ru/post/ar462055/


All Articles