DevTools 아키텍처 업데이트: DevTools를 TypeScript로 이전

Tim van der Lippe
Tim van der Lippe

이 게시물은 DevTools 아키텍처에 적용되는 변경사항과 빌드 방법을 설명하는 블로그 게시물 시리즈의 일부입니다.

JavaScript 모듈로의 이전웹 구성요소로의 이전에 이어 오늘은 Devtools 아키텍처의 변경사항 및 빌드 방법에 관한 블로그 게시물 시리즈를 계속 이어가겠습니다. (아직 보지 못하신 경우 DevTools 아키텍처를 최신 웹으로 업그레이드 작업에 관한 동영상을 게시했으며, 이 동영상에는 웹 프로젝트를 개선하는 방법에 관한 14가지 팁이 포함되어 있습니다.)

이 게시물에서는 Closure 컴파일러 유형 검사기에서 TypeScript로 전환하는 13개월의 여정을 설명합니다.

소개

DevTools 코드베이스의 크기와 이를 사용하는 엔지니어에게 확신을 제공해야 하는 점을 고려할 때 유형 검사기를 사용하는 것이 필요합니다. 이를 위해 2013년에 DevTools에서 Closure 컴파일러를 채택했습니다. Closure를 채택함으로써 DevTools 엔지니어는 확신을 가지고 변경할 수 있었습니다. Closure 컴파일러는 유형 검사를 실행하여 모든 시스템 통합이 올바르게 유형 지정되었는지 확인합니다.

그러나 시간이 지남에 따라 현대 웹 개발에서 대체 유형 검사기가 인기를 얻었습니다. 주목할 만한 예로는 TypeScriptFlow가 있습니다. 또한 TypeScript는 Google에서 공식 프로그래밍 언어가 되었습니다. 이러한 새로운 유형 검사기가 인기를 얻으면서 유형 검사기에서 포착했어야 할 회귀가 출시되고 있는 것으로 확인되었습니다. 따라서 Google은 유형 검사기 선택을 재평가하고 DevTools에서 개발을 위한 다음 단계를 결정하기로 했습니다.

유형 검사기 평가

DevTools에서 이미 유형 검사기를 사용하고 있었으므로 답변해야 할 질문은 다음과 같습니다.

Closure Compiler를 계속 사용해야 하나요? 아니면 새 유형 검사기로 이전해야 하나요?

이 질문에 답변하기 위해 여러 특성에서 유형 검사기를 평가해야 했습니다. Google에서는 유형 검사기 사용 시 엔지니어의 신뢰에 중점을 두므로 가장 중요한 측면은 유형 정확성입니다. 즉, 유형 검사기가 실제 문제를 발견하는 데 얼마나 유용하나요?

평가는 출시된 회귀와 그 근본 원인을 파악하는 데 중점을 두었습니다. 여기서는 이미 Closure 컴파일러를 사용하고 있으므로 Closure에서 이러한 문제를 포착하지 못했을 것이라고 가정합니다. 따라서 다른 유형 검사기에서 이를 확인할 수 있었는지 확인해야 합니다.

TypeScript의 유형 정확성

TypeScript는 Google에서 공식적으로 지원하는 프로그래밍 언어이며 인기가 급속히 증가하고 있으므로 TypeScript를 먼저 평가하기로 결정했습니다. TypeScript팀 자체가 DevTools를 테스트 프로젝트 중 하나로 사용하여 JavaScript 유형 검사와의 호환성을 추적하므로 TypeScript는 흥미로운 선택이었습니다. 기준 참조 테스트 출력에서 TypeScript가 Closure 컴파일러에서 반드시 감지하지 않는 많은 유형 문제를 포착하고 있음을 확인할 수 있었습니다. 이러한 문제 중 상당수가 출시된 회귀의 근본 원인일 수 있었습니다. 이에 따라 TypeScript가 DevTools에 적합한 옵션일 수 있다고 판단했습니다.

JavaScript 모듈로 이전하는 과정에서 Closure 컴파일러가 이전보다 더 많은 문제를 발견한다는 사실이 이미 확인되었습니다. 표준 모듈 형식으로 전환하면 Closure가 코드베이스를 이해하는 기능이 향상되어 유형 검사기의 효과가 높아졌습니다. 하지만 TypeScript팀은 JavaScript 모듈 이전 이전의 DevTools 기준 버전을 사용하고 있었습니다. 따라서 JavaScript 모듈로의 이전으로 TypeScript 컴파일러가 포착하는 오류의 양도 줄었는지 확인해야 했습니다.

