Puppeteer から TypeScript への移行

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

移行の計画

移行の計画を立てるとき、小さなステップで前進できるようにしたいと考えていました。これにより、いつでもコードの一部でしか作業できない、移行のオーバーヘッドとリスクが軽減されます。いずれかの手順で問題が発生した場合は、簡単に元に戻すことができます。Puppeteer には多数のユーザーがいますが、不完全なリリースが多くのユーザーに問題を引き起こすため、互換性を破る変更のリスクを最小限に抑えることが不可欠でした。

幸いなことに、Puppeteer のすべての機能を網羅する堅牢な単体テストのセットが用意されています。つまり、移行時にコードに互換性がないことに自信を持てると同時に、API に変更を加えていないということを確信できました。移行の目標は Puppeteer のユーザーが移行したことに気づかずに完了することであり、テストはその戦略の重要な要素でした。テスト カバレッジが良好ではなかった場合、移行を続行する前にその範囲を追加していました。

テストなしでコード変更を実行することは危険ですが、ファイル全体またはコードベース全体に触れる変更は特に危険です。機械的な変更を加えると、手順を見逃しがちです。何度もテストで 1 つの問題が検出され、実装担当者と審査担当者の両方が見落としてしまいます。

事前に時間をかけて行ったのは、継続的インテグレーション(CI)のセットアップでした。pull リクエストに対する CI 実行は不安定で、失敗することが多いことに気づきました。これは何度も起きたため、失敗は Puppeteer の問題ではなく CI の 1 回限りの問題だと仮定して、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 で記述されたテストです。つまり、型定義がテストされていないということです。プロジェクトの長期的な目標の一つは、(Google が引き続き取り組んでいる)Puppeteer で高品質の型定義をすぐにリリースすることですが、型定義をコードベースで確認していませんでした。

テストを TypeScript に移行することで(同じプロセスに従い、ファイルごとに移行することで)、TypeScript の問題が見つかりました。これは、他の方法ではユーザーが見つけることができなければなりません。これで、テストはすべての機能をカバーするだけでなく、TypeScript の品質チェックとしても機能します

Puppeteer コードベースを担当するエンジニアとして、すでに TypeScript から多大な恩恵を受けています。大幅に改善された CI 環境と組み合わせることで、Puppeteer での作業の生産性が向上し、TypeScript は他の方法では npm リリースになっていたはずのバグをキャッチできるようになりました。高品質の TypeScript 定義がリリースされ、Puppeteer を使用するすべてのデベロッパーがこの機能を活用できるようになります。

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

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

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

投稿内の新機能や変更点、または DevTools に関するその他の事項について議論するには、以下のオプションを使用します。

  • crbug.com からご提案やフィードバックをお寄せください。
  • DevTools の [その他のオプション] その他   > [ヘルプ] > [DevTools の問題を報告する] を使用して、DevTools の問題を報告する。
  • @ChromeDevTools でツイートします。
  • DevTools の新機能の YouTube 動画または DevTools のヒントの YouTube 動画で、コメントを記入してください。