Puppeteer から TypeScript への移行

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

移行の計画

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

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

テストを行わずにコードを変更することはリスクを伴いますが、ファイル全体やコードベース全体に影響する変更は特にリスクが高くなります。機械的な変更を行う場合、ステップを見落としがちです。実装者とレビュー担当者の両方が見落とした問題が、テストで検出されたことが何度もありました。

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

一般的なメンテナンスと、定期的なテストのフレークを修正するための専用の時間を確保した結果、テストはより一貫して合格する状態になり、CI を監視して、失敗が実際の問題を示していることを把握できるようになりました。この作業は地味で、CI の実行が延々と続くのを見るのはストレスがたまりましたが、移行によって大量の pull リクエストが送られてくることを考えると、テストスイートを確実に実行することが不可欠でした。

1 つのファイルを選択して着地させる

この時点で、移行の準備が整い、テストが満載の堅牢な CI サーバーが背後で監視していました。任意のファイルに飛び込むのではなく、移行する小さなファイルを選択しました。これは、これから実施する予定のプロセスを検証できるため、有用な演習です。このファイルで動作する場合は、アプローチが有効です。動作しない場合は、最初からやり直します。

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

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

幸いなことに、DeviceDescriptors.js への変更はコードベースに正常に反映され、計画は期待どおりに機能しました。この時点で、私たちは腰を据えて取り組む準備が整いました。そして、まさにそのようにしました。GitHub ラベルを使用すると、すべての pull リクエストをグループ化できるため、進捗状況を追跡するのに便利です。

移行してから後で改善する

個々の JavaScript ファイルの処理は次のとおりです。

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

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

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 で記述されていました。つまり、テストされていないのは型定義だけでした。このプロジェクトの長期的な目標の一つ(現在も進行中)は、Puppeteer で高品質の型定義をすぐに提供することですが、コードベースには型定義に関するチェックがありませんでした。

テストを TypeScript に移行することで(同じプロセスに従い、ファイルごとに移行)、TypeScript の問題を発見できました。この問題は、移行しなければユーザーが発見するまで放置されていた可能性があります。これで、テストはすべての機能を網羅するだけでなく、TypeScript の品質チェックとしても機能するようになりました。

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

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

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

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

DevTools の新機能、更新、その他の関連事項について話し合うには、次のオプションを使用します。