Puppeteer から TypeScript への移行

私たちは DevTools チームで TypeScript を大いに気に入っています。そのため DevTools の新しいコードが TypeScript で作成されており、コードベース全体を TypeScript による型チェックへと大規模に移行しつつあります。この移行について詳しくは、Chrome Dev Summit 2020 での Google の講演をご覧ください。そのため、Puppeteer のコードベースを TypeScript に移行することも理にかなっています。

移行の計画

移行方法を計画する際には、段階的に進められるようにしました。これにより、移行のオーバーヘッドが抑えられ、常にコードのごく一部しか扱えなくなります。また、リスクも抑えられます。いずれかの手順で問題が発生した場合は、簡単に元に戻すことができます。Puppeteer には多くのユーザーがおり、リリースに問題があると多くのユーザーに問題が発生するため、破壊的な変更のリスクを最小限に抑えることが重要でした。

また、Puppeteer には、すべての機能を網羅する堅牢な単体テストセットが用意されているという幸運もありました。これにより、移行中にコードが破損していないこと、また API に変更が加えられていないことを確認できました。この移行の目標は、Puppeteer ユーザーが移行したことに気付かれることなく移行を完了することでした。テストはその戦略の重要な部分でした。テスト カバレッジが十分でなかった場合は、移行を続行する前に追加していたでしょう。

テストなしでコードを変更することはリスクがありますが、ファイル全体またはコードベース全体に変更を加える場合は特にリスクが高くなります。機械的な変更を行うと、ステップを踏み忘れることがあります。テストでは、実装者と審査者の両方に見落とされていた問題が何度も見つかりました。

事前に時間をかけて取り組んだのは、継続的インテグレーション(CI)の設定です。プルリクエストに対する CI 実行が不安定で、頻繁に失敗することが判明しました。この問題は頻繁に発生していたため、Puppeteer の問題ではなく CI の一時的な問題であると想定して、CI を無視してプルリクエストをマージする習慣がついてしまいました。

一般的なメンテナンスを行い、定期的なテストフレークを修正するために時間を費やした後、より一貫して合格する状態になりました。これにより、CI をリッスンし、エラーが実際の問題を示していることを認識できるようになりました。この作業は華やかなものではなく、CI が延々と実行されるのを見るのはイライラしますが、移行によって発生するプルリクエストの数を考えると、テストスイートを確実に実行することが不可欠でした。

1 つのファイルを選択して配置する

この時点で、移行の準備が整い、テストが充実した堅牢な CI サーバーが用意されました。任意のファイルをテストするのではなく、移行する小さなファイルを選択しました。これは、これから行う予定のプロセスを検証できるため、有用な演習です。このファイルで機能する場合は、その方法が有効です。機能しない場合は、最初からやり直す必要があります。

さらに、ファイルごとに(また通常の Puppeteer リリースでは、すべての変更が同じ npm バージョンでリリースされなかったため)リスクが軽減されました。最初のファイルとして DeviceDescriptors.js を選択しました。これは、コードベースで最も単純なファイルの 1 つです。ここまでの準備作業をすべて行って、このような小さな変更を適用するのは、少し物足りなく感じるかもしれません。しかし、目標はすぐに大きな変更を加えることではなく、慎重に、ファイルごとに段階的に進めていくことです。アプローチの検証に費やした時間は、移行の後の段階で複雑なファイルに遭遇したときに確実に時間を節約できます。

パターンを証明して繰り返す

幸い、DeviceDescriptors.js への変更はコードベースに正常に反映され、計画どおりに機能しました。これで、本格的に作業を開始できます。Google はまさにそうしました。GitHub ラベルを使用すると、すべての pull リクエストをグループ化できます。これは、進捗状況を追跡するうえで便利です。

移行して後で改善する

個々の JavaScript ファイルに対して、Google は次のプロセスを実施しました。

  1. ファイルの名前を .js から .ts変更します。
  2. TypeScript コンパイラを実行します。
  3. 問題を修正します。
  4. pull リクエストを作成します。

これらの最初のプル リクエストの作業のほとんどは、既存のデータ構造の TypeScript インターフェースを抽出することでした。前述の DeviceDescriptors.js を移行した最初のプルリクエストの場合、コードは次のように変更されました。

module.exports = [
  { 
    name: 'Pixel 4',
     // Other fields omitted to save space
  }, 
  
]

その後、

interface Device {
  name: string,
  
}

const devices: Device[] = [{name: 'Pixel 4', }, ]

module.exports = devices;

このプロセスの一環として、コードベースのすべての行を調べて問題をチェックしました。数年経過して成長してきたコードベースには、コードをリファクタリングして状況を改善できる部分が必ずあります。特に TypeScript への移行に伴い、コードを少し再構成することで、コンパイラをより活用し、型の安全性を高められる箇所が見つかりました。

直感に反しますが、すぐに変更を加えないことが重要です。移行の目的は、コードベースを TypeScript に移行することです。大規模な移行中は常に、ソフトウェアやユーザーに損害を与えるリスクについて考える必要があります。最初の変更を最小限に抑えることで、そのリスクを低く抑えました。ファイルがマージされて TypeScript に移行されたら、フォローアップの変更を行ってコードを改善し、型システムを活用できるようになりました。移行の厳格な境界を設定し、その範囲内に収まるようにしてください。

型定義をテストするようにテストを移行する

ソースコード全体を TypeScript に移行したら、テストに集中できました。テストのカバレッジは非常に高かったものの、すべて JavaScript で記述されていました。つまり、テストされなかったものの 1 つが型定義でした。このプロジェクトの長期的な目標の 1 つ(現在も取り組んでいます)は、Puppeteer で高品質の型定義をすぐに出荷することですが、コードベースには型定義に関するチェックがありませんでした。

テストを TypeScript に移行(同じプロセスに沿ってファイルごとに処理)することで、TypeScript の問題点を発見しました。問題は、そうでなければユーザーに発見してもらうものでした。これで、テストはすべての機能を網羅するだけでなく、TypeScript の品質チェックとしても機能するようになりました。

Puppeteer のコードベースを扱うエンジニアとして、TypeScript からすでに大きなメリットを得ています。大幅に改善された CI 環境と併用することで、Puppeteer での作業の生産性が向上し、npm リリースに含まれたバグを TypeScript で検出できるようになりました。高品質な TypeScript 定義をリリースし、Puppeteer を使用しているすべてのデベロッパーがこの作業の恩恵を受けられるようにすることを楽しみにしています。

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

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

Chrome DevTools チームへのお問い合わせ

以下のオプションを使用して、新機能、アップデート、DevTools に関連するその他の内容について話し合います。