首页
/ 在 Angular 源码仓库中从零构建 Angular DevTools:环境搭建、Dev 调试、E2E 测试与扩展发布的完整指南

在 Angular 源码仓库中从零构建 Angular DevTools:环境搭建、Dev 调试、E2E 测试与扩展发布的完整指南

2026-09-07 17:46:34作者:郁楠烈Hubert

Angular DevTools 是 Angular 官方提供的浏览器扩展,专门用于 Angular 应用的调试与性能分析(Profiling)。本篇指南基于当前仓库 devtools/README.md 的官方开发说明展开,结合仓库内的构建脚本、浏览器 manifest、消息通信架构等源码证据,完整讲解:如何在本地搭建 Angular DevTools 开发环境、启动开发服务器、以 "Load unpacked" 方式安装 dev 构建版本、逐脚本定位调试入口、开启 sourcemap、运行 Cypress 端到端测试,以及产出面向 Chrome / Firefox 的 release 扩展并手工安装加载。读完本文,你将具备独立在该仓库中构建、调试和打包 Angular DevTools 的能力。

Angular DevTools 在仓库中的定位与目录结构

Angular DevTools(devtools/README.md)是一款浏览器 DevTools 扩展,为 Angular 应用提供调试与性能剖析能力。它支持通过两种“外壳(shell)”运行:真实浏览器扩展(chrome shell / firefox shell),以及开发模式下将用户应用加载到 iframe 的 “development shell”。

仓库中的相关工程分布在 devtools/devtools/projects/ 之下,各子工程职责清晰:

目录 职责
devtools/projects/ng-devtools/ 用户看到的面板主应用(Angular UI),提供 Components 树与 Profiler 等视图
devtools/projects/shell-browser/ 浏览器扩展外壳:manifest、后台 service worker、content script、注入逻辑、DevTools 面板注册等
devtools/projects/ng-devtools-backend/ 运行在被检查页面内的后端,负责与 Angular 的调试 API(debug APIs)通信
devtools/projects/protocol/ 面板与后端之间共享的消息协议定义
devtools/projects/shared-utils/ 共享工具库
devtools/src/ 开发模式下的 demo app 与 devserver(即 development shell 的目标)

扩展对外呈现的“组件树 + Profiler”两大能力在仓库文档 devtools/docs/overview.md 中有完整功能级介绍;本文则聚焦如何把整套扩展在自己的机器上构建出来。

环境准备(Set up)

说明:仓库中 devtools/README.md 刻意保留了完整的构建指引,以便 Mozilla Add-On 审核人员可以复现构建过程。所有命令都在仓库根目录执行,而不是 devtools/ 目录内;文中所有文件路径同样以仓库根目录为基准。

支持的操作系统

仓库文档明确:Debian Linux、macOS 以及 Windows(通过 WSL)可以成功构建。现阶段不支持在未使用 WSL 的 Windows 上原生构建。如果你使用 Windows,请先准备好 WSL 环境。

第一步:安装指定版本的 Node

仓库根目录下的 .nvmrc 固定了推荐的 Node 版本。若已配置 nvm,可在仓库根目录直接执行:

nvm install

该命令会读取 .nvmrc 中声明的版本并自动安装、切换。如果你的 Node 管理方式不同,请手动安装与 .nvmrc 一致的版本。

第二步:安装 pnpm 包管理器

Angular 主仓库使用 pnpm 作为包管理器,全局安装即可:

npm install -g pnpm

第三步:安装 NPM 依赖

在仓库根目录执行,--frozen-lockfile 会严格依据 pnpm-lock.yaml 安装,保证依赖版本可复现:

pnpm install --frozen-lockfile

依赖就绪后,就可以开始构建 DevTools 扩展了。

开发构建(Dev builds)

启动开发服务器:pnpm devtools:devserver

pnpm devtools:devserver

该命令会启动一个开发服务器,浏览器访问 http://localhost:4200 即可打开。在仓库根 package.json 中,该脚本实际是:

"devtools:devserver": "ibazel run //devtools/src:devserver"

即通过 ibazel 运行 Bazel 目标 devtools/src/BUILD.bazel 中的 devserver。开发模式下 Angular DevTools 使用 “development shell”:它把被测用户应用(见 devtools/src/app/demo-app/ 下的 demo app)运行在一个 iframe 中,DevTools 面板与用户应用之间通过 消息传递(message passing) 通信。这与真实浏览器扩展(chrome shell)不同——后者依赖 chrome.runtime 端口。得益于 devtools/docs/connection.md 所述的设计,两种外壳使用同一套协议与 message-bus 接口,面板代码无需变更。

Dev Install:作为真实浏览器扩展安装

若要在开发模式下构建并安装为真正的 Chrome 扩展,使用:

pnpm devtools:build:chrome:debug

该脚本实际展开为根 package.json 中的:

"devtools:build:chrome": "bazelisk build --//devtools/projects/shell-browser/src:flag_browser=chrome //devtools/projects/shell-browser/src:prodapp",
"devtools:build:chrome:debug": "pnpm run -s devtools:build:chrome --//devtools:debug"

