Migracja Puppeteer do TypeScript

W zespole Narzędzi deweloperskich jesteśmy wielkimi fanami TypeScript. Nasz nowy kod w Narzędziach deweloperskich jest tworzony w tym języku, a obecnie jesteśmy w trakcie dużej migracji całej bazy kodu, aby sprawdzać typy za pomocą TypeScript. Więcej informacji o tej migracji znajdziesz w prezentacji na konferencji Chrome Dev Summit 2020. Dlatego postanowiliśmy przenieść kod Puppeteer do TypeScript.

Planowanie migracji

Podczas planowania migracji chcieliśmy dokonać postępów małymi krokami. Dzięki temu zmniejszysz nakłady związane z migracją (w każdej chwili pracujesz tylko nad małą częścią kodu) i ograniczysz ryzyko. Jeśli coś pójdzie nie tak podczas wykonywania któregoś z tych kroków, możesz łatwo cofnąć zmiany. Puppeteer ma wielu użytkowników, a nieudana wersja może spowodować problemy dla wielu z nich, dlatego bardzo ważne było dla nas zminimalizowanie ryzyka wprowadzenia zmian, które mogłyby spowodować nieprawidłowe działanie.

Mieliśmy też szczęście, że Puppeteer ma solidny zestaw testów jednostkowych obejmujących wszystkie jego funkcje. Dzięki temu mogliśmy mieć pewność, że podczas migracji nie uszkodziliśmy kodu, a także że nie wprowadziliśmy zmian w naszym interfejsie API. Celem migracji było jej przeprowadzenie bez udziału użytkowników Puppeteer, którzy nawet nie zdawali sobie sprawy, że nastąpiła migracja. Testy były kluczowym elementem tej strategii. Gdybyśmy nie mieli dobrego pokrycia testami, dodaliśmy je przed kontynuowaniem migracji.

Wprowadzanie jakichkolwiek zmian w kodzie bez testów jest ryzykowne, ale zmiany, które dotyczą całych plików lub całej bazy kodu, są szczególnie ryzykowne. Podczas wprowadzania zmian mechanicznych łatwo pominąć jakiś krok, a testy wielokrotnie wykrywały problemy, które umykały zarówno implementatorowi, jak i sprawdzającemu.

Na początku poświęciliśmy dużo czasu na konfigurowanie ciągłej integracji (CI). Zauważyliśmy, że procesy CI dotyczące próśb o przeniesienie były niestabilne i często kończyły się niepowodzeniem. Zdarzało się to tak często, że zaczęliśmy ignorować nasz system CI i i tak łączyć żądania pull, zakładając, że błąd był jednorazowym problemem w systemie CI, a nie w Puppeteer.

Po przeprowadzeniu ogólnej konserwacji i poświęceniu czasu na naprawienie niektórych testów, które nie działały prawidłowo, udało nam się uzyskać znacznie bardziej stabilny stan, dzięki któremu mogliśmy słuchać CI i wiedzieć, że błąd wskazuje na rzeczywisty problem. To nie jest praca dla gwiazd i nie jest przyjemnością obserwowanie niekończących się procesów CI, ale ze względu na liczbę żądań pull, które migracja wysyłała do naszego kompletnego zestawu testów, konieczne było zapewnienie jego niezawodnego działania.

Wybierz i zapisz jeden plik

W tym momencie mieliśmy już gotową migrację i solidny serwer CI z wieloma testami, które nas chroniły. Zamiast zająć się dowolnym plikiem, celowo wybraliśmy mały plik do przeniesienia. To przydatne ćwiczenie, ponieważ pozwala sprawdzić zaplanowany proces. Jeśli to działa, Twój sposób jest prawidłowy. Jeśli nie, musisz wrócić do rysowania.

Dodatkowo ryzyko było ograniczone, ponieważ sprawdzaliśmy poszczególne pliki (i to z regularnymi wersjami Puppeteer, więc nie wszystkie zmiany były wprowadzane w tej samej wersji npm). Pierwszym wybranym przez nas plikiem był DeviceDescriptors.js, ponieważ był to jeden z najprostszych plików w kodeksie źródłowym. Może się wydawać, że to nie jest warte całego tego wstępnego wysiłku, ale nie chodzi o to, aby wprowadzić duże zmiany od razu, tylko metodycznie i ostrożnie modyfikować poszczególne pliki. Czas poświęcony na weryfikację podejścia z pewnością zaoszczędzi Ci czas później, podczas migracji bardziej skomplikowanych plików.

