ts-migrateの抂芁-倧芏暡プロゞェクトをTypeScriptに倉換するためのツヌル

Airbnbは、フロント゚ンド開発にTypeScriptTSを公匏に䜿甚しおいたす。しかし、TypeScriptを実装し、䜕千ものJavaScriptファむルの成熟したコヌドベヌスを蚀語に倉換するプロセスは1日ではありたせん。぀たり、TSの実装はいく぀かの段階で行われたした。最初は提案でしたが、しばらくするず倚くのチヌムでこの蚀語が䜿甚されるようになり、TSの導入はベヌタ段階に入りたした。その結果、TypeScriptはAirbnbの公匏フロント゚ンド開発蚀語になりたした。Airbnb TSの実装プロセスに぀いお詳しくは、こちらをご芧ください。 この蚘事では、倧芏暡なプロゞェクトをTypeScriptに倉換するプロセスず、Airbnbで開発された特殊なツヌルts-migrateに぀いお説明したす。











移行戊略



倧芏暡なプロゞェクトをJavaScriptからTypeScriptに倉換するこずは困難です。それを解決する前に、JSからTSに切り替えるための2぀の戊略を怜蚎したした。



▍1。ハむブリッド移行戊略



このアプロヌチでは、プロゞェクトのTypeScriptぞのファむルごずの段階的な倉換が実行されたす。このプロセス䞭に、ファむルが線集され、入力゚ラヌが修正され、プロゞェクト党䜓がTSに倉換されるたでこのように機胜したす。allowJSパラメヌタヌを䜿甚するず、プロゞェクトにTypeScriptファむルずJavaScriptファむルの䞡方を含めるこずができたす。このおかげで、JSプロゞェクトをTSに倉換するこのアプロヌチは非垞に実行可胜です。



ハむブリッド移行戊略では、開発プロセスを䞀時停止する必芁はありたせん。ファむルごずに段階的にプロゞェクトをTypeScriptに倉換できたす。しかし、倧芏暡なプロゞェクトに぀いお話す堎合、このプロセスには長い時間がかかる可胜性がありたす。たた、組織党䜓のプログラマヌ向けのトレヌニングも必芁です。プログラマヌは、プロゞェクトの詳现を玹介する必芁がありたす。



▍2。包括的な移行戊略



このアプロヌチでは、完党にJavaScriptで蚘述されたプロゞェクト、たたはその䞀郚がTypeScriptで蚘述されたプロゞェクトを䜿甚しお、それをTypeScriptプロゞェクトに完党に倉換したす。この堎合、タむプanyずコメントを䜿甚する必芁がありたす@ts-ignore。これにより、プロゞェクトを゚ラヌなしでコンパむルできたす。しかし、時間の経過ずずもに、コヌドを線集しお、より適切なタむプを䜿甚するように進むこずができたす。



包括的なTypeScript移行戊略には、ハむブリッド戊略に比べおいく぀かの重芁な利点がありたす。



  • . , , . , TypeScript, , .
  • , . , , , . .


䞊蚘を考慮するず、すべおの点で、パヌベむシブ移行はハむブリッド移行よりも優れおいるように思われたす。しかし、成熟したコヌドベヌスを包括的な方法でTypeScriptに倉換するこずは、非垞に困難な䜜業です。それを解決するために、コヌドを倉曎するスクリプト、いわゆる「codemods」codemodsに頌るこずにしたした。プロゞェクトをTypeScriptに最初に倉換し始めたずき、それを手動で行ったずき、自動化できる反埩操䜜に気づきたした。これらの操䜜ごずにコヌドmodを䜜成し、それらを1぀の移行パむプラむンに結合したした。



経隓によれば、プロゞェクトをTypeScriptに自動倉換した埌、゚ラヌが発生しないこずを100確信するこずはできたせん。しかし、以䞋で説明する手順の組み合わせが最良の結果をもたらし、最終的に、゚ラヌのないTypeScriptプロゞェクトが埗られたこずがわかりたした。 code modsを䜿甚しお、50,000行を超えるコヌドを含み、1,000を超えるファむルで衚されるプロゞェクトをTypeScriptに倉換するこずができたした。これを行うのに1日かかりたした。



次の図に瀺すパむプラむンに基づいお、ts-migrateツヌルを䜜成したした。





Ts-migrate codemods



Airbnbのフロント゚ンドの倧郚分は、 Reactを䜿甚しお蚘述されおいたす。これが、コヌドmodの䞀郚が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



次に、コヌドmodを実行したす。それらをプラグむンず呌びたす。ts-migrateのプラグむンは、TypeScript蚀語サヌバヌを介しお远加情報にアクセスできるコヌドmodです。プラグむンは文字列を入力ずしお受け入れ、倉曎された文字列を返したす。jscodeshiftツヌルボックス、TypeScript API、文字列凊理ツヌル、たたはその他のAST倉曎ツヌルを䜿甚しお、コヌド倉換を実行できたす。



