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

Tim van der Lippe
Tim van der Lippe

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

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

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

소개

DevTools 코드베이스의 크기와 이를 작업하는 엔지니어에게 확신을 주어야 할 필요성을 고려할 때 유형 검사기를 사용하는 것이 필수입니다. 이를 위해 2013년에 DevTools에서는 클로저 컴파일러를 채택했습니다. 클로저 채택으로 DevTools 엔지니어는 확신을 갖고 변경할 수 있었습니다. 클로저 컴파일러는 유형 검사를 수행하여 모든 시스템 통합의 유형이 올바르게 입력되었는지 확인합니다.

그러나 시간이 지나면서 최신 웹 개발에서 대체 유형 검사기가 인기를 얻게 되었습니다. 두 가지 주목할 만한 예로는 TypeScriptFlow가 있습니다. 또한 TypeScript는 Google의 공식 프로그래밍 언어가 되었습니다. 이러한 새로운 유형의 검사기가 인기가 높아지는 동안, 유형 검사기에서 포착해야 하는 회귀를 배송하고 있음을 알게 되었습니다. 따라서 선택한 유형 검사기를 재평가하고 DevTools에서 개발하기 위한 다음 단계를 결정하기로 했습니다.

유형 검사기 평가

DevTools는 이미 유형 검사기를 사용하고 있었기 때문에 우리가 답해야 할 질문은 다음과 같았습니다.

클로저 컴파일러를 계속 사용하거나 새로운 유형 검사기로 이전해야 하나요?

우리는 이 질문에 답하기 위해 몇 가지 특성을 기준으로 유형 검사기를 평가해야 했습니다. 유형 검사기를 사용할 때는 엔지니어 신뢰도에 중점을 두므로 가장 중요한 측면은 유형 정확성입니다. 즉, 유형 검사기는 실제 문제를 얼마나 신뢰할 수 있나요?

평가는 Google에서 분석한 회귀에 초점을 맞추고 그러한 회귀의 근본 원인을 결정하는 데 집중했습니다. 여기서는 클로저 컴파일러를 이미 사용하고 있었기 때문에 클로저가 이러한 문제를 발견하지 못했을 것이라고 가정합니다. 따라서 다른 유형 검사기가 할 수 있었는지 확인해야 합니다.

TypeScript의 유형 정확성

TypeScript는 Google에서 공식적으로 지원하는 프로그래밍 언어였고 인기가 빠르게 높아졌기 때문에 먼저 TypeScript를 평가하기로 결정했습니다. TypeScript는 흥미로운 선택이었습니다. TypeScript팀 자체에서 테스트 프로젝트 중 하나로 DevTools를 사용하여 자바스크립트 유형 검사와의 호환성을 추적했습니다. 기준 참조 테스트 출력을 통해 TypeScript가 클로저 컴파일러가 감지할 수 없는 수많은 유형 문제를 포착하는 것으로 나타났습니다. 이러한 문제 중 상당수는 Google에서 제공하던 회귀의 근본 원인일 가능성이 높으며, 결과적으로 TypeScript가 DevTools에 적합한 옵션이 될 수 있다고 확신하게 되었습니다.

JavaScript 모듈로 이전하는 동안 클로저 컴파일러가 이전보다 더 많은 문제를 발견하고 있다는 사실을 발견했습니다. 표준 모듈 형식으로 전환함에 따라 클로저의 코드베이스 이해 능력이 향상되어 유형 검사기의 효과가 커졌습니다. 그러나 TypeScript 팀은 자바스크립트 모듈 마이그레이션 이전에 기준 버전의 DevTools를 사용하고 있었습니다. 따라서 자바스크립트 모듈로의 이전으로 TypeScript 컴파일러가 포착하는 오류의 양도 줄었는지 파악해야 했습니다.

TypeScript 평가

DevTools는 10년 넘게 존재했으며, 그 동안 규모가 크고 기능이 풍부한 웹 애플리케이션으로 성장했습니다. 이 블로그 게시물을 작성하는 시점 기준으로 DevTools에는 약 150,000줄의 퍼스트 파티 JavaScript 코드가 포함되어 있습니다. 소스 코드에서 TypeScript 컴파일러를 실행했을 때 엄청난 양의 오류가 발생했습니다. TypeScript 컴파일러가 코드 해상도와 관련된 오류를 적게 발생시키지만 (약 2,000개의 오류) 유형 호환성과 관련된 코드베이스에는 여전히 6,000개의 오류가 더 있다는 것을 알아낼 수 있었습니다.

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