即通过 flag_browser=chrome 指定浏览器目标,再叠加 --//devtools:debug 标志生成调试版本。构建产物位于:

dist/bin/devtools/projects/shell-browser/src/prodapp

安装步骤:

  1. 打开 chrome://extensions
  2. 开启右上角的 Developer mode(开发者模式)
  3. 点击 Load unpacked(加载已解压的扩展程序),选择上面的 dist/bin/devtools/projects/shell-browser/src/prodapp 目录。

需要注意一个开发工作流细节:每次重新构建扩展后,都需要依次执行三步,否则改动不会生效:

  1. chrome://extensions 中点击扩展的 刷新/重新加载
  2. 在已打开的 Angular DevTools 面板上右键,选择 "Reload frame"(重新加载面板所在的 iframe);
  3. 刷新你正在检查的页面。

仓库中的浏览器 manifest

Chrome 与 Firefox 使用不同的 manifest,是理解扩展各部件如何被注册的关键,源码位于 devtools/projects/shell-browser/src/manifest/

  • manifest.chrome.json:Manifest V3,声明 background.service_workerdevtools_page、三类 content_scriptsall_frames: true),并把 backend_bundle.jsdetect_angular_bundle.js 列为 web_accessible_resources,允许注入页面主世界。其 version 字段为 1.21.0,minimum_chrome_version 为 102。
  • manifest.firefox.json:Manifest V2,使用 background.scripts(持久后台页面),web_accessible_resources 还额外包含 devtools.html

这些文件解释了为什么调试时会看到若干个以 _bundle 结尾的脚本(见下节)。

Debugging:定位并调试各脚本

关于“面板、后台 worker、content scripts、backend 如何按标签页连接并传递消息”,请参见仓库文档 devtools/docs/connection.md,其中包含完整的拓扑图与启动时序(sequence diagram)。简单来说,扩展在一条检查链路上有多个执行环境不同的脚本:

脚本 / 部件 运行位置 职责
面板 UI(ng-devtools 应用) Chrome DevTools 的 "Angular" 面板 frame 用户直接交互的界面
background_bundle.jsbackground.ts 扩展后台(Chrome 为 service worker,Firefox 为持久后台页) 托管 TabManager,按标签页路由消息、切换工具栏图标与 popup
ng_validate_bundle.jsng-validate.ts 被检查页面、隔离世界(isolated world)、所有 frame 向页面主世界注入 detect_angular_bundle.js
detect_angular_bundle.jsdetect-angular.ts 被检查页面、主世界(main world) 检测页面是否为受支持的 Angular 应用
content_script_bundle.jscontent-script.ts 被检查页面、隔离世界、所有 frame 打开到后台的端口、注入 backend、中继消息
backend_bundle.jsbackend.ts 被检查页面、主世界 通过 ng-devtools-backend 与 Angular 调试 API 通信

后台文件 background.ts 的逻辑也印证了这一点:默认使用黑白图标,一旦检测到 Angular 应用就通过 chrome.runtime.onMessage 切换为彩色图标并更新 popup(根据是否 Angular、是否 Ivy、版本是否受支持、是否为 debug 模式分别展示 not-angular.htmlunsupported.htmlproduction.htmlsupported.html)。

调试模式下,上述脚本都应带有 sourcemap 且未被压缩,可以在以下不同位置找到它们:

  • 面板主 UI:运行在独立 frame 中,直接在 UI 上点击 "Inspect Element"(检查元素)即可打开其调试器。注意这会检查整个 Chrome DevTools——Angular DevTools 只是以 iframe 形式被加载在里面;正确的入口位于 index.html/ienfalfjdbdpebioblfackkekamfmbnh/... 路径下。
  • 在被检查页面内直接执行的脚本:位于普通 Sources 面板中 "Angular DevTools" 目录下:
    • backend_bundle.js
    • detect_angular_bundle.js
  • content scripts:同样在被检查页面执行,但运行在隔离环境中。它们出现在普通 Sources 面板的 "Content Scripts" 分区(与 "Page"、"Workspace"、"Overrides" 等平级,必要时先点击展开该分区列表):
    • content_script_bundle.js
    • ng_validate_bundle.js
  • 后台 service worker:进入 chrome://extensions,点击 "Angular DevTools" 扩展上的 "Inspect Views > service worker" 按钮即可打开调试器。

补充一个与消息路由相关的实现细节(源自 devtools/docs/connection.md):content script 运行在无法读取页面 ng 调试全局变量的隔离世界,backend 则运行在能访问 Angular debug API 但无法调用 chrome.* 的主世界。因此 content script 负责把页面(通过 backend.tsSamePageMessageBus)与扩展后台(通过 chrome.runtime.Port)桥接起来,形成面板 ⇄ 后台 doublePipe ⇄ content script ⇄ window.postMessage ⇄ backend 的双重管线。Chrome MV3 下 service worker 约空闲 30 秒会被终止,content script 每 20 秒发送一次 __NG_DEVTOOLS_BEAT 心跳来保活连接。

开启 sourcemap

