DevTools 아키텍처 업데이트: 자바스크립트 모듈로 이전

Tim van der Lippe
Tim van der Lippe

아시다시피 Chrome DevTools는 HTML, CSS, 자바스크립트를 사용하여 작성된 웹 애플리케이션입니다. 지난 몇 년 동안 DevTools는 기능이 다양해지고 광범위한 웹 플랫폼에 대해 더욱 스마트하고 풍부한 지식을 갖추었습니다. 수년간 DevTools는 확장해 왔지만 아키텍처는 여전히 WebKit의 일부였을 때의 원래 아키텍처와 유사합니다.

이 게시물은 DevTools 아키텍처의 변경사항 및 빌드 방법을 설명하는 블로그 게시물 시리즈의 일부입니다. 지금까지 DevTools가 작동한 방식, 이점과 한계, 이러한 제한을 완화하기 위해 Google에서 어떤 조치를 취했는지 설명해 드리겠습니다. 이제 모듈 시스템, 코드 로드 방법, 자바스크립트 모듈 사용 방법을 자세히 살펴보겠습니다.

초반에는

현재의 프런트엔드 환경에는 이러한 도구를 중심으로 구축된 도구가 포함된 다양한 모듈 시스템과 현재 표준화된 JavaScript 모듈 형식이 있지만, DevTools가 처음 빌드되었을 때는 이들 중 어느 것도 존재하지 않았습니다. DevTools는 WebKit에 처음 출시된 지 12년이 넘은 코드를 기반으로 합니다.

DevTools에서 모듈 시스템이 처음 언급되는 것은 2012년입니다. 연결된 소스 목록이 포함된 모듈 목록을 도입한 것입니다. 이는 당시에 DevTools를 컴파일하고 빌드하는 데 사용된 Python 인프라의 일부였습니다. 후속 변경으로 인해 2013년에는 모든 모듈이 별도의 frontend_modules.json 파일 (commit)으로 추출되었으며, 2014년에는 별도의 module.json 파일 (commit)으로 추출되었습니다.

module.json 파일의 예는 다음과 같습니다.

{
  "dependencies": [
    "common"
  ],
  "scripts": [
    "StylePane.js",
    "ElementsPanel.js"
  ]
}

2014년부터 DevTools에서 module.json 패턴이 모듈과 소스 파일을 지정하는 데 사용되었습니다. 한편 웹 생태계는 빠르게 진화하고 UMD, CommonJS, 그리고 최종적으로 표준화된 JavaScript 모듈을 비롯한 여러 모듈 형식이 만들어졌습니다. 그러나 DevTools는 module.json 형식을 유지합니다.

DevTools는 계속 작동하지만, 표준화되지 않은 고유한 모듈 시스템을 사용할 때 몇 가지 단점이 있었습니다.

  1. module.json 형식에는 최신 번들러와 유사한 맞춤 빌드 도구가 필요했습니다.
  2. IDE 통합이 없었기 때문에 최신 IDE가 이해할 수 있는 파일 (VS Code용 jsconfig.json 파일을 생성하는 원본 스크립트)을 생성하기 위해 커스텀 도구가 필요했습니다.
  3. 모듈 간 공유가 가능하도록 함수, 클래스, 객체를 모두 전역 범위에 배치했습니다.
  4. 파일의 순서가 중요했습니다. 즉, sources가 나열된 순서가 중요했습니다. 사람이 확인했다는 것 외에는 여러분이 사용하는 코드가 로드된다는 보장이 없었습니다.

무엇보다 DevTools와 더 널리 사용되는 다른 모듈 형식에서 모듈 시스템의 현재 상태를 평가할 때 module.json 패턴이 해결된 것보다 더 많은 문제를 야기하고 있다는 결론을 내렸으며, 이제 이 패턴에서 벗어날 계획을 세울 때가 되었습니다.

표준의 이점

기존 모듈 시스템 중에서 마이그레이션할 모듈로 JavaScript 모듈을 선택했습니다. 결정 당시 JavaScript 모듈은 여전히 Node.js의 플래그 뒤에서 제공되고 있었으며 NPM에서 사용 가능한 많은 패키지에는 사용할 수 있는 JavaScript 모듈 번들이 없었습니다. 그럼에도 불구하고, 우리는 JavaScript 모듈이 최선의 옵션이라는 결론을 내렸습니다.

자바스크립트 모듈의 주요 이점은 자바스크립트의 표준화된 모듈 형식이라는 점입니다. module.json의 단점 (위 참고)을 나열했을 때 거의 모든 부분이 표준화되지 않은 고유한 모듈 형식을 사용하는 것과 관련이 있다는 사실을 깨달았습니다.

표준화되지 않은 모듈 형식을 선택하면 유지관리 담당자가 사용한 빌드 도구 및 도구와의 통합을 구축하는 데 시간을 직접 투자해야 합니다.