여기서 중요한 점은 DevTools의 클로저 컴파일러를 사용할 때 @unrestricted가 자주 사용된다는 점입니다. 클래스에 @unrestricted 주석을 달면 특정 클래스에 관한 클로저 컴파일러의 엄격한 속성 검사가 효과적으로 사용 중지됩니다. 즉, 개발자가 유형 안전성 없이 원하는 대로 클래스 정의를 보강할 수 있습니다. DevTools 코드베이스에서 @unrestricted가 널리 사용된 이유에 관한 과거 컨텍스트를 찾을 수는 없었지만, 이로 인해 코드베이스의 상당 부분에서 클로저 컴파일러가 덜 안전한 작업 모드에서 실행되었습니다.

TypeScript가 발견한 타입 오류를 포함한 회귀를 교차 분석한 결과도 중복이 발견되어 TypeScript가 이러한 문제를 방지할 수 있었다고 확신했습니다 (유형 자체가 올바르다면).

any 전화 거는 중

이 시점에서는 클로저 컴파일러 사용을 개선할지 아니면 TypeScript로 마이그레이션할지를 결정해야 했습니다. Flow는 Google이나 Chromium에서 지원되지 않았으므로 이 옵션은 생략해야 했습니다. JavaScript/TypeScript 도구를 개발하는 Google 엔지니어와의 논의 및 권고 사항에 따라 TypeScript 컴파일러를 선택했습니다. (최근 Puppeteer를 TypeScript로 이전에 관한 블로그 게시물도 게시했습니다.)

TypeScript 컴파일러의 주된 이유는 개선된 유형 정확성 때문이었고, 다른 장점으로는 Google 내부의 TypeScript팀의 지원 및 interfaces와 같은 TypeScript 언어 기능 (JSDoc의 typedefs와 반대)이 있습니다.

TypeScript 컴파일러를 선택함에 따라 DevTools 코드베이스와 내부 아키텍처에 크게 투자해야 했습니다. 따라서 TypeScript로 마이그레이션하는 데는 최소 1년이 필요한 것으로 추정됩니다 (2020년 3분기 목표).

이전 수행

여전히 남아 있는 가장 큰 질문은 TypeScript로 어떻게 마이그레이션할 것인가입니다. 150,000줄의 코드가 있는데 한 번에 이전할 수는 없습니다. 또한 코드베이스에서 TypeScript를 실행하면 수천 개의 오류가 발생한다는 점도 알고 있었습니다.

여러 옵션을 평가했습니다.

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

많은 노력이 요구될 것으로 예상했음에도 불구하고 옵션 3을 선택했습니다. 이 옵션을 선택한 또 다른 이유는 모든 코드를 감사하고 구현을 포함한 모든 기능을 10년에 한 번 검토할 수 있다는 점입니다. 비즈니스 관점에서 Google은 새로운 가치를 제공하는 것이 아니라 현재를 유지하고 있습니다. 이로 인해 옵션 3을 올바른 선택으로 정당화하기가 더 어려웠습니다.

하지만 TypeScript를 채택함으로써 특히 회귀와 관련된 향후 문제를 방지할 수 있다고 굳게 믿었습니다. 따라서 논쟁은 "새로운 비즈니스 가치를 추가하고 있다"는 것이 아니라 "획득한 비즈니스 가치를 잃지 않도록 하고 있다"는 것이 더 밝혔습니다.

TypeScript 컴파일러의 JavaScript 지원

동의를 얻고 동일한 JavaScript 코드에서 클로저와 TypeScript 컴파일러를 모두 실행할 계획을 세운 후 몇 개의 작은 파일부터 시작했습니다. 우리의 접근 방식은 대부분 상향식이었습니다. 즉, 핵심 코드부터 시작하여 높은 수준의 패널에 도달할 때까지 아키텍처의 위로 올라갔습니다.

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. 이 문제를 해결하기 위해 .tsbuildinfo 파일 (기본적으로 JSON을 포함함)을 원키 패치하고 확정적인 출력을 반환하도록 후처리해야 했습니다. https://crrev.com/c/2091448 다행히 TypeScript팀에서 업스트림 문제를 해결했으며 곧 해결 방법을 삭제할 수 있었습니다. 버그 신고를 수용하고 이러한 문제를 신속하게 해결해 준 TypeScript팀에 감사드립니다.

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

피해 분석