要在构建产物中获得内联 sourcemap,需要为 tools/defaults.bzl 中的 esbuild 宏添加 sourcemap = "inline" 标志:

sourcemap = "inline"

仓库中 tools/defaults.bzl 负责统一封装并导出 esbuild 宏(底层复用 aspect_rules_esbuild,参见 tools/bazel/esbuild.bzl)。在 tools/bazel/esbuild.bzl 中可以看到 esbuild_checked_in 已经使用了 sourcemap = "external" 这类既有参数示例,可作为配置格式参考。修改后重新执行 dev build,各 bundle 即携带内联 sourcemap,方便在调试器中直接定位到 TypeScript 源码。

运行端到端(E2E)测试

运行 E2E 测试前,需要先启动开发服务器:

pnpm devtools:devserver

Cypress 测试工程位于 devtools/cypress。有两种运行方式:

方式一:交互式 Cypress UI

pnpm devtools:e2e:open

对应根 package.json 中的 cypress open --project ./devtools/cypress,会打开 Cypress 图形界面,便于在调试中逐步查看测试执行。

方式二:headless 模式

pnpm devtools:test:e2e

对应 cypress run --project ./devtools/cypress,适合 CI 或命令行批量执行。集成测试用例位于 devtools/cypress/integration

若只想运行单元测试,仓库还提供了 pnpm devtools:test:unit(即 bazelisk test -- //devtools/...)与 pnpm devtools:test(带 flag_browser=chrome 的 Bazel 测试),可覆盖 devtools/projects/ng-devtools/ 等工程的组件级与状态管理测试。

Release 构建与安装

构建 release 版本

为 Chrome 或 Firefox 构建发布版扩展:

pnpm devtools:build:chrome:release
pnpm devtools:build:firefox:release

两者构建产物都在同一目录:

dist/bin/devtools/projects/shell-browser/src/prodapp

对应的脚本定义(见根 package.json):

"devtools:build:chrome:release": "pnpm run -s devtools:build:chrome",
"devtools:build:firefox:release": "pnpm run -s devtools:build:firefox --jobs 4"

其中 Firefox 构建使用 --config snapshot-build-firefox 并限制并发(--jobs 4),Chrome 构建通过 --//devtools/projects/shell-browser/src:flag_browser=chrome 设定浏览器标志;相关 flag 定义在 devtools/projects/shell-browser/src/BUILD.bazel 中(flag_browserchrome/firefox 取值)。

安装到浏览器

Chrome:从 dist/bin/devtools/projects/shell-browser/src/prodapp 目录,按照 Chrome 官方 "Load unpacked"(加载已解压扩展)的通用步骤安装即可,即在 chrome://extensions 开启开发者模式后选择该目录。

Firefox:需要加载临时附加组件:

  1. 打开 about:debugging 页面;
  2. 点击 "This Firefox" 选项;
  3. 点击 Load Temporary Add-on(临时载入附加组件) 按钮;
  4. 直接选择 dist/bin/devtools/projects/shell-browser/src/prodapp 目录中的 manifest 文件(Firefox 加载的是 manifest.firefox.json 对应的 manifest)。

Firefox 使用 MV2 持久后台页(background.scripts),与 Chrome 的 MV3 service worker 生命周期不同,因此热重载与调试方式略有差异;如需在 Firefox 中进行开发调试,可参考构建脚本 devtools:build:firefox:debug

发布流程补充

当完成本地验证、希望产出正式分发包时,仓库还提供了一键发布脚本(参见 devtools/docs/release.md):

pnpm run devtools:release

它对应根 package.json 中的 node devtools/tools/release.mts,其执行逻辑位于 devtools/tools/release.mts,会引导完成后续的分发步骤。

常见问题与进一步阅读

  • 为什么本地构建命令都基于 Bazel / ibazel? Angular 主仓库使用 Bazel 管理构建(参见 tools/bazel),DevTools 也沿用了同一套体系,因此所有 devtools 脚本内部都是 bazelisk / ibazel 调用。
  • dev 版与 release 版的差异是什么? dev 版通过 --//devtools:debug 标志保留 sourcemap、不做压缩,方便断点调试;release 版则会进行完整压缩与生产化处理。
  • 想了解扩展如何支持 iframe、多 frame 切换以及 MV3 心跳机制? 阅读 devtools/docs/connection.md,其中详述了 TabManager 的按 tab + frame 连接管理、enableFrameConnection 帧选择、Chrome service worker 生命周期与 20 秒心跳保活机制。
  • 想了解面板两大标签页(Components、Profiler)的具体用法? 阅读 devtools/docs/overview.md,其中覆盖组件树探索、属性查看/编辑、控制台 $ng0/$ng1 快捷访问、宿主节点与源码跳转,以及 Profiler 的变更检测时间线、火焰图视图、OnPush 调试与记录导入导出。

遵循上述流程,你就能在本地完整地完成 Angular DevTools 从“装依赖”到“出扩展包”的整条链路,并借助源码级调试能力深入其内部实现。

登录后查看全文
热门项目推荐
相关项目推荐