Odświeżona architektura narzędzi deweloperskich: migracja narzędzi deweloperskich do TypeScript

Tim van der Lippe
Tim van der Lippe

Ten post jest częścią serii postów na blogu, w których opisujemy zmiany, jakie wprowadzamy w architekturze DevTools i sposobie jego budowy.

W związku z migracją do modułów JavaScript i migracją do komponentów sieciowych chcemy dziś kontynuować naszą serię postów na blogu na temat zmian wprowadzanych w architekturze Devtools oraz o sposobie jej tworzenia. Zachęcam do obejrzenia filmu o uaktualnianiu architektury Narzędzi deweloperskich do nowoczesnej sieci, w którym przedstawiamy 14 wskazówek dotyczących ulepszania projektów internetowych.

W tym poście opiszemy naszą 13-miesięczną podróż, w ramach której odchodziliśmy od sprawdzania typów kompilatora Closure Compiler na TypeScript.

Wstęp

Ze względu na rozmiar bazy kodu Narzędzi deweloperskich i potrzebę zapewnienia inżynierom, którzy nad nią pracują, użycie narzędzia do sprawdzania typów jest niezbędne. W tym celu w 2013 r. firma DevTools wdrożyła Closure Compiler. Wdrożenie Closure pozwoliło inżynierom DevTools na wprowadzanie zmian bez obaw, a kompilator Closure przeprowadził testy typu, aby sprawdzić, czy wszystkie integracje systemów były poprawnie wpisane.

Jednak z czasem w nowoczesnym tworzeniu stron internetowych popularne były alternatywne typy sprawdzania. 2 ważne przykłady to TypeScript i Flow. Co więcej, TypeScript stał się oficjalnym językiem programowania w Google. Chociaż nowe typy mechanizmów sprawdzania zyskały popularność, zauważyliśmy też, że robiliśmy to w przypadku regresji wysyłanych, które powinny zostać przechwycone przez narzędzie do sprawdzania typów. Dlatego postanowiliśmy ponownie przeanalizować wybrane przez nas narzędzie do sprawdzania typów i opracować kolejne kroki programowania w Narzędziach deweloperskich.

Ocenianie modułów do sprawdzania typów

Ponieważ narzędzia deweloperskie używały już narzędzia do sprawdzania typów, musimy odpowiedzieć na pytanie:

Czy nadal korzystamy z narzędzia Closure Compiler, czy przechodzimy na nowe narzędzie do sprawdzania typów?

Aby odpowiedzieć na to pytanie, musieliśmy ocenić kilka cech narzędzia. W przypadku korzystania z narzędzia do sprawdzania typów koncentrujemy się na zaufaniu inżynierów, dlatego najważniejszym aspektem jest dla nas prawidłowość wpisywania. Innymi słowy: Jak wiarygodne jest narzędzie do sprawdzania pisania?

Podczas oceny skupiliśmy się na wprowadzonych przez nas regresjach i ustaleniu ich głównych przyczyn. Zakładamy przy tym, że już wcześniej korzystaliśmy z kompilatora Closure Compiler, więc Closure nie wychwyciłoby takich problemów. Musielibyśmy więc ustalić, czy poradziłby im to jakieś inne narzędzie.

Poprawność wpisywania w TypeScript

Ponieważ TypeScript był oficjalnie obsługiwany w Google i szybko zyskuje na popularności, zdecydowaliśmy się najpierw przetestować TypeScript. TypeScript był ciekawym wyborem, ponieważ zespół TypeScript sam korzysta z Narzędzi deweloperskich jako jednego z projektów testowych w celu śledzenia zgodności ze sprawdzaniem typu JavaScriptu. Wyniki testu podstawowego odniesienia wskazują, że TypeScript wychwytuje dużą liczbę problemów typu, a kompilator Closure nie wykrywa takich problemów. Wiele z tych problemów mogło być główną przyczyną regresji, które przesyłaliśmy. To z kolei skłoniło nas do przekonania, że TypeScript może sprawdzić się w przypadku Narzędzi deweloperskich.

Podczas migracji do modułów JavaScript odkryliśmy już, że narzędzie Closure Compiler wykrywa więcej problemów niż wcześniej. Przejście na format modułu standardowego zwiększyło zdolność Closure do rozumienia naszej bazy kodu, a tym samym zwiększyło skuteczność narzędzi do sprawdzania typów. Zespół TypeScriptu używał jednak podstawowej wersji Narzędzi deweloperskich, która pojawiła się przed migracją modułów JavaScript. Musieliśmy więc sprawdzić, czy migracja do modułów JavaScript ograniczyła liczbę błędów, które może wychwycić kompilator TypeScript.