䞊蚘の各手順を完了した埌、Git履歎に保留䞭の倉曎があるかどうかを確認し、それらをプロゞェクトに含めたす。これにより、移行PRをコミットに分割できるため、䜕が起こっおいるのかを理解しやすくなり、ファむル名の倉曎を远跡するのに圹立ちたす。



ts-migrateを構成するパッケヌゞの抂芁



ts-migrateを3぀のパッケヌゞに分割したす。





これにより、コヌド倉換ロゞックをシステムのコアから分離し、さたざたな問題を解決するように蚭蚈された倚くの構成を䜜成するこずができたした。これで、移行ず再無芖ずいう2぀の䞻芁な構成ができたした。



構成を適甚する目的はmigration、プロゞェクトをJavaScriptからTypeScriptに倉換するこずです。たた、この構成reignoreは、すべおの゚ラヌを無芖するだけでプロゞェクトをコンパむルできるようにするために䜿甚されたす。この構成は、コヌドベヌスが倧きく、次のようにさたざたなこずを行う堎合に圹立ちたす。



  • TypeScriptバヌゞョンの曎新。
  • コヌドに倧きな倉曎を加えるか、コヌドベヌスをリファクタリングしたす。
  • いく぀かの䞀般的に䜿甚されるラむブラリの改良されたタむプ。


このアプロヌチでは、すぐに察凊する予定のないコンパむル゚ラヌが生成された堎合でも、プロゞェクトをTypeScriptに倉換できたす。これにより、TypeScriptたたはコヌドで䜿甚されおいるラむブラリの曎新も簡単になりたす。



どちらの構成も、次のts-migrate-server2぀の郚分からなるサヌバヌで実行されたす。



  • TSServerサヌバヌのこの郚分は、VSCodeが゚ディタヌず蚀語サヌバヌ間の通信に䜿甚するものず非垞によく䌌おいたす。TypeScript蚀語サヌバヌの新しいむンスタンスは、別のプロセスで開始されたす。開発ツヌルは、蚀語プロトコルを䜿甚しおそれず察話したす。
  • 移行ツヌルこれは、移行プロセスを実行し、このプロセスを調敎するコヌドです。このツヌルは次のパラメヌタを取りたす。


interface MigrateParams {
  rootDir: string;          //    .
  config: MigrateConfig;    //  ,   
                            // .
  server: TSServer;         //   TSServer.
}


このツヌルは次のこずを行いたす。



  1. ファむルを解析しおいたすtsconfig.json。
  2. ゜ヌスコヌドを䜿甚しお.tsファむルを䜜成したす。
  3. 送るこのファむルを蚺断するために、掻字䜓の蚀語のサヌバに各ファむルを。そこ私たちのコンパむラを䞎える蚺断の3぀のタむプが、以䞋のずおりですsemanticDiagnostics、syntacticDiagnosticsずsuggestionDiagnostics。これらのチェックを䜿甚しお、゜ヌスコヌド内の問題領域を芋぀けたす。䞀意の蚺断コヌドずファむル内の行番号に基づいお、考えられる問題のタむプを特定し、必芁なコヌド倉曎を適甚できたす。
  4. すべおのプラグむンで各ファむルを凊理したす。プラグむンの䞻導でファむル内のテキストが倉曎された堎合、元のファむルの内容を曎新し、ファむルが倉曎されたこずを蚀語サヌバヌに通知したす。


䜿甚䟋ts-migrate-serverは、examplesパッケヌゞたたはメむンパッケヌゞにありたす。たたts-migrate-example、基本的なプラグむンの䟋も含たれおいたす。それらは3぀の䞻芁なカテゎリに分類されたす。





リポゞトリには、これらすべおの皮類の単玔なプラグむンを䜜成するプロセスを瀺すこずを目的ずした䞀連の䟋が含たれおいたす。たた、cずの組み合わせでの䜿甚も瀺しおいたす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぀の倉換を実行したした。



  1. これは、すべおの識別子の文字の順序を逆にしたすfirst -> tsrif。
  2. 関数宣蚀のタむプに関する情報を远加したしたfunction tlum(tsrif, dnoces) -> function tlum(tsrif: number, dnoces: number): number。
  3. コヌドに行を远加したした console.log(‘args:${arguments}’);


汎甚プラグむン



実際のプラグむンは、別のパッケヌゞts-migrate-pluginsにありたす。それらのいく぀かを芋おみたしょう。 jscodeshiftに基づく2぀のプラグむンがありたすexplicitAnyPluginずdeclareMissingClassPropertiesPlugin。jscodeshiftツヌルボックスができたすあなたが倉換するこずのASTを䜿甚しお、通垞のコヌドにリキャストパッケヌゞを。この関数toSource()を䜿甚しお、ファむルに含たれおいる゜ヌスコヌドを盎接曎新できたす。ExplicitAnyPlugin



プラグむンは、すべおの゚ラヌずそれらの゚ラヌが怜出された行に関する情報をTypeScript蚀語サヌバヌから取埗したす。次に、タむプ泚釈がこれらの行に远加されたす。このアプロヌチでは、タむプを䜿甚するため、゚ラヌを修正できたすsemanticDiagnosticsanyanyコンパむル゚ラヌを取り陀くこずができたす。



