استراتيجيات الهجرة
تعتبر ترجمة مشروع كبير الحجم من JavaScript إلى TypeScript أمرًا صعبًا. قبل أن نبدأ في حلها ، درسنا استراتيجيتين للتبديل من JS إلى TS.
▍1. استراتيجية الهجرة الهجينة
باستخدام هذا الأسلوب ، يتم تنفيذ ترجمة تدريجية من ملف إلى ملف للمشروع إلى TypeScript. أثناء هذه العملية ، يتم تحرير الملفات وتصحيح أخطاء الكتابة وتعمل بهذه الطريقة حتى تتم ترجمة المشروع بأكمله إلى TS. تتيح لك المعلمة allowJS أن يكون لديك ملفات TypeScript وملفات JavaScript في مشروعك. بفضل هذا ، فإن هذا النهج لترجمة مشاريع JS إلى TS قابل للتطبيق تمامًا.
باستخدام إستراتيجية ترحيل مختلطة ، لا تحتاج إلى إيقاف عملية التطوير مؤقتًا ، يمكنك تدريجيًا ، ملفًا بملف ، ترجمة المشروع إلى TypeScript. ولكن إذا تحدثنا عن مشروع واسع النطاق ، فقد تستغرق هذه العملية وقتًا طويلاً. كما يتطلب تدريبًا للمبرمجين عبر المؤسسة. سيحتاج المبرمجون إلى تعريفهم بتفاصيل المشروع.
▍2. استراتيجية الهجرة الشاملة
يأخذ هذا الأسلوب مشروعًا مكتوبًا بالكامل في JavaScript ، أو جزء منه مكتوب بلغة TypeScript ، ويحوله بالكامل إلى مشروع TypeScript. في هذه الحالة ، ستحتاج إلى استخدام النوع
anyوالتعليقات @ts-ignore، مما سيسمح للمشروع بالتجميع دون أخطاء. ولكن بمرور الوقت ، يمكن تحرير الكود والمضي قدمًا لاستخدام أنواع أكثر ملاءمة.
تتمتع إستراتيجية ترحيل TypeScript الشاملة بالعديد من المزايا الهامة مقارنة بالإستراتيجية المختلطة:
- . , , . , TypeScript, , .
- , . , , , . .
بالنظر إلى ما سبق ، يبدو أن الهجرة المنتشرة تتفوق على الهجرة المختلطة من جميع النواحي. لكن ترجمة قاعدة كود ناضجة إلى TypeScript بطريقة شاملة هي مهمة صعبة للغاية. لحلها ، قررنا اللجوء إلى البرامج النصية لتعديل الكود ، إلى ما يسمى بـ "codemods" ( codemods ). عندما بدأنا لأول مرة في ترجمة مشروع إلى TypeScript ، ونقوم بذلك يدويًا ، لاحظنا عمليات متكررة يمكن أتمتتها. كتبنا تعديلات التعليمات البرمجية لكل من هذه العمليات ودمجناها في خط أنابيب ترحيل واحد.
تخبرنا التجربة أنه لا يمكننا أن نكون متأكدين بنسبة 100٪ أنه بعد الترجمة الآلية للمشروع إلى TypeScript ، لن تكون هناك أخطاء فيه. لكننا اكتشفنا أن مجموعة الخطوات الموضحة أدناه أعطتنا أفضل النتائج ، وفي النهاية ، حصلنا على مشروع TypeScript بدون أخطاء. باستخدام تعديلات التعليمات البرمجية ، تمكنا من ترجمة مشروع إلى TypeScript يحتوي على أكثر من 50000 سطر من التعليمات البرمجية ويمثله أكثر من 1000 ملف. استغرق الأمر منا يومًا واحدًا للقيام بذلك.
بناءً على خط الأنابيب الموضح في الشكل التالي ، أنشأنا أداة ts-migrate.

