Migracja Puppeteer do TypeScript

W zespole Narzędzi deweloperskich bardzo lubimy TypeScript. Nowy kod w Narzędziach deweloperskich jest pisany w tym języku, a my jesteśmy w trakcie dużej migracji całej bazy kodu do TypeScriptu, gdzie jest sprawdzany typ. Więcej informacji o tej migracji znajdziesz w naszym wystąpieniu na Chrome Dev Summit 2020. Dlatego też logiczne było rozważenie migracji bazy kodu Puppeteer do TypeScriptu.

Planowanie migracji

Planując migrację, chcieliśmy móc robić postępy małymi krokami. Dzięki temu migracja nie jest zbyt uciążliwa, ponieważ w danym momencie pracujesz tylko nad niewielką częścią kodu, co zmniejsza też ryzyko. Jeśli coś pójdzie nie tak, możesz łatwo cofnąć zmiany. Puppeteer ma wielu użytkowników, a wadliwa wersja spowodowałaby problemy u wielu z nich, dlatego ważne było, aby zminimalizować ryzyko wprowadzenia zmian powodujących niezgodność.

Mieliśmy też szczęście, że Puppeteer ma solidny zestaw testów jednostkowych, które obejmują wszystkie jego funkcje. Dzięki temu mogliśmy mieć pewność, że podczas migracji nie uszkodzimy kodu, a także że nie wprowadzimy zmian w naszym interfejsie API. Celem migracji było przeprowadzenie jej w taki sposób, aby użytkownicy Puppeteer nie zorientowali się, że została ona przeprowadzona. Testy były kluczowym elementem tej strategii. Gdybyśmy nie mieli dobrego pokrycia testami, dodalibyśmy je przed kontynuowaniem migracji.

Wprowadzanie zmian w kodzie bez testów jest ryzykowne, ale szczególnie niebezpieczne są zmiany, które dotyczą całych plików lub całej bazy kodu. Podczas wprowadzania zmian mechanicznych łatwo jest pominąć jakiś krok. W wielu przypadkach testy wykryły problem, który umknął zarówno osobie wdrażającej, jak i recenzentowi.

Jedną z rzeczy, w którą zainwestowaliśmy czas na początku, była konfiguracja trybu ciągłej integracji (CI). Zauważyliśmy, że uruchomienia CI w przypadku żądań pull były niestabilne i często kończyły się niepowodzeniem. Zdarzało się to tak często, że zaczęliśmy ignorować CI i scalaliśmy żądania pull, zakładając, że błąd był jednorazowym problemem w CI, a nie problemem w Puppeteer.

Po przeprowadzeniu ogólnej konserwacji i poświęceniu czasu na usunięcie niektórych regularnych problemów z testami udało nam się osiągnąć znacznie bardziej stabilny stan, dzięki czemu mogliśmy polegać na CI i mieć pewność, że błąd wskazuje na rzeczywisty problem. To nie jest efektowna praca i może być frustrujące oglądanie niekończących się uruchomień CI, ale niezawodne działanie pakietu testów było kluczowe ze względu na liczbę żądań scalenia, które generowała migracja.

Wybieranie i przesyłanie jednego pliku

W tym momencie mieliśmy gotową migrację i solidny serwer CI z wieloma testami, które miały nas chronić. Zamiast wybierać dowolny plik, celowo wybraliśmy mały plik do przeniesienia. Jest to przydatne ćwiczenie, ponieważ pozwala sprawdzić planowany proces, który zamierzasz przeprowadzić. Jeśli to zadziała, Twoje podejście jest prawidłowe. Jeśli nie, możesz wrócić do punktu wyjścia.

Dodatkowo sprawdzanie plików po kolei (i regularne publikowanie Puppeteera, dzięki czemu wszystkie zmiany nie zostały wprowadzone w tej samej wersji npm) zmniejszyło ryzyko. Jako pierwszy plik wybraliśmy DeviceDescriptors.js, ponieważ był to jeden z najprostszych plików w bazie kodu. Może się wydawać, że cała ta praca przygotowawcza i wprowadzenie tak małej zmiany to trochę za mało, ale celem nie jest natychmiastowe wprowadzanie ogromnych zmian, ale ostrożne i metodyczne działanie plik po pliku. Czas poświęcony na weryfikację podejścia z pewnością pozwoli zaoszczędzić czas w późniejszym etapie migracji, gdy napotkasz bardziej skomplikowane pliki.