이러한 통합은 불안정하고 기능 지원이 부족했던 경우가 많았고, 이로 인해 추가 유지보수 시간이 필요했으며, 결과적으로 미묘한 버그가 사용자에게 전달되기도 했습니다.

JavaScript 모듈이 표준이었기 때문에 이는 VS Code와 같은 IDE, 클로저 컴파일러/TypeScript와 같은 유형 검사기, Rollup/minifier와 같은 빌드 도구가 우리가 작성한 소스 코드를 이해할 수 있음을 의미했습니다. 또한 새로운 유지관리자가 DevTools 팀에 합류할 때 독점적인 module.json 형식을 배우는 데 시간을 쓸 필요가 없으나 JavaScript 모듈에는 이미 익숙할 가능성이 높습니다.

물론 DevTools가 처음 빌드되었을 때는 위와 같은 이점이 없었습니다. 표준 그룹, 런타임 구현, JavaScript 모듈을 사용하는 개발자 등에서 수년간 노력한 결과, 피드백을 제공하여 현재에 닿을 수 있었습니다. 하지만 JavaScript 모듈을 사용할 수 있게 되었을 때 우리는 자체 형식을 계속 유지하거나 새 형식으로 이전하는 데 투자할지 선택할 수 있었습니다.

반짝이는 새 제품의 비용

JavaScript 모듈에는 사용하고 싶은 이점이 많지만 비표준 module.json 환경에 머물렀습니다. JavaScript 모듈의 이점을 누리게 되자 기술 부채를 없애고 잠재적으로 기능을 손상시키고 회귀 버그를 발생시킬 수 있는 마이그레이션을 수행하는 데 상당한 투자를 해야 했습니다.

이 시점에서는 '자바스크립트 모듈을 사용하시겠습니까?'가 아니라 '자바스크립트 모듈을 사용하려면 비용이 얼마나 드나요?'가 궁금했습니다. 이 단계에서는 사용자가 회귀로 인해 손상될 수 있는 위험, 엔지니어가 마이그레이션에 상당한 시간을 소비하는 비용 (많은 시간), 작업 상태가 일시적으로 악화될 수 있는 상태 사이에서 균형을 맞춰야 했습니다.

이 마지막 요점은 매우 중요했습니다. 이론적으로는 JavaScript 모듈에 도달할 수 있지만 이전하는 동안 module.json 및 JavaScript 모듈을 모두 고려해야 하는 코드가 생성됩니다. 이는 기술적으로는 어려웠을 뿐만 아니라 DevTools에서 작업하는 모든 엔지니어가 이 환경에서 작업하는 방법을 알아야 한다는 것을 의미했습니다. 개발자는 '코드베이스의 이 부분에서 module.json 모듈인지 JavaScript 모듈인지, 어떻게 변경하나요?'라고 끊임없이 질문해야 합니다.

살짝 엿보기: 마이그레이션을 통해 동료 유지관리 담당자를 안내하는 데는 예상보다 많은 비용이 듭니다.

비용 분석 결과, JavaScript 모듈로 이전하는 것이 여전히 가치가 있다는 결론을 내렸습니다. 따라서 우리의 주요 목표는 다음과 같았습니다.

  1. 자바스크립트 모듈을 사용하면 최대한의 이점을 누릴 수 있어야 합니다.
  2. 기존 module.json 기반 시스템과의 통합이 안전하게 이루어지고 사용자에게 부정적인 영향 (회귀 버그, 사용자 불만)이 발생하지 않아야 합니다.
  3. 모든 DevTools 유지관리자에게 마이그레이션 과정을 안내합니다. 주로 우발적인 실수를 방지하기 위해 확인 및 균형 기능이 내장되어 있습니다.

스프레드시트, 변환 및 기술 부채

목표는 명확했지만 module.json 형식이 가진 한계는 해결하기 어려운 것으로 판명되었습니다. 우리가 익숙한 솔루션을 개발하기까지 여러 차례의 반복, 프로토타입, 아키텍처 변경을 거쳤습니다. 마침내 완성한 이전 전략을 사용해 디자인 문서를 작성했습니다. 설계 문서에도 초기 예상 시간(2~4주)이 나와 있었습니다.

스포일러 주의: 이전 작업 중 가장 집중적인 부분은 4개월이 걸렸으며 시작부터 완료까지 7개월이 걸렸습니다.

그러나 초기 계획은 시간에 대한 테스트를 지켰습니다. module.json 파일의 scripts 배열에 나열된 모든 파일을 이전 방식으로 로드하고 modules 배열에 나열된 모든 파일은 JavaScript 모듈 동적 가져오기를 사용하여 로드하도록 DevTools 런타임을 학습시킵니다. modules 배열에 있는 모든 파일은 ES 가져오기/내보내기를 사용할 수 있습니다.

