우수사례: DevTools를 사용한 Angular 디버깅 개선

디버깅 환경 개선

지난 몇 달 동안 Chrome DevTools 팀은 Angular 팀과 협력하여 Chrome DevTools의 디버깅 환경을 개선했습니다. 두 팀의 인력이 협력하여 개발자가 작성 관점에서 웹 애플리케이션을 디버그하고 프로파일링할 수 있도록 지원하는 조치를 취했습니다. 즉, 출발어와 프로젝트 구조 측면에서 사용자에게 익숙하고 관련성이 높은 정보에 액세스할 수 있습니다.

이 게시물에서는 이를 달성하기 위해 Angular와 Chrome DevTools에서 어떤 변경 사항이 필요한지 자세히 살펴봅니다. 이러한 변경사항 중 일부는 Angular를 통해 나오지만 다른 프레임워크에도 적용될 수 있습니다. Chrome DevTools 팀은 사용자에게 더 나은 디버깅 환경을 제공할 수 있도록 다른 프레임워크에서도 새로운 콘솔 API와 소스 지도 확장 지점을 채택하도록 권장합니다.

무시 목록 코드

Chrome DevTools를 사용하여 애플리케이션을 디버깅할 때 작성자는 일반적으로 자신의 코드만 확인하고 싶어 하며, 아래에 있는 프레임워크의 코드나 node_modules 폴더에 숨겨진 일부 종속 항목은 보고 싶어 하지 않습니다.

이를 위해 DevTools 팀은 x_google_ignoreList라는 소스 맵 확장 프로그램을 도입했습니다. 이 확장 프로그램은 프레임워크 코드 또는 번들러에서 생성된 코드와 같은 서드 파티 소스를 식별하는 데 사용됩니다. 프레임워크에서 이 확장 프로그램을 사용하면 이제 작성자는 이 확장 프로그램을 미리 수동으로 구성할 필요 없이 보거나 단계별로 실행하고 싶지 않은 코드를 자동으로 피할 수 있습니다.

실제로 Chrome DevTools는 스택 트레이스, 소스 트리, Quick Open 대화상자에서 식별된 코드를 자동으로 숨기고 디버거에서의 스테핑 및 재개 동작을 개선할 수 있습니다.

DevTools의 전후 비교를 보여주는 애니메이션 GIF 이후 이미지에서 DevTools가 트리에 작성 코드를 표시하고, 'Quick Open' 메뉴에서 더 이상 프레임워크 파일을 추천하지 않으며, 오른쪽에 훨씬 더 깔끔한 스택 트레이스를 표시합니다.

x_google_ignoreList 소스 맵 확장

소스 맵에서 새 x_google_ignoreList 필드는 sources 배열을 참조하고 해당 소스 맵에 있는 모든 알려진 서드 파티 소스의 색인을 나열합니다. 소스 맵을 파싱할 때 Chrome DevTools는 이를 사용하여 무시 목록에 추가할 코드의 섹션을 파악합니다.

다음은 생성된 파일 out.js의 소스 맵입니다. 출력 파일 생성에 기여한 원본 sources 두 개(foo.jslib.js)가 있습니다. 전자는 웹사이트 개발자가 작성한 것이고 후자는 개발자가 사용한 프레임워크입니다.

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "lib.js"],
  "sourcesContent": ["...", "..."],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE;"
}

sourcesContent는 두 가지 원본 소스 모두에 포함되어 있으며 Chrome DevTools는 기본적으로 이러한 파일을 Debugger에 표시합니다.

  • 소스 트리의 파일로.
  • 바로 열기 대화상자의 결과
  • 중단점에서 일시중지된 동안 및 스테핑 중에 오류 스택 트레이스에서 매핑된 호출 프레임 위치

이제 소스 맵에 포함된 추가 정보를 통해 이러한 소스 중 퍼스트 파티 코드와 서드 파티 코드 중 하나를 식별할 수 있습니다.

{
  ...
  "sources": ["foo.js", "lib.js"],
  "x_google_ignoreList": [1],
  ...
}

x_google_ignoreList 필드에는 sources 배열을 참조하는 단일 색인이 포함됩니다(1). 이는 lib.js에 매핑된 리전이 무시 목록에 자동으로 추가되어야 하는 서드 파티 코드임을 나타냅니다.