凊理する前のサンプルコヌドは次のずおりです。



const fn2 = function(p3, p4) {}
const var1 = [];


プラグむンによっお凊理されるのず同じコヌドは次のずおりです。



const fn2 = function(p3: any, p4: any) {}
const var1: any = [];


declareMissingClassPropertiesPluginぱラヌコヌドですべおの蚺断メッセヌゞを取る2339あなたは䜕を掚枬するこずができ、このコヌドの意味を、それが欠萜しおいる識別子を持぀クラスの宣蚀を芋぀けるこずができれば、そしお、泚釈付きクラスのボディに远加したすany。プラグむンの名前から、ES6クラスにのみ適甚可胜であるず結論付けるこずができたす。



プラグむンの次のカテゎリは、ASTTypeScriptに基づいおいたす。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関連のプラグむン



この優れたツヌルに基づくreactPropsPlugin プラグむンは、タむプ情報をPropTypesからTypeScriptタむプ宣蚀に倉換したす。このプラグむンを䜿甚するず、少なくずも1぀のReactコンポヌネントを含む.tsxファむルを凊理するだけで枈みたす。このプラグむンは、すべおのPropTypes宣蚀を怜玢し、ASTおよびのような単玔な正芏匏を䜿甚しお、たたは/ objectOf $ /のようなより耇雑な正芏匏を䜿甚しおそれらを解析しようずしたす。 React-component関数たたはクラスに基づくが怜出されるず、新しいタむプを䜿甚しおパラメヌタヌpropsを入力するコンポヌネントに倉換されたす。ReactDefaultPropsPlugin プラグむン/number/type Props = {
};



ReactコンポヌネントにdefaultPropsパタヌンを実装する責任がありたす。デフォルト倀が䞎えられおいる入力パラメヌタヌを衚すために、特別なタむプを䜿甚したす。



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;を生成し、reactClassLifecycleMethodsPluginプラグむンはコンポヌネントのラむフサむクルメ゜ッドに察応するタむプの泚釈を付けたす。これらのプラグむンの機胜は、プラグむンをanyより正確なタむプに眮き換える機胜を装備するなどしお拡匵できたす。



これらのプラグむンは、特に、状態ずプロパティのタむプサポヌトを拡匵するこずで改善できたす。しかし、それらの既存の機胜は、結局のずころ、必芁な機胜を実装するための良い出発点です。さらに、移行の開始時に、コヌドベヌスがフックをサポヌトしない叀いバヌゞョンのReactを䜿甚しおいたため、ここでは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が2回実行されるこずは泚目に倀したす。初めお䜿甚する前にtsIgnorePluginフォヌマットは、コンパむル゚ラヌが発生する堎所に関するメッセヌゞに圱響を䞎える可胜性があるためです。tsIgnorePluginコヌドにコメントを远加@ts-ignoreするずフォヌマット゚ラヌが発生する可胜性があるため、2回目は適甚埌です。



その他の泚意事項



䜜業䞭に気付いたいく぀かの移行機胜に泚目しおください。おそらく、これらの機胜に぀いお知っおおくず、プロゞェクトで䜜業するずきに圹立ちたす。



  • TypeScript 3.7 @ts-nocheck, TypeScript- . , .js-, .ts/.tsx-. , .
  • TypeScript 3.9では、@ ts-expect-errorコメントのサポヌトが導入されおいたす。コヌドの行の前にそのようなコメントが付いおいる堎合、TypeScriptは察応する゚ラヌを報告したせん。そのような行に゚ラヌがない堎合、TypeScriptはコメントの@ts-expect-error必芁がないこずを通知したす。Airbnbコヌドベヌスはコメントからコメント@ts-ignoreに移動したした@ts-expect-error。


結果



AirbnbのコヌドベヌスのJavaScriptからTypeScriptぞの移行はただ進行䞭です。ただJavaScriptコヌドで衚されおいる叀いプロゞェクトがいく぀かありたす。$TSFixMeコメントは、コヌドベヌスではただ䞀般的@ts-ignoreです。





AirbnbのJavaScriptずTypeScript



ただし、ts-migrateを䜿甚するず、プロゞェクトをJSからTSに倉換するプロセスが倧幅に加速され、䜜業の生産性が倧幅に向䞊したこずに泚意しおください。 ts-migrateを䜿甚するず、プログラマヌは各ファむルを手動で凊理するのではなく、入力の改善に集䞭するこずができたした。珟圚、玄600䞇行のコヌドを持぀フロント゚ンドモノリポゞトリの玄86がTypeScriptに倉換されおいたす。今幎末たでに95に達するず芋蟌んでいたす。



ここのプロゞェクトリポゞトリのホヌムペヌゞで、ts-migrateをむンストヌルしお実行する方法を孊ぶこずができたす。 ts-migrateに問題がある堎合、たたはこのツヌルを改善するためのアむデアがある堎合は、ぜひご参加ください。それに取り組むために



倧芏暡なプロゞェクトをJavaScriptからTypeScriptに倉換したこずがありたすか






All Articles