Ocenianie TypeScriptu

Narzędzia deweloperskie istnieją od ponad 10 lat, a czasem rozrosły się do rozmiarów aplikacji internetowej z dużą liczbą funkcji. W chwili pisania tego posta na blogu Narzędzia deweloperskie zawierają około 150 000 wierszy własnego kodu JavaScript. Gdy w kodzie źródłowym uruchomiliśmy kompilator TypeScript, liczba błędów była przytłaczająca. Udało nam się ustalić, że chociaż kompilator TypeScript generuje mniej błędów związanych z rozpoznawaniem kodu (około 2000 błędów), w bazie kodu nadal występuje 6000 kolejnych błędów związanych ze zgodnością typów.

Pokazało to, że chociaż przeglądarka TypeScript była w stanie zrozumieć, jak rozpoznać typy, wykryła w naszej bazie kodu znaczną liczbę niezgodności typów. Ręczna analiza tych błędów wykazała, że typ TypeScript był (w większości przypadków) poprawny. Powodem, dla którego TypeScript było w stanie je wykryć, a Closure, nie jest to, że kompilator Closure wydedukowałby typ typu Any, natomiast TypeScript przeprowadzał wnioskowanie o typach na podstawie przypisań i ustalał bardziej dokładny typ. W związku z tym TypeScript wyraźnie lepiej rozumieł strukturę naszych obiektów i odkrył problematyczne zastosowania.

Ważną różnicą jest to, że użycie kompilatora Closure w Narzędziach deweloperskich obejmowało częste korzystanie z @unrestricted. Dodanie adnotacji do klasy za pomocą @unrestricted skutecznie wyłącza rygorystyczne sprawdzanie właściwości kompilatora Closure dla tej klasy. Oznacza to, że programista może bez obaw rozszerzyć definicję klasy bez zabezpieczenia typu. Nie udało nam się znaleźć żadnego kontekstu historycznego wyjaśniającego, dlaczego w bazie kodu DevTools powszechny jest użycie @unrestricted, ale w wyniku tego działania kompilator Closure uruchamiał się w mniej bezpiecznym trybie działania dla dużej części bazy kodu.

Analiza krzyżowa naszych regresji z błędami typów wykrytych przez TypeScript również wykazała pokrywanie się, co skłoniło nas do przekonania, że typ Script może zapobiec tym problemom (pod warunkiem, że same typy były prawidłowe).

Wykonuję połączenie z aplikacji any

Musieliśmy też zdecydować, czy lepiej wykorzystać Closure Compiler, czy przejść na TypeScript. (Ponieważ nie był on obsługiwany ani w Google, ani w Chromium, musieliśmy pominąć tę opcję). Na podstawie opinii i zaleceń inżynierów Google pracujących nad narzędziami JavaScript/TypeScript zdecydowaliśmy się wybrać kompilator TypeScript. (Niedawno opublikowaliśmy też posta na blogu na temat migracji Puppeteer do TypeScriptu).

Głównymi przyczynami zastosowania kompilatora TypeScript jest poprawa poprawności typu. Inne zalety obejmowały wsparcie wewnętrznych zespołów TypeScript w Google oraz funkcje języka TypeScript, takie jak interfaces (w przeciwieństwie do typedefs w JSDoc).

Wybór kompilatora TypeScript oznacza, że musieliśmy zainwestować znaczne środki w bazę kodu Narzędzi deweloperskich i jej wewnętrzną architekturę. W związku z tym szacowaliśmy, że migracja do TypeScriptu będzie miała co najmniej 1 rok (funkcja jest planowana na III kwartał 2020 roku).

Przeprowadzanie migracji

Najważniejszym pytaniem, jakie zostało, pozostało pytanie: jak zamierzamy przejść na TypeScript? Mamy 150 tys. wierszy kodu i nie możemy ich przenieść za jednym razem. Wiedzieliśmy też,że uruchomienie TypeScriptu w naszej bazie kodu pozwoli nam uniknąć tysięcy błędów.

Oceniliśmy kilka opcji:

  1. Pobierz wszystkie błędy TypeScript i porównaj je z „złotymi” danymi wyjściowymi. Podejście to jest podobne do stosowanego przez zespół TypeScript. Główną wadą tego podejścia jest duża liczba konfliktów scalania, ponieważ na tej samej bazie kodu pracują dziesiątki inżynierów.
  2. Ustaw wszystkie typy problematyczne na any. Zasadniczo spowodowałoby to pomijanie błędów TypeScriptu. Nie wybraliśmy tej opcji, ponieważ naszym celem migracji była prawidłowość typów, która mogłaby osłabić skuteczność jej pomijania.
  3. Napraw wszystkie błędy TypeScriptu ręcznie. Wiąże się to z naprawą tysięcy błędów, co zajmuje dużo czasu.