Udowodnij, że wzór jest powtarzalny

Na szczęście zmiana na DeviceDescriptors.js została wprowadzona do kodu źródłowego, a plan działał zgodnie z nadziejami. W tym momencie możesz zacząć działać, czyli dokładnie tak, jak my. Używanie etykiety GitHub to bardzo dobry sposób na grupowanie wszystkich żądań pull. Okazało się to przydatne do śledzenia postępów.

Przeprowadź migrację i ulepszaj rozwiązanie później

W przypadku każdego pojedynczego pliku JavaScript proces wyglądał tak:

  1. Zmień nazwę pliku z .js na .ts.
  2. Uruchom kompilator TypeScript.
  3. Rozwiąż wszelkie problemy.
  4. Utwórz żądanie pull.

Większość pracy w tych początkowych żądaniach pull polegała na wyodrębnianiu interfejsów TypeScript dla istniejących struktur danych. W przypadku pierwszego zgłoszenia do wersji roboczej, które zostało przeniesione do DeviceDescriptors.js, o którym już rozmawialiśmy, kod zmienił się z:

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

I stała się:

interface Device {
  name: string,
  
}

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

module.exports = devices;

W ramach tego procesu sprawdziliśmy każdy wiersz kodu pod kątem problemów. Podobnie jak w przypadku każdego kodu, który istnieje od kilku lat i zmieniał się na przestrzeni lat, w tym przypadku również można znaleźć obszary, w których można zmienić kod i poprawić sytuację. Zwłaszcza w przypadku przejścia na TypeScript zauważyliśmy miejsca, w których niewielka zmiana struktury kodu pozwoliłaby nam bardziej polegać na kompilatorze i uzyskać większą ochronę typów.

Wbrew pozorom bardzo ważne jest, aby nie wprowadzać tych zmian od razu. Celem migracji jest przekształcenie kodu źródłowego w TypeScript. Podczas dużej migracji należy zawsze pamiętać o ryzyku spowodowania awarii oprogramowania i uniemożliwienia korzystania z niego przez użytkowników. Dzięki temu, że początkowe zmiany były minimalne, ryzyko było niewielkie. Po scaleniu pliku i przeniesieniu go do TypeScript mogliśmy wprowadzić dalsze zmiany, aby ulepszyć kod i wykorzystać system typów. Pamiętaj, aby ustawić ścisłe granice migracji i staraj się ich nie przekraczać.

Migracja testów w celu sprawdzenia definicji typów

Po przeniesieniu całego kodu źródłowego do TypeScripta mogliśmy skupić się na testach. Nasze testy miały świetne pokrycie, ale były napisane w języku JavaScript. Oznacza to, że nie testowano definicji typów. Jednym z długoterminowych celów projektu (nad którym nadal pracujemy) jest dostarczanie wysokiej jakości definicji typów w ramach Puppeteer, ale w naszej bazie kodu nie ma żadnych kontroli dotyczących definicji typów.

Podczas migracji testów do TypeScript (zgodnie z tym samym procesem, krok po kroku) znaleźliśmy problemy z TypeScript, które w przeciwnym razie zostałyby odkryte przez użytkowników. Teraz nasze testy obejmują wszystkie funkcje i służą też do sprawdzania jakości kodu TypeScript.

Jako inżynierowie pracujący nad kodem źródłowym Puppeteer odnieśliśmy już ogromne korzyści z TypeScript. W połączeniu z znacznie ulepszonym środowiskiem CI pozwoliło nam to zwiększyć produktywność podczas pracy nad Puppeteer i uzyskać możliwość wykrywania błędów w TypeScript, które w przeciwnym razie mogłyby zostać uwzględnione w wersji npm. Cieszymy się, że udostępniliśmy wysokiej jakości definicje TypeScript, z których mogą korzystać wszyscy deweloperzy korzystający z Puppeteer.

Pobieranie kanałów podglądu

Rozważ użycie jako domyślnej przeglądarki deweloperskiej wersji Canary, Dev lub Beta przeglądarki Chrome. Te kanały wersji wstępnej zapewniają dostęp do najnowszych funkcji DevTools, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i pomagają znaleźć problemy w witrynie, zanim zrobią to użytkownicy.

Kontakt z zespołem Narzędzi deweloperskich w Chrome

Aby omówić nowe funkcje, aktualizacje lub inne kwestie związane z Narzędziami deweloperskimi, skorzystaj z tych opcji.