아래에 표시된 더 복잡한 예시에서 색인 2, 4, 5는 lib1.ts, lib2.coffee, hmr.js에 매핑된 리전이 모두 무시 목록에 자동으로 추가되어야 하는 서드 파티 코드임을 지정합니다.

{
  ...
  "sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
  "x_google_ignoreList": [2, 4, 5],
  ...
}

프레임워크 또는 번들러 개발자인 경우, Chrome DevTools의 이러한 새로운 기능을 연결할 수 있도록 빌드 프로세스 중에 생성되는 소스 맵에 이 필드를 포함해야 합니다.

x_google_ignoreList(Angular)

Angular v14.1.0부터 node_moduleswebpack 폴더의 콘텐츠가 'to ignore'로 표시되었습니다.

webpack의 Compiler 모듈에 후크하는 플러그인을 생성하여 angular-cli를 변경함으로써 구현되었습니다.

Google 엔지니어가 PROCESS_ASSETS_STAGE_DEV_TOOLING 단계에 후크를 만든 webpack 플러그인은 webpack에서 생성하고 브라우저가 로드하는 최종 애셋의 소스 맵에 x_google_ignoreList 필드를 채웁니다.

const map = JSON.parse(mapContent) as SourceMap;
const ignoreList = [];

for (const [index, path] of map.sources.entries()) {
  if (path.includes('/node_modules/') || path.startsWith('webpack/')) {
    ignoreList.push(index);
  }
}

map[`x_google_ignoreList`] = ignoreList;
compilation.updateAsset(name, new RawSource(JSON.stringify(map)));

연결된 스택 트레이스

스택 트레이스는 '어떻게 여기까지 왔나요?'에 대한 질문에 답하지만, 주로 머신의 관점에서 본 결과이며 개발자의 관점이나 애플리케이션 런타임의 정신 모델과 일치하지 않을 수도 있습니다. 특히 일부 작업이 나중에 비동기식으로 실행되도록 예약한 경우에는 더욱 그러합니다. 이러한 작업의 '근본 원인'이나 스케줄링 측면을 아는 것은 여전히 흥미로울 수 있지만, 비동기 스택 추적에는 포함되지 않습니다.

V8에는 setTimeout와 같은 표준 브라우저 예약 프리미티브가 사용될 때 이러한 비동기 작업을 추적하는 메커니즘이 내부적으로 포함되어 있습니다. 이러한 경우 이 작업이 기본적으로 실행되므로 개발자가 이미 이러한 항목을 검사할 수 있습니다. 그러나 더 복잡한 프로젝트에서는 그렇게 간단하지 않습니다. 특히 영역 추적, 커스텀 태스크 큐 추가를 수행하거나 시간이 지남에 따라 실행되는 여러 작업 단위로 업데이트를 분할하는 프레임워크와 같이 고급 스케줄링 메커니즘이 포함된 프레임워크를 사용할 때는 더욱 그렇습니다.

이 문제를 해결하기 위해 DevTools는 console 객체에 'Async Stack Tagging API'라는 메커니즘을 제공합니다. 이를 통해 프레임워크 개발자는 작업이 예약된 위치와 작업이 실행되는 위치를 모두 힌트를 얻을 수 있습니다.

Async Stack Tagging API

비동기 스택 태그 지정을 사용하지 않으면 프레임워크에 의해 복잡한 방식으로 비동기적으로 실행되는 코드의 스택 트레이스가 예약된 코드에 연결되지 않고 표시됩니다.

예약된 시기에 관한 정보가 없는 일부 비동기 실행 코드의 스택 트레이스입니다. `requestAnimationFrame` 부터 시작되는 스택 트레이스만 표시되며 예약된 시점의 정보는 저장되지 않습니다.

비동기 스택 태그 지정을 사용하면 이 컨텍스트를 제공할 수 있으며 스택 추적은 다음과 같습니다.

예약된 시점에 관한 정보가 포함된 비동기 실행 코드의 스택 트레이스 이전과 달리 스택 트레이스에 `businessLogic` 과 `schedule` 이 포함된다는 점에 유의하세요.

이렇게 하려면 Async Stack Tagging API에서 제공하는 console.createTask()라는 새로운 console 메서드를 사용합니다. 서명은 다음과 같습니다.

interface Console {
  createTask(name: string): Task;
}

