DevTools のアーキテクチャ更新: DevTools から TypeScript への移行

Tim van der Lippe
Tim van der Lippe

この記事は、DevTools のアーキテクチャとその構築方法に対する変更について説明する一連のブログ投稿の一部です。

JavaScript モジュールへの移行ウェブ コンポーネントへの移行に続いて、今回は Devtools のアーキテクチャとその構築方法に対する変更に関するブログ投稿シリーズの続きをお届けします。(まだご覧いただいていない場合は、DevTools のアーキテクチャをモダン ウェブにアップグレードするという動画をご覧ください。ウェブ プロジェクトを改善するための 14 のヒントが紹介されています)。

この記事では、Closure Compiler の型チェッカーから TypeScript に移行する 13 か月間の取り組みについて説明します。

はじめに

DevTools のコードベースのサイズと、そのコードベースを担当するエンジニアに信頼性を提供する必要性を考えると、型チェッカーの使用は必須です。そのため、2013 年に DevTools で Closure Compiler を採用しました。Closure を採用したことで、DevTools エンジニアは安心して変更を加えることができるようになりました。Closure コンパイラは型チェックを実行し、すべてのシステム統合が適切に型付けされていることを確認します。

しかし、時代が進むにつれ、代替の型チェッカーが最新のウェブ開発で普及しました。代表的な例として、TypeScriptFlow があります。さらに、TypeScript は Google の公式プログラミング言語になりました。これらの新しい型チェッカーの人気が高まる一方で、型チェッカーで検出されるはずの回帰がリリースされていることも判明しました。そのため、タイプチェッカーの選択を再評価し、DevTools の開発の次のステップを検討することにしました。

型チェッカーの評価

DevTools ではすでに型チェッカーが使用されていたため、次の質問に答える必要がありました。

Closure Compiler を引き続き使用するか、新しい型チェッカーに移行するか

この質問に答えるには、いくつかの特性について型チェッカーを評価する必要がありました。Google では、型チェッカーの使用はエンジニアの信頼に重点を置いているため、最も重要な要素は型の正確性です。つまり、型チェッカーは実際の問題を検出できる信頼性があるか?ということになります。

評価では、リリースされた回帰と、その根本原因を特定することに重点を置きました。ここでの前提は、Closure Compiler をすでに使用していたため、Closure ではこれらの問題が検出されなかったということです。したがって、他の型チェッカーで検出できたかどうかを判断する必要があります。

TypeScript での型の正確性

TypeScript は Google で正式にサポートされているプログラミング言語であり、急速に普及しているため、まず TypeScript を評価することにしました。TypeScript は興味深い選択でした。TypeScript チーム自体が DevTools をテスト プロジェクトの一つとして使用し、JavaScript の型チェックとの互換性をトラッキングしています。ベースライン リファレンス テストの出力では、TypeScript が大量の型の問題を検出していることが示されていました。これらの問題は、Closure コンパイラでは必ずしも検出されませんでした。これらの問題の多くは、リリースされたリグレッションの根本原因である可能性がありました。これにより、TypeScript が DevTools の有効なオプションになる可能性があると判断しました。

JavaScript モジュールへの移行中、Closure Compiler で以前よりも多くの問題が検出されることがわかりました。標準モジュール形式に移行したことで、Closure がコードベースを理解する能力が向上し、タイプチェッカーの有効性が向上しました。ただし、TypeScript チームは、JavaScript モジュールの移行より前の DevTools のベースライン バージョンを使用していました。そのため、JavaScript モジュールへの移行によって、TypeScript コンパイラが検出するエラーの量も減ったかどうかを把握する必要がありました。

TypeScript の評価

DevTools は 10 年以上前から存在し、かなりのサイズで機能豊富なウェブ アプリケーションに成長しました。このブログ投稿の執筆時点で、DevTools には約 15 万行のファーストパーティ JavaScript コードが含まれています。ソースコードで TypeScript コンパイラを実行したところ、エラーの量が膨大でした。TypeScript コンパイラはコード解決に関連するエラーを減らしていましたが(約 2,000 件のエラー)、コードベースには型の互換性に関連する 6,000 件のエラーがまだ残っていることがわかりました。

この結果、TypeScript は型の解決方法を理解できたものの、コードベースに大量の型の非互換が見つかりました。これらのエラーを手動で分析した結果、TypeScript は(ほとんどの場合)正しいことが判明しました。TypeScript がこれらの問題を検出できたのに Closure が検出できなかったのは、Closure コンパイラは多くの場合、型を Any と推論するのに対し、TypeScript は代入に基づいて型推論を行い、より正確な型を推論したためです。そのため、TypeScript はオブジェクトの構造を理解し、問題のある使用方法を検出する点で、確かに優れていました