또한 마이그레이션은 exportimport 단계, 두 단계 (마지막 단계는 결국 아래 2단계 참고)로 진행됩니다. 대규모 스프레드시트에서 어떤 단계가 추적되었는지에 대한 상태:

자바스크립트 모듈 이전 스프레드시트

진행률 시트 스니펫은 여기에서 공개적으로 제공됩니다.

export단계

첫 번째 단계는 모듈/파일 간에 공유되어야 하는 모든 기호에 export 문을 추가하는 것입니다. 폴더당 스크립트를 실행하여 변환을 자동화합니다. 다음 기호가 module.json 세계에는 존재할 수 있습니다.

Module.File1.exported = function() {
  console.log('exported');
  Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
  console.log('Local');
};

여기서 Module는 모듈 이름이고 File1은 파일 이름입니다. 소스 트리에서는 front_end/module/file1.js입니다.)

이는 다음과 같이 변환됩니다.

export function exported() {
  console.log('exported');
  Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
  console.log('Local');
}

/** Legacy export object */
Module.File1 = {
  exported,
  localFunctionInFile,
};

처음에는 이 단계에서 동일 파일 가져오기를 다시 작성할 계획입니다. 예를 들어 위의 예에서는 Module.File1.localFunctionInFilelocalFunctionInFile로 다시 작성합니다. 그러나 이 두 가지 변환을 분리하면 자동화하기가 더 쉽고 더 안전하게 적용할 수 있다는 것을 알게 되었습니다. 따라서 '동일한 파일의 모든 기호 이전'은 import 단계의 두 번째 하위 단계가 됩니다.

파일에 export 키워드를 추가하면 파일이 '스크립트'에서 '모듈'로 변환되므로 이에 따라 많은 DevTools 인프라를 업데이트해야 했습니다. 여기에는 런타임 (동적 가져오기 포함)뿐만 아니라 모듈 모드에서 실행할 ESLint와 같은 도구도 포함되었습니다.

이러한 문제를 해결하는 동안 발견한 한 가지는 테스트가 '엉성한' 모드로 실행되었다는 것입니다. JavaScript 모듈은 파일이 "use strict" 모드에서 실행된다고 암시하므로 테스트에도 영향을 미칩니다. 그 결과, with 문을 사용한 테스트를 포함해 상당한 양의 테스트가 이러한 속도 저하에 의존하고 있었습니다.

결국 export 문을 포함하도록 첫 번째 폴더를 업데이트하는 데 약 일주일이 걸렸으며 재전달을 여러 번 시도했습니다.

import단계

모든 기호를 export 문을 사용하여 내보내고 전역 범위 (레거시)에 유지한 후 ES 가져오기를 사용하려면 파일 간 기호의 모든 참조를 업데이트해야 했습니다. 최종 목표는 모든 '기존 내보내기 객체'를 삭제하여 전역 범위를 정리하는 것입니다. 폴더당 스크립트를 실행하여 변환을 자동화합니다.

예를 들어 module.json 세계에 있는 다음 기호의 경우:

Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();

다음과 같이 변환됩니다.

import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';

import {moduleScoped} from './AnotherFile.js';

Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();

그러나 이 접근 방식에는 몇 가지 주의할 점이 있습니다.

  1. 모든 기호의 이름이 Module.File.symbolName로 지정된 것은 아닙니다. 일부 기호의 이름은 Module.File 또는 Module.CompletelyDifferentName로만 지정되었습니다. 이러한 불일치로 인해 이전 전역 객체에서 새로 가져온 객체로 내부 매핑을 만들어야 했습니다.
  2. moduleScope 이름이 충돌할 때가 있습니다. 가장 눈에 띄는 점은 특정 유형의 Events를 선언하는 패턴을 사용했으며 각 기호의 이름은 Events로 지정되었습니다. 즉, 서로 다른 파일에서 선언된 여러 유형의 이벤트를 수신 대기하는 경우 해당 Eventsimport 문에서 이름 충돌이 발생합니다.
  3. 그 결과, 파일 간에 순환적 종속성이 있었습니다. 이는 모든 코드가 로드된 후에 기호가 사용되었으므로 전역 범위 컨텍스트에서는 문제가 되지 않았습니다. 그러나 import가 필요한 경우 순환 종속 항목이 명시됩니다. 이는 DevTools에도 있는 전역 범위 코드에 부작용 함수 호출이 없는 한 즉시 문제가 되지 않습니다. 무엇보다도 안전한 변환을 위해 수술과 리팩터링이 필요했습니다.

JavaScript 모듈이 있는 완전히 새로운 환경