TypeScript 평가

DevTools는 10년 넘게 존재했으며, 이 기간 동안 상당한 크기와 기능이 풍부한 웹 애플리케이션으로 성장했습니다. 이 블로그 게시물을 작성할 당시 DevTools에는 퍼스트 파티 JavaScript 코드가 약 150,000줄 포함되어 있었습니다. 소스 코드에서 TypeScript 컴파일러를 실행했을 때 오류의 양이 너무 많았습니다. TypeScript 컴파일러가 코드 확인과 관련된 오류를 더 적게 내보내고 있지만 (오류 2,000개 미만) 코드베이스에는 유형 호환성과 관련된 오류가 6,000개 더 있음을 확인할 수 있었습니다.

이를 통해 TypeScript는 유형을 확인하는 방법을 이해할 수 있었지만 코드베이스에서 상당한 양의 유형 비호환성을 발견했습니다. 이러한 오류를 수동으로 분석한 결과 TypeScript가 대부분 올바른 것으로 확인되었습니다. TypeScript는 이를 감지할 수 있었지만 Closure는 감지할 수 없었던 이유는 Closure 컴파일러는 유형을 Any으로 추론하는 경우가 많았지만 TypeScript는 할당을 기반으로 유형 추론을 실행하고 더 정확한 유형을 추론했기 때문입니다. 따라서 TypeScript가 객체의 구조를 더 잘 이해하고 문제가 있는 사용을 발견했습니다.

중요한 점은 DevTools에서 Closure 컴파일러를 사용할 때 @unrestricted가 자주 사용되었다는 것입니다. @unrestricted로 클래스에 주석을 추가하면 해당 클래스에 대한 Closure 컴파일러의 엄격한 속성 검사가 사실상 사용 중지됩니다. 즉, 개발자는 유형 안전성을 염두에 두지 않고 클래스 정의를 마음대로 확장할 수 있습니다. DevTools 코드베이스에서 @unrestricted 사용이 보편화된 이유에 관한 이전 맥락을 찾을 수 없지만, 이로 인해 코드베이스의 상당 부분에서 Closure 컴파일러가 덜 안전한 작동 모드로 실행되었습니다.

TypeScript에서 발견한 유형 오류와 회귀 분석을 교차 분석한 결과 중복이 발견되어 TypeScript가 이러한 문제를 방지할 수 있다고 생각했습니다 (유형 자체가 올바른 경우).

any 전화 걸기

이 시점에서 Closure 컴파일러 사용을 개선할지 TypeScript로 이전할지 결정해야 했습니다. Flow는 Google 또는 Chromium에서 지원되지 않으므로 이 옵션을 포기해야 했습니다. JavaScript/TypeScript 도구를 개발하는 Google 엔지니어와의 논의 및 권장사항에 따라 TypeScript 컴파일러를 선택했습니다. 최근에는 Puppeteer를 TypeScript로 이전하는 방법에 관한 블로그 게시물도 게시되었습니다.

TypeScript 컴파일러를 사용한 주된 이유는 향상된 유형 정확성 때문이었습니다. 그 밖에도 Google 내부 TypeScript팀의 지원과 TypeScript 언어의 기능 (예: JSDoc의 typedefs가 아닌 interfaces)이 있었습니다.

TypeScript 컴파일러를 선택하면 DevTools 코드베이스와 내부 아키텍처에 상당한 투자를 해야 했습니다. 따라서 TypeScript로 이전하는 데 1년 이상이 걸릴 것으로 예상했습니다 (2020년 3분기 타겟).

이전 수행

남은 가장 큰 문제는 TypeScript로 어떻게 이전할 것인가였습니다. 코드가 15만 줄이나 되며 한 번에 이전할 수는 없습니다. 또한 코드베이스에서 TypeScript를 실행하면 수천 개의 오류가 발견된다는 것도 알고 있었습니다.