Sprawdź wzór i powtórz go.

Na szczęście zmiana DeviceDescriptors.js została wprowadzona do bazy kodu, a plan zadziałał zgodnie z naszymi oczekiwaniami. W tym momencie trzeba się wziąć do pracy i zrobić to, co trzeba. Właśnie tak zrobiliś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.

Przenieś go i ulepsz później

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

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

Większość pracy w tych początkowych żądaniach pull polegała na wyodrębnieniu interfejsów TypeScript dla istniejących struktur danych. W przypadku pierwszego żądania pull, które przeniosło DeviceDescriptors.js, o którym mówiliśmy wcześniej, kod został zmieniony z:

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

I został:

interface Device {
  name: string,
  …
}

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

module.exports = devices;

W ramach tego procesu sprawdziliśmy każdą linijkę kodu pod kątem błędów. Jak w przypadku każdego kodu, który istnieje od kilku lat i z czasem się rozrasta, są obszary, w których można go refaktoryzować i poprawić. Szczególnie po przejściu na TypeScript zauważyliśmy miejsca, w których niewielka zmiana struktury kodu pozwoliłaby nam bardziej polegać na kompilatorze i uzyskać lepsze zabezpieczenia wpisywania.

Wbrew intuicji ważne jest, aby nie wprowadzać tych zmian od razu. Celem migracji jest przeniesienie bazy kodu do TypeScriptu. Podczas dużej migracji należy zawsze pamiętać o ryzyku spowodowania awarii oprogramowania i zakłóceń w pracy użytkowników. Dzięki ograniczeniu początkowych zmian do minimum udało nam się zmniejszyć to ryzyko. Po scaleniu pliku i przeniesieniu go do TypeScriptu mogliśmy wprowadzić kolejne zmiany, aby ulepszyć kod i wykorzystać system typów. Wyznacz ścisłe granice migracji i staraj się ich nie przekraczać.

Przenoszenie testów w celu sprawdzenia definicji typów

Po przeniesieniu całego kodu źródłowego do TypeScriptu mogliśmy skupić się na testach. Nasze testy miały świetne pokrycie, ale wszystkie były napisane w języku JavaScript. Oznacza to, że nie przetestowali naszych definicji typów. Jednym z długoterminowych celów projektu (nad którym nadal pracujemy) jest dostarczanie wysokiej jakości definicji typów od razu z Puppeteerem, ale w naszym kodzie nie mieliśmy żadnych kontroli dotyczących definicji typów.

Przenosząc testy do TypeScriptu (postępując zgodnie z tą samą procedurą, plik po pliku), znaleźliśmy problemy z naszym kodem TypeScript, które w przeciwnym razie musieliby znaleźć użytkownicy. Teraz nasze testy nie tylko obejmują wszystkie funkcje, ale też sprawdzają jakość kodu TypeScript.

Jako inżynierowie pracujący nad bazą kodu Puppeteer odnieśliśmy już ogromne korzyści z TypeScriptu. W połączeniu z naszym znacznie ulepszonym środowiskiem CI pozwoliło nam to zwiększyć produktywność podczas pracy nad Puppeteerem i wykrywać błędy za pomocą TypeScriptu, które w przeciwnym razie trafiłyby do wersji npm. Cieszymy się, że możemy udostępnić wysokiej jakości definicje TypeScript, dzięki którym wszyscy deweloperzy korzystający z Puppeteera będą mogli korzystać z tych rozwiązań.

Pobieranie kanałów podglądu

Jako domyślnej przeglądarki do tworzenia stron używaj Chrome w wersji Canary, deweloperskiej lub beta. Te kanały wersji zapoznawczych zapewniają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i pomagają znajdować problemy w witrynie, zanim zauważą je użytkownicy.

Skontaktuj się z zespołem Narzędzi deweloperskich w Chrome

Skorzystaj z tych opcji, aby porozmawiać o nowych funkcjach, aktualizacjach lub innych kwestiach związanych z Narzędziami deweloperskimi.