2019년 9월이 시작된 지 6개월 후인 2020년 2월에는 ui/ 폴더에서 마지막 정리가 수행되었습니다. 이로써 이전이 비공식적으로 끝났습니다. 원활한 작업을 마친 후 Google에서는 이전을 2020년 3월 5일에 완료로 공식적으로 표시했습니다. 🎉

이제 DevTools의 모든 모듈이 JavaScript 모듈을 사용하여 코드를 공유합니다. 레거시 테스트를 위해 또는 DevTools 아키텍처의 다른 부분과 통합하기 위해 여전히 일부 기호를 전역 범위 (module-legacy.js 파일)에 배치합니다. 이러한 기능은 시간이 지남에 따라 삭제될 예정이지만 향후 개발에 방해가 되지는 않습니다. JavaScript 모듈 사용에 관한 스타일 가이드도 있습니다.

통계

이 이전과 관련된 CL (변경 목록의 약어 - 변경사항을 나타내는 Gerrit에서 사용되는 용어로 GitHub pull 요청과 유사)의 보수적인 추정치는 약 250개의 CL이며 주로 2명의 엔지니어가 수행합니다. 변경사항의 크기에 대한 확정된 통계는 없지만 줄의 보수적인 추정치 (각 CL의 삽입과 삭제 간 절대 차이의 합계로 계산)는 약 30,000개 (모든 DevTools 프런트엔드 코드의 약 20%)입니다.

export를 사용하는 첫 번째 파일은 Chrome 79에서 제공되었으며 2019년 12월에 안정화 버전으로 출시되었습니다. import로의 이전을 위한 마지막 변경사항은 2020년 5월에 안정화 버전으로 출시된 Chrome 83에서 제공되었습니다.

Google에서는 Chrome 안정화 버전으로 출시되었으며 이번 이전 과정에서 도입된 회귀 1건을 파악하고 있습니다. 관련 없는 default 내보내기로 인해 명령어 메뉴의 스니펫 자동 완성이 작동하지 않습니다. 다른 몇 가지 성능 저하도 있었지만, Google의 자동 테스트 도구 모음 및 Chrome Canary 사용자가 이를 보고하여 Chrome 공개 버전 사용자에게 도달하기 전에 이를 수정했습니다.

crbug.com/1006759에서 전체 여정을 확인할 수 있습니다. 모든 CL이 이 버그에 첨부된 것은 아니지만 대부분이 포함되어 있습니다.

알게 된 점

  1. 과거에 내린 결정이 프로젝트에 장기적인 영향을 미칠 수 있습니다. 자바스크립트 모듈 (및 다른 모듈 형식)은 상당히 오랫동안 사용 가능했지만, DevTools는 마이그레이션을 정당화할 수 있는 위치에 있지 않았습니다. 이전할 시기와 이전하지 않을 시기를 결정하는 것은 교훈적인 추측에 힘입어 판단하기 어렵습니다.
  2. 초기 예상 소요 시간은 몇 개월이 아닌 몇 주였습니다. 이는 주로 초기 비용 분석에서 예상했던 것보다 더 많은 예기치 않은 문제를 발견했기 때문입니다. 마이그레이션 계획은 안정된 상태였지만 기술적 부채가 장애물 역할을 하는 경우가 많았습니다.
  3. 자바스크립트 모듈 이전 작업에는 상당량의 기술 부채 정리 작업이 포함되었습니다. 최신 표준 모듈 형식으로 이전한 덕분에 코딩 권장사항을 최신 웹 개발에 맞게 재조정할 수 있었습니다. 예를 들어 커스텀 Python 번들러를 최소 Rollup 구성으로 대체할 수 있었습니다.
  4. 코드베이스에 큰 영향을 미치고 (코드의 약 20% 가 변경됨)에도 불구하고 회귀는 거의 보고되지 않았습니다. 처음 몇 개의 파일을 이전할 때 수많은 문제가 있었지만, 얼마 지나지 않아 탄탄하고 부분적으로 자동화된 워크플로가 구현되었습니다. 따라서 이번 이전으로 안정화 버전 사용자에게 미치는 부정적인 영향을 최소화했습니다.
  5. 특정 마이그레이션과 관련된 복잡성을 동료 유지관리자에게 가르치는 것은 어렵고 때로는 불가능할 수도 있습니다. 이러한 규모의 마이그레이션은 따르기가 어려우며 많은 도메인 지식이 필요합니다. 동일한 코드베이스에서 작업하는 다른 사람에게 도메인 지식을 전달하는 것은 그들이 하고 있는 일에 바람직하지 않습니다. 공유할 내용과 공유하지 말아야 할 세부사항을 아는 것은 일종의 기술이지만 꼭 필요한 사항입니다. 따라서 대규모 마이그레이션의 양을 줄이거나, 적어도 동시에 이를 수행하지 않는 것이 중요합니다.

미리보기 채널 다운로드

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

Chrome DevTools팀에 문의하기

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

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