우리는 이러한 유형 오류를 해결하고 TypeScript에서 확인하는 코드의 양을 서서히 늘리는 데 많은 성과를 거뒀습니다. 그러나 이전을 시작한 지 9개월이 지난 2020년 8월에 확인을 진행한 결과, 현재 속도로는 기한을 맞추지 못할 것으로 확인되었습니다. Google 엔지니어 중 한 명이 "TypeScriptification" (이 마이그레이션에 지정한 이름)의 진행 상황을 보여주는 분석 그래프를 작성했습니다.

TypeScript 이전 진행률

TypeScript 이전 진행 상황 - 이전이 필요한 나머지 코드 줄 추적

2021년 7월부터 2021년 12월까지 0줄에 도달할 것으로 예측되었으며 기한이 거의 1년이 지났습니다. 경영진 및 다른 엔지니어와 논의한 후 TypeScript 컴파일러 지원으로 마이그레이션하는 엔지니어의 수를 늘리기로 결정했습니다. 이는 여러 개의 서로 다른 파일에서 작업하는 여러 엔지니어가 서로 충돌하지 않도록 이전을 병렬화하도록 설계했기 때문에 가능했습니다.

이 시점에서 TypeScriptification 프로세스는 팀 전체의 노력이 되었습니다. 추가적인 도움 덕분에 이전을 시작한 지 13개월이 지난 2020년 11월 말에 이전을 완료할 수 있었으며, 초기 예상 예상일보다 1년 이상 앞당겨졌습니다.

18명의 엔지니어가 제출한 변경 목록 (Pull 요청과 유사)은 771개였습니다. Google의 추적 버그 (https://crbug.com/1011811)에는 1,200개가 넘는 댓글이 있습니다 (거의 모두 변경 목록을 기반으로 자동 생성된 게시물). Google 추적 시트에는 입력 스크립트화할 모든 파일, 담당자, 파일이 '유형스크립트화'된 변경 목록에 대한 500개 이상의 행이 있었습니다.

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

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

Google에서는 Microsoft TypeScript 컴파일러팀에 이 문제를 보고했으나 안타깝게도 이 동작은 의도적인 것으로 확인되었습니다. Google은 이 문제를 재검토하기를 바라며, 그동안 Chromium 측의 느린 성능에 미치는 영향을 최대한 완화하기 위해 노력하고 있습니다.

안타깝게도 현재 사용 가능한 솔루션이 Google 직원이 아닌 기여자에게 항상 적합한 것은 아닙니다. Chromium에 대한 오픈소스 기여는 매우 중요하므로 (특히 Microsoft Edge팀) Google은 모든 기여자가 사용할 수 있는 대안을 적극적으로 찾고 있습니다. 하지만 현재로서는 적합한 대안을 찾지 못했습니다.

DevTools의 TypeScript 현재 상태

현재 코드베이스에서 클로저 컴파일러 유형 검사기를 삭제하고 TypeScript 컴파일러만 사용합니다. TypeScript로 작성된 파일을 작성할 수 있고 TypeScript 전용 기능 (예: 인터페이스, 제네릭 등)을 활용할 수 있으며 이는 매일 도움이 됩니다. TypeScript 컴파일러가 유형 오류와 회귀를 포착할 수 있다는 확신이 높았습니다. 저희가 이 마이그레이션 작업을 처음 시작했을 때 일어났으면 하는 일이었습니다. 다른 많은 기업과 마찬가지로 이 마이그레이션은 느리고 미묘하고 어려운 문제도 많았지만, 이점을 얻을 수 있으므로 그만한 가치가 있다고 생각합니다.

미리보기 채널 다운로드

Chrome Canary, 개발자 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 이러한 Preview 채널을 통해 개발자는 최신 DevTools 기능에 액세스하고 최첨단 웹 플랫폼 API를 테스트하며 다른 사용자보다 먼저 사이트에서 문제를 찾을 수 있습니다.

Chrome DevTools팀에 문의하기

게시물에서 새로운 기능과 변경사항 또는 DevTools와 관련된 다른 항목에 대해 논의하려면 다음 옵션을 사용하세요.

  • crbug.com을 통해 제안 또는 의견을 제출하세요.
  • DevTools에서 옵션 더보기   더보기   > 도움말 > DevTools 문제 신고를 사용하여 DevTools 문제를 신고합니다.
  • @ChromeDevTools로 트윗을 보냅니다.
  • DevTools의 새로운 기능 YouTube 동영상 또는 DevTools 팁 YouTube 동영상에 댓글을 남겨주세요.