여러 옵션을 평가한 결과 다음과 같은 결과가 도출되었습니다.

  1. 모든 TypeScript 오류를 가져와 '골드' 출력과 비교합니다. 이 접근 방식은 TypeScript팀의 접근 방식과 유사합니다. 이 접근 방식의 가장 큰 단점은 수십 명의 엔지니어가 동일한 코드베이스에서 작업하므로 병합 충돌이 자주 발생한다는 점입니다.
  2. 문제가 있는 모든 유형을 any로 설정합니다. 이렇게 하면 TypeScript에서 오류를 억제하게 됩니다. 억제가 유형 정확성을 저해할 수 있으므로 이 옵션은 선택하지 않았습니다.
  3. 모든 TypeScript 오류를 수동으로 수정합니다. 이렇게 하면 수천 개의 오류를 수정해야 하므로 시간이 많이 걸립니다.

예상되는 노력이 많았지만 옵션 3을 선택했습니다. 이 옵션을 선택한 데는 다른 이유도 있습니다. 예를 들어 이 옵션을 사용하면 모든 코드를 감사하고 구현을 포함한 모든 기능을 10년에 한 번 검토할 수 있습니다. 비즈니스 관점에서는 새로운 가치를 제공하는 것이 아니라 현상 유지에 급급했습니다. 이로 인해 3번 옵션을 올바른 선택으로 정당화하기가 더 어려워졌습니다.

하지만 TypeScript를 채택하면 특히 회귀와 관련된 향후 문제를 방지할 수 있다고 확신했습니다. 따라서 '새로운 비즈니스 가치를 창출하고 있습니다'가 아니라 '획득한 비즈니스 가치를 잃지 않도록 하고 있습니다'가 더 적합한 논거였습니다.

TypeScript 컴파일러의 JavaScript 지원

동의를 얻고 동일한 JavaScript 코드에서 Closure 및 TypeScript 컴파일러를 모두 실행할 계획을 수립한 후 작은 파일로 시작했습니다. Google의 접근 방식은 대부분 하향식입니다. 핵심 코드에서 시작하여 상위 패널에 도달할 때까지 아키텍처를 위로 이동합니다.

DevTools의 모든 파일에 @ts-nocheck를 선제적으로 추가하여 작업을 병렬화할 수 있었습니다. 'TypeScript 수정' 프로세스는 @ts-nocheck 주석을 삭제하고 TypeScript에서 찾은 모든 오류를 해결하는 것입니다. 즉, 각 파일이 확인되었으며 최대한 많은 유형 문제가 해결되었다고 확신할 수 있었습니다.

일반적으로 이 접근 방식은 문제가 거의 없이 작동했습니다. TypeScript 컴파일러에서 몇 가지 버그가 발생했지만 대부분은 모호했습니다.

  1. any를 반환하는 함수 유형의 선택적 매개변수가 필수로 취급됨: #38551
  2. 클래스의 정적 메서드에 속성 할당이 선언을 중단합니다. #38553
  3. 인수가 없는 생성자가 있는 서브클래스와 인수가 있는 생성자가 있는 슈퍼클래스의 선언에서 하위 생성자가 생략됩니다. #41397

이러한 버그는 99% 의 경우 TypeScript 컴파일러가 빌드할 수 있는 견고한 기반임을 보여줍니다. 예, 이러한 모호한 버그가 DevTools에 문제를 일으키는 경우가 있었지만 대부분은 모호하여 쉽게 해결할 수 있었습니다.