interface Task {
  run<T>(f: () => T): T;
}

console.createTask()를 호출하면 나중에 비동기 코드를 실행하는 데 사용할 수 있는 Task 인스턴스가 반환됩니다.

// Task Creation
const task = console.createTask(name);

// Task Execution
task.run(f);

비동기 작업도 중첩될 수 있으며, '근본 원인'이 스택 트레이스에 순서대로 표시됩니다.

태스크는 횟수에 제한 없이 실행할 수 있으며 작업 페이로드는 실행할 때마다 다를 수 있습니다. 예약 사이트의 호출 스택은 작업 객체가 가비지로 수집될 때까지 기억됩니다.

Angular의 Async Stack Tagging API

Angular에서 비동기 작업 전반에 걸쳐 지속되는 Angular의 실행 컨텍스트인 NgZone이 변경되었습니다.

작업을 예약할 때 가능한 경우 console.createTask()를 사용합니다. 결과 Task 인스턴스는 나중에 사용할 수 있도록 저장됩니다. 작업을 호출하면 NgZone은 저장된 Task 인스턴스를 사용하여 작업을 실행합니다.

이러한 변경사항은 pull 요청 #46693#46958을 통해 Angular의 NgZone 0.11.8에 적용되었습니다.

친숙한 통화 프레임

프레임워크는 프로젝트를 빌드할 때 모든 종류의 템플릿 언어에서 코드를 생성하는 경우가 많습니다(예: HTML처럼 보이는 코드를 브라우저에서 실행되는 일반 자바스크립트로 바꿔주는 Angular 또는 JSX 템플릿). 이렇게 생성된 함수 유형에는 그다지 친숙하지 않은 이름이 주어질 때가 있습니다. 축소된 뒤에 한 글자의 이름을 사용하거나, 불분명하거나 익숙하지 않은 이름을 사용할 수도 있습니다.

Angular에서는 스택 트레이스에서 이름이 AppComponent_Template_app_button_handleClick_1_listener와 같은 호출 프레임을 보는 것이 드물지 않습니다.

자동 생성된 함수 이름이 있는 스택 트레이스 스크린샷

이 문제를 해결하기 위해 Chrome DevTools는 이제 소스 맵을 통해 이러한 함수의 이름을 변경할 수 있도록 지원합니다. 소스 맵에 함수 범위의 시작 (즉, 매개변수 목록의 왼쪽 괄호)을 위한 이름 항목이 있는 경우 호출 프레임은 스택 트레이스에 이 이름을 표시해야 합니다.

Angular의 친숙한 호출 프레임

Angular에서 호출 프레임 이름을 바꾸는 것은 지속적인 노력입니다. 이러한 개선사항은 시간이 지남에 따라 점진적으로 적용될 것으로 예상됩니다.

작성자가 작성한 HTML 템플릿을 파싱하는 동안 Angular 컴파일러는 TypeScript 코드를 생성하며, 이 코드는 브라우저가 로드하고 실행하는 JavaScript 코드로 변환 컴파일됩니다.

이 코드 생성 프로세스의 일부로 소스 맵도 생성됩니다. Google은 현재 소스 맵의 'names' 필드에 함수 이름을 포함하고, 생성된 코드와 원본 코드 간의 매핑에서 이러한 이름을 참조하는 방법을 모색하고 있습니다.

예를 들어 이벤트 리스너의 함수가 생성되고 축소 중에 이름이 비친숙하거나 삭제된 경우 이제 소스 맵은 'names' 필드에 이 함수의 더 친숙한 이름을 포함할 수 있으며 함수 범위 시작 부분의 매핑은 이제 이 이름을 참조할 수 있습니다 (즉, 매개변수 목록의 왼쪽 괄호). 그러면 Chrome DevTools가 이러한 이름을 사용하여 스택 트레이스의 호출 프레임 이름을 바꿉니다.

향후 계획

Angular를 테스트 파일럿으로 사용해 작업한 것을 확인하는 것은 훌륭한 경험이었습니다. Google은 프레임워크 개발자의 의견을 듣고 확장 포인트에 대한 의견을 제공하고자 합니다.

우리가 탐구하고 싶은 영역이 더 많이 있습니다. 특히 DevTools에서 프로파일링 환경을 개선하는 방법을 알아야 합니다.