重要な点として、DevTools で Closure コンパイラを使用すると、@unrestricted が頻繁に使用されます。クラスに @unrestricted アノテーションを付けると、その特定のクラスに対する Closure コンパイラの厳格なプロパティ チェックが事実上無効になります。つまり、デベロッパーは型安全性なしでクラス定義を自由に拡張できます。DevTools のコードベースで @unrestricted が広く使用されていた理由に関する過去の文脈は見つかりませんでしたが、その結果、コードベースの大部分で Closure コンパイラが安全性の低いモードで実行されていました。

TypeScript が検出した型エラーとリグレッションのクロス分析でも重複が確認されたため、(型自体が正しい場合)TypeScript によってこれらの問題を防ぐことができた可能性があると判断しました。

any 通話を発信する

この時点で、Closure Compiler の使用を改善するか、TypeScript に移行するかを決定する必要がありました。(Flow は Google でも Chromium でもサポートされていないため、このオプションは使用できませんでした)。JavaScript/TypeScript ツールの開発に携わる Google エンジニアとの話し合いと推奨に基づき、TypeScript コンパイラを選択しました。(最近、Puppeteer を TypeScript に移行する方法に関するブログ投稿も公開しました)。

TypeScript コンパイラを選択した主な理由は、型の正確性の向上でしたが、Google 内部の TypeScript チームからのサポートや、interfaces などの TypeScript 言語の機能(JSDoc の typedefs とは対照的)もメリットでした。

TypeScript コンパイラを選択したことで、DevTools のコードベースとその内部アーキテクチャに多大な投資が必要になりました。そのため、TypeScript への移行には少なくとも 1 年が必要と見積もりました(2020 年第 3 四半期を目標)。

移行を実行する

残された最大の問題は、TypeScript にどのように移行するかでした。コードは 15 万行あり、一度に移行することはできません。また、コードベースで TypeScript を実行すると、数千ものエラーが検出されることがわかっていました。

複数のオプションを検討しました。

  1. すべての TypeScript エラーを取得し、「ゴールド」出力と比較する。このアプローチは、TypeScript チームのアプローチに似ています。このアプローチの最大の欠点は、数十人のエンジニアが同じコードベースで作業するため、マージの競合が発生する頻度が高いことです。
  2. 問題のあるタイプをすべて any に設定します。これにより、基本的に TypeScript でエラーが抑制されます。移行の目標は型の正確性であり、抑制によってこの目標が損なわれるため、このオプションは選択しませんでした。
  3. すべての TypeScript エラーを手動で修正します。これには、何千ものエラーを修正する必要があり、時間がかかります。

予想される作業量は膨大でしたが、オプション 3 を選択しました。このオプションを選択した理由は他にもあります。たとえば、すべてのコードを監査し、実装を含むすべての機能を 10 年に 1 回審査できるようになります。ビジネスの観点から、新しい価値を提供しているのではなく、現状を維持しているに過ぎませんでした。これにより、オプション 3 を正しい選択として正当化することが難しくなりました。

ただし、TypeScript を採用することで、特にリグレッションに関する将来の問題を防止できると強く信じていました。そのため、主張は「新しいビジネス価値を追加している」というより、「獲得したビジネス価値を失わないよう確実に行っている」という内容でした。

TypeScript コンパイラの JavaScript サポート

賛同を得て、Closure コンパイラと TypeScript コンパイラの両方を同じ JavaScript コードで実行する計画を策定した後、小さなファイルから始めました。私たちのアプローチは主にボトムアップでした。コアコードから始めて、上位パネルに到達するまでアーキテクチャを上に移動しました。

DevTools のすべてのファイルに @ts-nocheck を事前に追加することで、作業を並列化できました。「TypeScript を修正する」プロセスでは、@ts-nocheck アノテーションを削除し、TypeScript が検出するエラーを解決します。これにより、各ファイルがチェックされ、できるだけ多くの型の問題が解決されたことを確認できました。

一般的に、このアプローチは問題なく機能しました。TypeScript コンパイラでいくつかのバグが発生しましたが、ほとんどはわかりにくいものでした。

  1. any を返す関数型のオプション パラメータが必須として扱われる: #38551
  2. クラスの静的メソッドへのプロパティの割り当てが宣言を破る: #38553
  3. 引数なしのコンストラクタを持つサブクラスと引数コンストラクタを持つスーパークラスの宣言で、子コンストラクタが省略される: #41397

これらのバグは、99% のケースで TypeScript コンパイラが堅固な基盤であることを示しています。はい。これらの不明瞭なバグは、DevTools で問題を引き起こすことがありましたが、ほとんどの場合、不明瞭なバグは簡単に回避できました。