Pomimo dużych oczekiwanych nakładów pracy, wybraliśmy opcję nr 3. Istnieją też inne powody, dla których zdecydowaliśmy się wybrać tę opcję, na przykład dzięki możliwości sprawdzenia całego kodu i przeprowadzenia jednorazowej weryfikacji wszystkich funkcji, w tym również ich implementacji. Z perspektywy firmy nie zapewnialiśmy nowej wartości, a jedynie utrzymanie aktualnego stanu. Utrudniało to wybranie opcji 3 jako właściwej.

Mocno wierzymy, że wdrożenie TypeScriptu uniknie przyszłych problemów, zwłaszcza związanych z regresjami. W związku z tym argument brzmiał mniej „dodajemy nową wartość biznesową”, a więcej: „gwarantujemy, że nie tracimy wartości biznesowej”.

Obsługa JavaScriptu kompilatora TypeScript.

Po wykupieniu subskrypcji i opracowaniu planu uruchamiania kompilatora Closure i TypeScript na tym samym kodzie JavaScript zaczęliśmy od niewielkich plików. Nasze podejście było przeważnie od podstaw – zaczynaliśmy od podstawowego kodu i później przesuwaliśmy w górę architektury, aż dojdziemy do paneli wysokiego poziomu.

Mogliśmy równolegle dodać plik @ts-nocheck do każdego pliku w Narzędziach deweloperskich z wyprzedzeniem. „Naprawianie kodu TypeScript” polega na usunięciu adnotacji @ts-nocheck i naprawianiu wszelkich błędów znalezionych przez TypeScript. Miało to pewność, że sprawdziliśmy każdy plik i rozwiązaliśmy jak najwięcej problemów z typami.

Ogólnie takie podejście sprawdziło się w przypadku niewielu problemów. Napotkaliśmy kilka błędów w kompilatorze TypeScript, ale większość z nich była niejasna:

  1. Opcjonalny parametr z typem funkcji, który zwraca any, jest traktowany jako wymagany: #38551
  2. Przypisanie właściwości do metody statycznej w deklaracji podziału klas: #38553
  3. Deklaracja podklasy z konstruktorem bez argumentów i klasą superklasy z konstruktorem argumentów pomija konstruktor podrzędny: #41397.

Błędy te wskazują, że w 99% przypadków kompilator TypeScript to solidna podstawa. Tak. Te mało znane błędy mogą czasami powodować problemy w Narzędziach deweloperskich, ale przeważnie są na tyle mało znane, że łatwo można je obejść.

Jedynym problemem, który spowodował pewne zamieszanie, były niedeterministyczne dane wyjściowe plików .tsbuildinfo: #37156. W Chromium wymagamy, aby 2 kompilacje tego samego zatwierdzenia Chromium generowały dokładnie takie same dane wyjściowe. Nasi inżynierowie odkryli, że dane wyjściowe .tsbuildinfo nie były deterministyczne: crbug.com/1054494. Aby obejść ten problem, musieliśmy naprawić plik .tsbuildinfo (który zasadniczo zawiera kod JSON) i przetworzyć go w celu zwrócenia deterministycznych danych wyjściowych: https://crrev.com/c/2091448. Na szczęście zespół TypeScript rozwiązał problem z przesyłaniem danych i wkrótce udało nam się usunąć obejście. Dziękujemy zespołowi TypeScript za przyjmowanie zgłoszeń o błędach i szybkie rozwiązanie tych problemów.

Ogólnie jesteśmy zadowoleni z poprawności (typu) kompilatora TypeScript. Mamy nadzieję, że Devtools to duży projekt JavaScript typu open source, który pomogł umocnić obsługę JavaScriptu w TypeScript.

Analizowanie skutków

Udało nam się naprawić błędy tego typu i powolnie zwiększyć ilość kodu sprawdzanego przez TypeScript. Jednak w sierpniu 2020 roku (po 9 miesiącach od migracji) poinformowaliśmy o tym i odkryliśmy, że nie uda nam się w podanym przez nas terminie. Jeden z naszych inżynierów stworzył wykres analizy, aby pokazać postęp tworzenia skryptu „TypeScriptification” (taką nazwę nadaliśmy tej migracji).

Postęp migracji TypeScript

Postęp migracji do TypeScript – pozostałe wiersze kodu, które trzeba przenieść

