DevTools팀은 TypeScript를 매우 좋아합니다. DevTools의 새 코드가 TypeScript로 작성되고 있으며 전체 코드베이스를 TypeScript로 유형 검사하는 대규모 이전이 진행 중입니다. 이 마이그레이션에 관한 자세한 내용은 Chrome Dev Summit 2020의 강연에서 확인할 수 있습니다. 따라서 Puppeteer의 코드베이스를 TypeScript로 이전하는 것도 당연한 일이었습니다.
이전 계획
마이그레이션 방법을 계획할 때 작은 단계로 진행할 수 있기를 원했습니다. 이렇게 하면 마이그레이션 오버헤드가 낮아지고(한 번에 코드의 작은 부분만 작업) 위험도 낮아집니다. 단계 중 하나에 문제가 발생하면 쉽게 되돌릴 수 있습니다. Puppeteer에는 많은 사용자가 있으며 출시가 중단되면 많은 사용자에게 문제가 발생하므로 브레이킹 체인지로 인한 위험을 최소한으로 유지하는 것이 중요했습니다.
또한 Puppeteer에는 모든 기능을 포괄하는 강력한 단위 테스트 세트가 마련되어 있었습니다. 즉, 이전하는 동안 코드가 중단되지 않으며 API가 변경되지 않는다는 것을 확신할 수 있었습니다. 마이그레이션의 목표는 Puppeteer 사용자가 마이그레이션이 완료되었다는 사실을 알지 못하도록 하는 것이었고, 테스트는 이 전략의 중요한 부분이었습니다. 테스트 범위가 충분하지 않았다면 마이그레이션을 계속하기 전에 테스트를 추가했을 것입니다.
테스트 없이 코드를 변경하는 것은 위험하지만 전체 파일이나 전체 코드베이스를 수정하는 변경사항은 특히 위험합니다. 기계적인 변경을 할 때는 단계를 놓치기 쉬운데, 구현자와 검토자 모두가 놓친 문제를 테스트에서 발견한 경우가 여러 번 있었습니다.
사전에 시간을 투자한 한 가지는 지속적 통합 (CI) 설정이었습니다. pull 요청에 대한 CI 실행이 불안정하고 실패하는 경우가 많았습니다. 이러한 일이 너무 자주 발생하여 Puppeteer의 문제가 아니라 CI의 일회성 문제라고 가정하고 CI를 무시하고 pull 요청을 병합하는 습관이 생겼습니다.
일반적인 유지보수와 정기적인 테스트 불안정성을 해결하는 데 시간을 할애한 후, 훨씬 더 일관되게 통과하는 상태로 만들 수 있었고, 이를 통해 CI를 듣고 실패가 실제 문제를 나타내고 있음을 알 수 있었습니다. 이 작업은 화려하지 않고 끝없이 CI가 실행되는 것을 지켜보는 것은 답답하지만, 마이그레이션으로 인해 발생하는 pull 요청의 수를 고려할 때 테스트 모음이 안정적으로 실행되는 것이 중요했습니다.
파일 하나를 선택하여 업로드
이 시점에서 마이그레이션을 시작할 준비가 되었고 테스트가 가득한 강력한 CI 서버가 지원해 주었습니다. 임의의 파일을 바로 살펴보는 대신 이전할 작은 파일을 선택했습니다. 이 연습은 계획한 프로세스를 검증할 수 있으므로 유용합니다. 이 파일에서 작동하면 접근 방식이 유효한 것이고, 작동하지 않으면 다시 처음부터 시작하면 됩니다.
또한 파일별로 진행하고 정기적인 Puppeteer 출시를 통해 모든 변경사항이 동일한 npm 버전으로 제공되지 않도록 하여 위험을 줄였습니다. DeviceDescriptors.js를 첫 번째 파일로 선택한 이유는 코드베이스에서 가장 간단한 파일 중 하나였기 때문입니다. 이러한 준비 작업을 모두 마치고 이렇게 작은 변경사항을 적용하는 것이 약간 실망스러울 수도 있지만, 목표는 즉시 큰 변경사항을 적용하는 것이 아니라 신중하게 파일별로 체계적으로 진행하는 것입니다. 접근 방식을 검증하는 데 시간을 투자하면 나중에 더 복잡한 파일을 처리할 때 시간을 절약할 수 있습니다.
패턴을 증명하고 반복합니다.
다행히 DeviceDescriptors.js 변경사항이 코드베이스에 적용되었고 계획이 예상대로 작동했습니다. 이 시점에서 본격적으로 시작할 준비가 되었으며, Google은 바로 이 작업을 수행했습니다. GitHub 라벨을 사용하면 모든 pull 요청을 함께 그룹화할 수 있으며 진행 상황을 추적하는 데 유용합니다.
마이그레이션 후 나중에 개선
개별 JavaScript 파일의 경우 Google의 절차는 다음과 같습니다.
- 파일 이름을
.js에서.ts로 바꿉니다. - TypeScript 컴파일러를 실행합니다.
- 문제가 있으면 수정합니다.
- pull 요청을 만듭니다.
이러한 초기 pull 요청의 대부분은 기존 데이터 구조의 TypeScript 인터페이스를 추출하는 것이었습니다. 앞서 설명한 DeviceDescriptors.js를 이전한 첫 번째 pull 요청의 경우 코드가 다음과 같이 변경되었습니다.
module.exports = [
{
name: 'Pixel 4',
… // Other fields omitted to save space
},
…
]
다음과 같이 변경되었습니다.
interface Device {
name: string,
…
}
const devices: Device[] = [{name: 'Pixel 4', …}, …]
module.exports = devices;
이 과정에서 코드베이스의 모든 줄을 검토하여 문제를 확인했습니다. 몇 년 동안 사용되어 시간이 지남에 따라 성장한 코드베이스와 마찬가지로 코드를 리팩터링하고 상황을 개선할 수 있는 영역이 있습니다. 특히 TypeScript로 전환하면서 코드를 약간 재구성하면 컴파일러를 더 많이 활용하고 유형 안전성을 높일 수 있는 부분이 있었습니다.
직관적이지는 않지만 이러한 변경사항을 즉시 적용하지 않는 것이 매우 중요합니다. 마이그레이션의 목표는 코드베이스를 TypeScript로 가져오는 것이며, 대규모 마이그레이션 중에는 항상 소프트웨어와 사용자에게 손상을 일으킬 위험을 고려해야 합니다. 초기 변경사항을 최소화하여 위험을 낮게 유지했습니다. 파일이 병합되고 TypeScript로 이전되면 타입 시스템을 활용하도록 코드를 개선하는 후속 변경사항을 적용할 수 있었습니다. 마이그레이션에 엄격한 경계를 설정하고 그 경계 내에 머무르세요.
테스트를 마이그레이션하여 유형 정의 테스트
전체 소스 코드를 TypeScript로 이전한 후에는 테스트에 집중할 수 있었습니다. 테스트의 적용 범위는 넓었지만 모두 JavaScript로 작성되었습니다. 즉, 테스트하지 않은 것 중 하나가 유형 정의였습니다. 프로젝트의 장기적인 목표 중 하나는 Puppeteer와 함께 고품질 유형 정의를 기본적으로 제공하는 것이지만 (아직 작업 중) 코드베이스에 유형 정의에 관한 검사가 없었습니다.
테스트를 TypeScript로 이전하면서 (동일한 프로세스를 따르고 파일별로 진행) TypeScript에서 사용자가 직접 발견할 수밖에 없었던 문제를 발견했습니다. 이제 테스트가 모든 기능을 다룰 뿐만 아니라 TypeScript의 품질 검사 역할도 합니다.
Puppeteer 코드베이스에서 작업하는 엔지니어로서 TypeScript의 큰 이점을 이미 누리고 있습니다. 대폭 개선된 CI 환경과 결합하여 Puppeteer에서 작업할 때 생산성을 높이고 TypeScript를 통해 npm 출시 버전에 포함될 수 있는 버그를 포착할 수 있었습니다. Puppeteer를 사용하는 모든 개발자가 이 작업의 이점을 누릴 수 있도록 고품질 TypeScript 정의를 제공하게 되어 기쁩니다.
미리보기 채널 다운로드
Chrome Canary, Dev 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 미리보기 채널을 사용하면 최신 DevTools 기능에 액세스하고, 최신 웹 플랫폼 API를 테스트하고, 사용자가 문제를 발견하기 전에 사이트에서 문제를 찾을 수 있습니다.
Chrome DevTools팀에 문의하기
다음 옵션을 사용하여 새로운 기능, 업데이트 또는 DevTools와 관련된 기타 사항을 논의하세요.
- crbug.com에서 의견 및 기능 요청을 제출합니다.
- DevTools에서 옵션 더보기 > 도움말 > DevTools 문제 신고를 사용하여 DevTools 문제를 알립니다.
- @ChromeDevTools에 트윗합니다.
- 'DevTools의 새로운 기능' YouTube 동영상 또는 'DevTools 팁' YouTube 동영상에 댓글을 남깁니다.