混乱を招いていた唯一の問題は、.tsbuildinfo ファイルの出力が非決定的だったことです(#37156)。Chromium では、同じ Chromium commit の 2 つのビルドでまったく同じ出力になることを要求しています。残念ながら、Chromium ビルド エンジニアは、.tsbuildinfo の出力が非確定的であることを発見しました(crbug.com/1054494)。この問題を回避するには、.tsbuildinfo ファイル(基本的に JSON が含まれている)にモンキーパッチを適用し、ポストプロセッシングして確定的な出力を返す必要がありました(https://crrev.com/c/2091448)。幸い、TypeScript チームがアップストリームの問題を解決し、すぐに回避策を削除できました。バグレポートを受け入れ、問題を迅速に修正していただいた TypeScript チームに感謝いたします。

全体として、TypeScript コンパイラの(型の)正確性には満足しています。大規模なオープンソースの JavaScript プロジェクトである Devtools が、TypeScript での JavaScript サポートの確固たる基盤となることを願っています。

事後分析

これらの型エラーの解決に大きな進展があり、TypeScript によってチェックされるコードの量を徐々に増やすことができました。しかし、2020 年 8 月(この移行開始から 9 か月後)にチェックインしたところ、現在のペースでは期限に間に合わないことが判明しました。エンジニアの 1 人が、この移行に付けた名前である「TypeScriptification」の進捗状況を示す分析グラフを作成しました。

TypeScript の移行の進捗状況

TypeScript の移行の進捗状況 - 移行が必要な残りのコード行のトラッキング

残り行がゼロになる時期の見積もりは、2021 年 7 月から 2021 年 12 月までと幅があり、期限からほぼ 1 年後でした。マネージャーや他のエンジニアと話し合った結果、TypeScript コンパイラ サポートへの移行に取り組むエンジニアの数を増やすことに同意しました。これは、複数のエンジニアが複数の異なるファイルに作業しても競合しないように、移行を並列化できるように設計したためです。

この時点で、TypeScript への変換プロセスはチーム全体の取り組みになりました。追加の支援により、移行は開始から 13 か月後の 2020 年 11 月末に完了しました。これは、当初の見積もりよりも 1 年以上早い完了です。

合計で、18 人のエンジニアが 771 個の変更リスト(pull リクエストに似ている)を送信しました。トラッキング バグ(https://crbug.com/1011811)には 1,200 件を超えるコメントが寄せられています(ほとんどが変更リストからの自動投稿です)。トラッキング用シートには、TypeScript に変換するすべてのファイル、その割り当て先、TypeScript に変換された変更リストの行が 500 行以上ありました。

TypeScript コンパイラのパフォーマンスの影響を軽減する

現在、Google が取り組んでいる最大の問題は、TypeScript コンパイラのパフォーマンスの低さです。Chromium と DevTools を構築するエンジニアの数を考えると、このボトルネックにはコストがかかります。残念ながら、移行前にこのリスクを特定することはできず、ファイルの大部分を TypeScript に移行した時点で、Chromium ビルド全体で費やされる時間が著しく増加していることが判明しました。https://crbug.com/1139220

本件は、Microsoft TypeScript コンパイラ チームに報告いたしましたが、残念ながら、この動作は意図的であると判断されました。Google は、この問題について Chromium 側で再検討を希望しておりますが、それまでの間、パフォーマンスの低下による影響をできるだけ軽減できるよう取り組んでおります。

残念ながら、現在利用可能なソリューションは、Google 社員以外のコントリビューターには適切でない場合があります。Chromium へのオープンソースの貢献(特に Microsoft Edge チームからの貢献)は非常に重要であるため、Google はすべてのコントリビュータに適した代替手段を積極的に模索しています。ただし、現時点では適切な代替ソリューションを見いだせていません。

DevTools での TypeScript の現在の状態

現時点では、コードベースから Closure コンパイラ タイプチェッカーを削除し、TypeScript コンパイラのみに依存しています。TypeScript で作成されたファイルを記述し、TypeScript 固有の機能(インターフェース、ジェネリクスなど)を利用できるため、日常業務に役立ちます。TypeScript コンパイラが型エラーとリグレッションを検出する信頼性が高まりました。これは、この移行作業を最初に開始したときに期待していたことです。多くの移行と同様に、この移行は時間がかかり、微妙な調整が必要で、多くの場合困難でしたが、メリットが得られるため、それだけの価値があったと考えています。

プレビュー チャネルをダウンロードする

デフォルトの開発用ブラウザとして Chrome の CanaryDevBeta のいずれかを使用することを検討してください。これらのプレビュー チャンネルでは、最新の DevTools 機能にアクセスしたり、最先端のウェブ プラットフォーム API をテストしたりできます。また、ユーザーよりも先にサイトの問題を見つけることもできます。

Chrome DevTools チームに問い合わせる

次のオプションを使用して、DevTools の新機能、アップデート、その他のトピックについて話し合います。