Szacunki, w których stwierdzono brak pozostałych połączeń, mieściły się w okresie od lipca do grudnia 2021 roku, czyli prawie rok po upływie terminu. Po konsultacjach z kierownictwem i innymi inżynierami ustaliliśmy, że zwiększymy liczbę inżynierów pracujących nad przejściem na kompilatory TypeScript. Było to możliwe, ponieważ zaprojektowaliśmy migrację możliwą do równoległego realizacji, dzięki czemu wielu inżynierów pracujących nad wieloma różnymi plikami nie koliduje ze sobą.

Od tego momentu proces TypeScriptification stał się inicjatywą obejmującą cały zespół. Dzięki dodatkowej pomocy udało nam się zakończyć migrację z końcem listopada 2020 roku, czyli po 13 miesiącach od rozpoczęcia, i na ponad rok przed prognozą wstępną.

Łącznie 771 list zmian (podobnych do żądania pull) przesłało 18 inżynierów. Nasz błąd śledzenia (https://crbug.com/1011811) zawiera ponad 1200 komentarzy (niemal wszystkie posty zostały utworzone automatycznie z list zmian). Nasz arkusz śledzenia miał ponad 500 wierszy w przypadku wszystkich plików do wpisania, ich przypisanych i na liście zmian, na których były one typu „wpisane”.

Ograniczanie wpływu kompilatora TypeScriptu

Największym problemem, z którym obecnie się zajmujemy, jest wolne działanie kompilatora TypeScript. Ze względu na liczbę inżynierów tworzących Chromium i Narzędzia deweloperskie to wąskie gardło jest kosztowne. Niestety nie udało nam się zidentyfikować tego ryzyka przed migracją i dopiero w momencie, w którym przenieśliśmy większość plików do TypeScriptu, odkryliśmy zauważalny wzrost czasu poświęcanego na kompilacje Chromium: https://crbug.com/1139220

Zgłosiliśmy ten problem zespołowi ds. kompilatorów Microsoft TypeScript. Zgłoszono go jednak jako zamierzone działanie. Mamy nadzieję, że ponownie rozważą ten problem, a tymczasem pracujemy nad tym, by w miarę możliwości ograniczyć spadek wydajności po stronie Chromium.

Niestety rozwiązania, które są obecnie dostępne w naszym serwisie, nie zawsze są odpowiednie dla osób, które nie korzystają z usług Google. Ponieważ wkład w tworzenie Chromium na licencji open source jest bardzo ważny (zwłaszcza ze strony zespołu Microsoft Edge), aktywnie szukamy rozwiązań alternatywnych, które sprawdzą się u wszystkich użytkowników. Jednak obecnie nie znaleźliśmy odpowiedniego alternatywnego rozwiązania.

Obecny stan TypeScript w Narzędziach deweloperskich

Obecnie usunęliśmy z bazy kodu narzędzie do sprawdzania typów kompilatora Closure i polegamy wyłącznie na kompilatorze TypeScript. Możemy pisać pliki utworzone za pomocą TypeScriptu i korzystać z specyficznych dla TypeScriptu funkcji (np. interfejsów, komunikatów ogólnych itp.), co pomaga nam w codziennej pracy. Zwiększyliśmy pewność, że kompilator TypeScript wyłapuje błędy typu i regresje, takich jak mieliśmy nadzieję, że uda nam się to osiągnąć, gdy zaczęliśmy pracować nad tą migracją. Ta migracja, tak jak wiele innych, była powolna, zróżnicowana i często trudna. Jednak, gdy przynosiła ona korzyści, uznaliśmy, że było warto.

Pobieranie kanałów podglądu

Jako domyślnej przeglądarki dla programistów możesz używać Chrome Canary, Dev lub Beta. Te kanały podglądu dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platform internetowych oraz wykrywanie problemów w witrynie, zanim zdołają zrobić użytkownicy.

Kontakt z zespołem Narzędzi deweloperskich w Chrome

Użyj poniższych opcji, aby omówić nowe funkcje i zmiany w poście lub wszelkie inne kwestie związane z Narzędziami dla deweloperów.

  • Prześlij nam sugestię lub opinię na stronie crbug.com.
  • Aby zgłosić problem z Narzędziami deweloperskimi, kliknij Więcej opcji   Więcej   > Pomoc > Zgłoś problemy z Narzędziami deweloperskimi.
  • zatweetuj na @ChromeDevTools.
  • Napisz komentarz o nowościach w filmach w YouTube dostępnych w Narzędziach deweloperskich lub z poradami dotyczącymi narzędzi dla deweloperów w filmach w YouTube.