혼란을 야기한 유일한 문제는 .tsbuildinfo 파일의 비결정론적 출력(#37156)이었습니다. Chromium에서는 동일한 Chromium 커밋의 두 빌드가 정확히 동일한 출력을 생성해야 합니다. 안타깝게도 Chromium 빌드 엔지니어가 .tsbuildinfo 출력이 비결정론적이라는 사실을 발견했습니다(crbug.com/1054494). 이 문제를 해결하려면 JSON이 기본적으로 포함된 .tsbuildinfo 파일을 원숭이 패치하고 후처리하여 결정론적 출력을 반환해야 했습니다. https://crrev.com/c/2091448 Afortunadamente, el equipo de TypeScript resolvió el problema upstream y pronto pudimos eliminar la solución alternativa. 버그 신고를 수용하고 문제를 신속하게 해결해 주신 TypeScript팀에 감사드립니다.

전반적으로 TypeScript 컴파일러의 (유형) 정확성에 만족합니다. 대규모 오픈소스 JavaScript 프로젝트인 Devtools가 TypeScript에서 JavaScript 지원을 강화하는 데 도움이 되었기를 바랍니다.

여파 분석

이러한 유형 오류를 해결하고 TypeScript에서 확인하는 코드의 양을 서서히 늘리는 데 큰 진전을 이루었습니다. 하지만 2020년 8월 (이전 9개월 차)에 진행 상황을 점검한 결과, 현재 속도로는 기한을 맞출 수 없음을 확인했습니다. Google 엔지니어 중 한 명이 'TypeScriptification' (이 이전에 붙인 이름)의 진행 상황을 보여주는 분석 그래프를 만들었습니다.

TypeScript 이전 진행률

TypeScript 이전 진행률 - 이전해야 하는 남은 코드 줄 추적

남은 대기열이 0이 될 것으로 예상되는 시기는 기한이 지났을 때인 2021년 7월에서 2021년 12월까지였습니다. 경영진 및 다른 엔지니어와 논의한 결과, TypeScript 컴파일러 지원으로의 이전을 담당하는 엔지니어의 수를 늘리기로 합의했습니다. 이는 여러 엔지니어가 여러 파일에서 작업할 때 서로 충돌하지 않도록 이전을 병렬화할 수 있도록 설계했기 때문에 가능했습니다.

이 시점에서 TypeScript로 변환하는 프로세스는 팀 전체의 노력이 되었습니다. 추가 지원을 통해 2020년 11월 말에 이전을 완료할 수 있었습니다. 이는 시작 후 13개월, 초기 예상보다 1년 이상 앞선 시점입니다.

총 18명의 엔지니어가 771개의 변경 목록 (pull 요청과 유사)을 제출했습니다. 추적 버그 (https://crbug.com/1011811)에는 1,200개가 넘는 댓글이 있습니다 (대부분 변경 목록의 자동 게시물). 추적 시트에는 TypeScript로 변환할 모든 파일, 담당자, 'TypeScript로 변환'된 변경사항 목록에 대한 행이 500개가 넘었습니다.

TypeScript 컴파일러 성능의 영향을 완화

현재 가장 큰 문제는 TypeScript 컴파일러의 느린 성능입니다. Chromium과 DevTools를 빌드하는 엔지니어의 수를 감안할 때 이 병목 현상은 비용이 많이 듭니다. 안타깝게도 이전하기 전에 이 위험을 파악하지 못했으며, 대부분의 파일을 TypeScript로 이전한 시점에서만 Chromium 빌드 전반에 걸쳐 소요 시간이 눈에 띄게 증가하는 것을 발견했습니다. https://crbug.com/1139220

Microsoft TypeScript 컴파일러팀에 업스트림으로 이 문제를 신고했지만 안타깝게도 이 동작이 의도적인 것으로 확인되었습니다. Google은 Chromium에서 발생하는 느린 성능 문제를 최대한 완화하기 위해 노력하고 있습니다.

안타깝게도 현재 사용 가능한 솔루션이 Google 외부 참여자에게는 적합하지 않을 수 있습니다. Chromium에 대한 오픈소스 참여 (특히 Microsoft Edge팀의 참여)는 매우 중요하므로 모든 참여자에게 적합한 대안을 적극적으로 모색하고 있습니다. 하지만 현재로서는 적절한 대안 솔루션을 찾지 못했습니다.

DevTools의 현재 TypeScript 상태

현재 코드베이스에서 Closure 컴파일러 유형 검사기를 삭제하고 TypeScript 컴파일러만 사용합니다. TypeScript로 작성된 파일을 작성하고 TypeScript 관련 기능 (예: 인터페이스, 제네릭 등)을 활용할 수 있으므로 일상적인 작업에 도움이 됩니다. TypeScript 컴파일러가 유형 오류와 회귀를 포착할 것이라는 확신이 커졌습니다. 이는 이 이전 작업을 처음 시작할 때 기대했던 바입니다. 이번 이전은 다른 많은 이전과 마찬가지로 느리고 미묘하며 때로는 어려웠지만, 그로 인해 얻은 이점을 생각하면 그만한 가치가 있었습니다.

미리보기 채널 다운로드

Chrome Canary, Dev 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 이러한 미리보기 채널을 사용하면 최신 DevTools 기능에 액세스하고, 최신 웹 플랫폼 API를 테스트하고, 사용자가 문제를 발견하기 전에 사이트에서 문제를 찾을 수 있습니다.

Chrome DevTools팀에 문의하기

다음 옵션을 사용하여 DevTools와 관련된 새로운 기능, 업데이트 또는 기타 사항을 논의하세요.