Ts-migrate codemods
Airbnb لديها جزء كبير من واجهتها الأمامية مكتوب باستخدام React . هذا هو سبب ارتباط بعض أجزاء تعديل الشفرة بمفاهيم خاصة بـ React. يمكن استخدام أداة ts-migrate مع مكتبات أو أطر عمل أخرى ، لكن هذا سيتطلب تكوينًا واختبارًا إضافيًا.
نظرة عامة على عملية الترحيل
دعنا نتصفح الخطوات الرئيسية التي تحتاج إلى اتباعها لترجمة مشروع من JavaScript إلى TypeScript. لنتحدث عن كيفية تنفيذ هذه الخطوات.
▍الخطوة 1
أول شيء ينشئه كل مشروع TypeScript هو ملف
tsconfig.json. يمكن لـ Ts-migrate القيام بذلك بمفرده إذا لزم الأمر. يوجد نموذج قياسي لهذا الملف. بالإضافة إلى ذلك ، يوجد نظام تحقق لضمان تكوين جميع المشاريع بشكل متسق. فيما يلي مثال على التكوين الأساسي:
{
"extends": "../typescript/tsconfig.base.json",
"include": [".", "../typescript/types"]
}
▍الخطوة 2
بمجرد أن يصبح الملف في
tsconfig.jsonالمكان الذي يجب أن يكون فيه ، تتم إعادة تسمية الملفات المصدر. وبالتحديد ، تتغير امتدادات .js / .jsx إلى .ts / .tsx. هذه الخطوة سهلة للغاية لأتمتة. هذا يسمح لك بالتخلص من كمية كبيرة من العمل اليدوي.
▍الخطوة 3
والآن حان الوقت لبدء تعديل التعليمات البرمجية! نسميها الإضافات. المكونات الإضافية لـ ts-migrate هي تعديلات على التعليمات البرمجية يمكنها الوصول إلى معلومات إضافية من خلال خادم لغة TypeScript. تقبل المكونات الإضافية السلاسل كمدخلات وترجع السلاسل المعدلة. يمكن استخدام مربع أدوات jscodeshift أو TypeScript API أو أدوات معالجة السلسلة أو أدوات تعديل AST الأخرى لإجراء تحويلات التعليمات البرمجية .
بعد إكمال كل خطوة من الخطوات المذكورة أعلاه ، نتحقق لمعرفة ما إذا كانت هناك أية تغييرات معلقة في سجل Git وإدراجها في المشروع. يسمح لك هذا بتقسيم العلاقات العامة الخاصة بالترحيل إلى التزامات ، مما يسهل فهم ما يحدث ويساعد على تتبع التغييرات في أسماء الملفات.
نظرة عامة على الحزم التي تتكون منها ts-migrate
نقسم ts-migrate إلى 3 حزم:
من خلال القيام بذلك ، تمكنا من فصل منطق تحويل الكود عن جوهر النظام وتمكنا من إنشاء العديد من التكوينات المصممة لحل المشكلات المختلفة. لدينا الآن اثنين من تكوينات رئيسية هي: الهجرة و reignore .
الغرض من تطبيق التكوين
migrationهو ترجمة المشروع من JavaScript إلى TypeScript. reignoreويتم استخدام التكوين للسماح بتجميع المشروع ببساطة عن طريق تجاهل أي أخطاء. يكون هذا التكوين مفيدًا عندما يكون لديك قاعدة بيانات كبيرة وتقوم بأشياء مختلفة باستخدامه ، مثل ما يلي:
- تحديث إصدار TypeScript.
- إجراء تغييرات كبيرة على الكود أو إعادة هيكلة قاعدة الكود.
- أنواع محسنة لبعض المكتبات شائعة الاستخدام.
باستخدام هذا النهج ، يمكننا ترجمة المشروع إلى TypeScript حتى إذا تم إنشاء أخطاء عند التجميع لا نخطط للتعامل معها على الفور. كما أنه يسهل تحديث TypeScript أو المكتبات المستخدمة في التعليمات البرمجية الخاصة بك.
يعمل كلا التكوينين على خادم يتكون
ts-migrate-serverمن جزأين:
- TSServer : هذا الجزء من الخادم مشابه جدًا لما يستخدمه VSCode للتواصل بين المحرر وخادم اللغة. يبدأ المثيل الجديد لخادم لغة TypeScript في عملية منفصلة. تتفاعل أدوات التطوير معها باستخدام بروتوكول اللغة .
- أداة الترحيل : هذا هو الكود الذي ينفذ عملية الترحيل وينسق هذه العملية. تأخذ هذه الأداة المعلمات التالية:
interface MigrateParams {
rootDir: string; // .
config: MigrateConfig; // ,
// .
server: TSServer; // TSServer.
}
تقوم هذه الأداة بما يلي:
- تحليل الملف
tsconfig.json. - إنشاء ملفات .ts مع شفرة المصدر.
- أرسل كل ملف إلى خادم لغة TypeScript لتشخيص هذا الملف. هناك ثلاثة أنواع من وسائل التشخيص، مما يعطينا المترجم:
semanticDiagnostics،syntacticDiagnosticsوsuggestionDiagnostics. نستخدم هذه الفحوصات للعثور على مناطق المشاكل في شفرة المصدر. استنادًا إلى رمز التشخيص الفريد ورقم السطر في الملف ، يمكننا تحديد نوع المشكلة المحتمل وتطبيق التعديلات اللازمة على الكود. - معالجة كل ملف من خلال جميع الملحقات. إذا تم تغيير النص في الملف بمبادرة من المكون الإضافي ، فإننا نقوم بتحديث محتوى الملف الأصلي وإخطار خادم اللغة بأنه تم تغيير الملف.
ts-migrate-serverيمكن العثور على أمثلة
الاستخدام في حزمة الأمثلة أو في الحزمة الرئيسية . أنه ts-migrate-exampleيحتوي أيضا الأساسية أمثلة المساعد . تقع في 3 فئات رئيسية:
- الإضافات على أساس jscodeshift.
- الإضافات مبنية على TypeScript شجرة التركيب التجريدي (AST).
- ملحقات معالجة النصوص.
يحتوي المستودع على مجموعة من الأمثلة التي تهدف إلى توضيح عملية إنشاء مكونات إضافية بسيطة من كل هذه الأنواع. كما يظهر استخدامها في تركيبة ج
ts-migrate-server. فيما يلي مثال على مسار ترحيل يقوم بتحويل التعليمات البرمجية. يتم استلام الكود التالي عند إدخاله:
function mult(first, second) {
return first * second;
}
ويعطي ما يلي:
function tlum(tsrif: number, dnoces: number): number {
console.log(`args: ${arguments}`);
return tsrif * dnoces;
}
في هذا المثال ، أجرى ts-migrate 3 تحويلات:
- انها عكس ترتيب الأحرف في كل معرفات:
first -> tsrif. - المزيد من المعلومات عن أنواع في تعريف الدالة:
function tlum(tsrif, dnoces) -> function tlum(tsrif: number, dnoces: number): number. - تمت إضافة السطر إلى الكود
console.log(‘args:${arguments}’);
الإضافات للأغراض العامة
توجد المكونات الإضافية الحقيقية في حزمة منفصلة - ts-migrate-plugins . دعونا نلقي نظرة على بعضها. لدينا مكونان إضافيان يعتمدان على jscodeshift:
explicitAnyPluginو declareMissingClassPropertiesPlugin. و الأدوات jscodeshift تسمح لك لتحويل ASTS إلى رمز منتظم باستخدام إعادة صياغة حزمة . يمكننا استخدام الوظيفة toSource()لتحديث شفرة المصدر الموجودة في ملفاتنا مباشرة.
و explicitAnyPlugin المساعد المعلومات يسترجع من خادم لغة نسخة مطبوعة على الآلة الكاتبة عن كل الأخطاء
semanticDiagnosticsوالخطوط التي تم الكشف عن هذه الأخطاء. ثم يضاف نوع التعليق التوضيحي إلى هذه السطور any. يتيح لك هذا الأسلوب إصلاح الأخطاء ، منذ استخدام النوعanyيسمح لك بالتخلص من أخطاء الترجمة.
إليك بعض نماذج التعليمات البرمجية قبل المعالجة:
const fn2 = function(p3, p4) {}
const var1 = [];
إليك نفس الكود الذي تمت معالجته بواسطة البرنامج المساعد:
const fn2 = function(p3: any, p4: any) {}
const var1: any = [];
و declareMissingClassPropertiesPlugin يأخذ كل الرسائل التشخيص مع رمز خطأ
2339(يمكنك تخمين ما هذا الرمز الوسائل ؟) وإذا كان يمكن أن تجد إعلانات فئة مع معرفات في عداد المفقودين، يضيفها إلى الجسم من الطبقة المشروح any. من اسم المكون الإضافي ، يمكننا أن نستنتج أنه ينطبق فقط على فئات ES6 .
تعتمد الفئة التالية من المكونات الإضافية على AST TypeScript. من خلال معالجة AST ، يمكننا إنشاء مجموعة من التحديثات التي يجب إجراؤها على الملف المصدر. تبدو أوصاف هذه التحديثات على النحو التالي:
type Insert = { kind: 'insert'; index: number; text: string };
type Replace = { kind: 'replace'; index: number; length: number; text: string };
type Delete = { kind: 'delete'; index: number; length: number };
بعد إنشاء معلومات حول التحديثات الضرورية ، يبقى فقط إدخالها في الملف بترتيب عكسي. إذا تلقينا رمز برنامج جديد بعد إجراء هذه العملية ، فسنقوم بتحديث ملف الكود المصدري وفقًا لذلك.
دعنا نلقي نظرة على الإضافات التالية المستندة إلى AST. هذا هو
stripTSIgnorePluginو hoistClassStaticsPlugin.
المكوِّن الإضافي stripTSIgnorePlugin هو أول مكون إضافي يتم استخدامه في مسار الترحيل. يزيل جميع التعليقات من الملف.
@ts-ignore(تسمح لنا هذه التعليقات بإخبار المترجم بتجاهل الأخطاء التي تحدث في السطر التالي). إذا كنا نترجم مشروعًا مكتوبًا بلغة JavaScript إلى TypeScript ، فلن يؤدي هذا المكون الإضافي أي إجراء. ولكن إذا كنا نتحدث عن مشروع تمت كتابته جزئيًا في JS وجزئيًا في TS (العديد من مشاريعنا كانت في حالة مماثلة) ، فهذه هي الخطوة الأولى في الترحيل التي لا يمكن الاستغناء عنها. فقط بعد إزالة التعليقات @ts-ignore، سيتمكن مترجم TypeScript من إصدار رسائل خطأ تشخيصية تحتاج إلى الإصلاح.
هذا هو الكود الذي يدخل في إدخال هذا البرنامج المساعد:
const str3 = foo
? // @ts-ignore
// @ts-ignore comment
bar
: baz;
ها هو الناتج:
const str3 = foo
? bar
: baz;
بعد التخلص من التعليقات ،
@ts-ignoreنقوم بتشغيل المكون الإضافي hoistClassStaticsPlugin . يمر من خلال جميع الإعلانات الطبقية. يكتشف المكون الإضافي إمكانية رفع المعرفات أو التعبيرات ويكتشف ما إذا كانت عملية تعيين معينة قد تم رفعها بالفعل إلى مستوى الفصل الدراسي.
من أجل ضمان سرعة تطوير عالية وتجنب التخفيض القسري للإصدارات السابقة من المشروع ، قمنا بتزويد كل مكون إضافي و ts-migrate بمجموعة من اختبارات الوحدة.
المكونات الإضافية ذات الصلة بـ React
يقوم المكون الإضافي responsePropsPlug ، بناءً على هذه الأداة الرائعة ، بتحويل معلومات الكتابة من PropTypes إلى إعلانات TypeScript. باستخدام هذا البرنامج المساعد ، تحتاج فقط إلى معالجة ملفات .tsx التي تحتوي على مكون React واحد على الأقل. يبحث هذا المكون الإضافي عن جميع إعلانات PropTypes ويحاول تحليلها باستخدام ASTs والتعبيرات العادية البسيطة مثل
/number/، أو باستخدام تعبيرات عادية أكثر تعقيدًا مثل / objectOf $ / . عندما تم الكشف عنه رد فعل مكون (وظيفة أو على أساس الطبقة)، وتحويله إلى عنصر في الذي يستخدم نوع جديد لمعلمات الإدخال (الدعائم): type Props = {…};.
البرنامج المساعد ReactDefaultPropsPluginمسؤول عن تنفيذ نمط الدعائم الافتراضي في مكونات React . نستخدم نوعًا خاصًا لتمثيل معلمات الإدخال التي يتم منحها قيمًا افتراضية:
type Defined<T> = T extends undefined ? never : T;
type WithDefaultProps<P, DP extends Partial<P>> = Omit<P, keyof DP> & {
[K in Extract<keyof DP, keyof P>]:
DP[K] extends Defined<P[K]>
? Defined<P[K]>
: Defined<P[K]> | DP[K];
};
نحاول العثور على الخاصيات التي تم تعيين قيم افتراضية لها ، ثم ندمجها مع النوع الذي يصف الخاصيات للمكون الذي أنشأناه في الخطوة السابقة.
يستخدم نظام React البيئي استخدامًا مكثفًا لمفاهيم دورة حياة الحالة والمكونات. نحن نعالج المشاكل المتعلقة بهذه المفاهيم في الإضافات التالية. لذلك ، إذا كان للمكوِّن حالة ، فإن المكوِّن الإضافي ReactClassStatePlugin يُنشئ نوعًا جديدًا (
type State = any;) ، ويقوم المكوِّن الإضافي ReactClassLifecycleMethodsPlug بتوضيح طرق دورة حياة المكوِّن مع الأنواع المقابلة. يمكن توسيع وظائف هذه المكونات الإضافية ، بما في ذلك عن طريق تزويدها بالقدرة على استبدالها anyبأنواع أكثر دقة.
يمكن تحسين هذه المكونات الإضافية ، على وجه الخصوص ، من خلال توسيع دعم النوع للحالة والخصائص. لكن قدراتهم الحالية ، كما اتضح ، هي نقطة انطلاق جيدة لتنفيذ الوظائف التي نحتاجها. بالإضافة إلى ذلك ، نحن لا نعمل مع React hooks هنا ، لأنه في بداية الترحيل ، استخدمت قاعدة الشفرة الخاصة بنا إصدارًا قديمًا من React لا يدعم الخطافات.
التحقق من تجميع المشروع بشكل صحيح
هدفنا هو تجميع مشروع TypeScript مزودًا بأنواع أساسية دون تغيير سلوك البرنامج.
بعد كل التحولات والتعديلات ، قد يتبين أن الكود الخاص بنا غير منسق بشكل موحد ، مما قد يؤدي إلى حقيقة أن بعض عمليات التحقق من الكود باستخدام لينتر تكشف عن أخطاء. تستخدم قاعدة كود الواجهة الأمامية نظامًا يعتمد على Prettier و ESLint. وبالتحديد ، يتم استخدام Prettier لتنسيق الكود تلقائيًا ، ويساعد ESLint في التحقق من التوافق مع أساليب التطوير الموصى بها. كل هذا يسمح لنا بالتعامل بسرعة مع مشاكل تنسيق التعليمات البرمجية الناشئة عن الإجراءات السابقة ، ببساطة عن طريق استخدام البرنامج المساعد المناسب .-
eslintFixPlugin.
تتمثل الخطوة الأخيرة في مسار الترحيل في التحقق من حل جميع مشكلات تجميع TypeScript. من أجل البحث عن الأخطاء المحتملة وإصلاحها ، يأخذ المكون الإضافي tsIgnorePlugin معلومات من التشخيص الدلالي للرمز وأرقام الأسطر ، ثم يضيف تعليقات إلى الكود
@ts-ignoreمع شرح للأخطاء. على سبيل المثال ، قد يبدو كالتالي:
// @ts-ignore ts-migrate(7053) FIXME: No index signature with a parameter of type 'string...
const { field1, field2, field3 } = DATA[prop];
// @ts-ignore ts-migrate(2532) FIXME: Object is possibly 'undefined'.
const field2 = object.some_property;
لقد زودنا النظام بدعم بناء جملة JSX:
{*
// @ts-ignore ts-migrate(2339) FIXME: Property 'NORMAL' does not exist on type 'typeof W... */}
<Text weight={WEIGHT.NORMAL}>
some text
</Text>
<input
id="input"
// @ts-ignore ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'.
name={getName()}
/>
وجود رسائل خطأ ذات مغزى تحت تصرفنا يجعل من السهل إصلاح الأخطاء والعثور على مقتطفات التعليمات البرمجية للبحث عنها.
$TSFixMeتسمح لنا التعليقات ذات الصلة ، جنبًا إلى جنب مع ، بجمع بيانات قيمة حول جودة الشفرة والعثور على أجزاء التعليمات البرمجية التي يحتمل أن تكون مشكلة. $TSFixMeهو نوع الاسم المستعار الذي أنشأناه any. وبالنسبة للوظائف ، هذا هو $TSFixMeFunction = (…args: any[]) => any;. يوصى بتجنب استخدام نوع any، ولكن استخدامه ساعدنا في تبسيط عملية الترحيل. ساعدنا استخدام هذا النوع في معرفة أجزاء التعليمات البرمجية التي تحتاج إلى تحسين بالضبط.
من الجدير بالذكر أن البرنامج المساعد
eslintFixPluginيعمل مرتين. أول مرة قبل الاستخدامtsIgnorePluginلأن التنسيق يمكن أن يؤثر على الرسائل المتعلقة بمكان حدوث أخطاء الترجمة. المرة الثانية بعد التطبيق tsIgnorePlugin، حيث أن إضافة التعليقات إلى الكود @ts-ignoreيمكن أن يؤدي إلى أخطاء في التنسيق.
ملاحظات إضافية
نود أن نلفت انتباهك إلى اثنين من ميزات الترحيل التي لاحظناها أثناء العمل. ربما تكون معرفة هذه الميزات مفيدة عند العمل مع مشاريعك.
- TypeScript 3.7 @ts-nocheck, TypeScript- . , .js-, .ts/.tsx-. , .
- يقدم TypeScript 3.9 دعمًا لتعليقات @ ts- due -error . إذا كان سطر من التعليمات البرمجية مسبوقًا بمثل هذا التعليق ، فلن يبلغ TypeScript عن الخطأ المقابل. إذا لم يكن هناك خطأ في مثل هذا السطر ، فستعلمك TypeScript
@ts-expect-errorبعدم الحاجة إلى التعليق . انتقلت قاعدة بيانات Airbnb من التعليقات@ts-ignoreإلى التعليقات@ts-expect-error.
النتيجة
لا يزال ترحيل قاعدة بيانات Airbnb من JavaScript إلى TypeScript مستمرًا. لدينا بعض المشاريع القديمة التي لا تزال ممثلة بشفرة JavaScript.
$TSFixMeالتعليقات لا تزال شائعة في قاعدة الرموز لدينا @ts-ignore.

JavaScript و TypeScript في Airbnb
ولكن تجدر الإشارة إلى أن استخدام ts- migrate أدى بشكل كبير إلى تسريع عملية ترجمة مشاريعنا من JS إلى TS وتحسين إنتاجية عملنا بشكل كبير. باستخدام ts-migrate ، تمكن المبرمجون من التركيز على تحسين الكتابة بدلاً من معالجة كل ملف يدويًا. حاليًا ، تتم ترجمة ما يقرب من 86٪ من مستودعنا الأحادي الأمامي ، والذي يحتوي على حوالي 6 ملايين سطر من التعليمات البرمجية ، إلى TypeScript. نتوقع أن تصل إلى 95٪ بنهاية هذا العام.
هنا في الصفحة الرئيسية لمستودع المشروع ، يمكنك معرفة كيفية تثبيت وتشغيل ts-migrate. إذا وجدت أي مشاكل في ts-migrate، أو إذا كانت لديك أفكار لتحسين هذه الأداة ، فنحن ندعوك للانضمام.للعمل عليها!
هل سبق لك أن ترجمت مشاريع كبيرة من JavaScript إلى TypeScript؟
