在 Angular 源码仓库中从零构建 Angular DevTools:环境搭建、Dev 调试、E2E 测试与扩展发布的完整指南
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
安装步骤:
- 打开
chrome://extensions; - 开启右上角的 Developer mode(开发者模式);
- 点击 Load unpacked(加载已解压的扩展程序),选择上面的
dist/bin/devtools/projects/shell-browser/src/prodapp目录。
需要注意一个开发工作流细节:每次重新构建扩展后,都需要依次执行三步,否则改动不会生效:
- 在
chrome://extensions中点击扩展的 刷新/重新加载; - 在已打开的 Angular DevTools 面板上右键,选择 "Reload frame"(重新加载面板所在的 iframe);
- 刷新你正在检查的页面。
仓库中的浏览器 manifest
Chrome 与 Firefox 使用不同的 manifest,是理解扩展各部件如何被注册的关键,源码位于 devtools/projects/shell-browser/src/manifest/:
- manifest.chrome.json:Manifest V3,声明
background.service_worker、devtools_page、三类content_scripts(all_frames: true),并把backend_bundle.js、detect_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.js(background.ts) |
扩展后台(Chrome 为 service worker,Firefox 为持久后台页) | 托管 TabManager,按标签页路由消息、切换工具栏图标与 popup |
ng_validate_bundle.js(ng-validate.ts) |
被检查页面、隔离世界(isolated world)、所有 frame | 向页面主世界注入 detect_angular_bundle.js |
detect_angular_bundle.js(detect-angular.ts) |
被检查页面、主世界(main world) | 检测页面是否为受支持的 Angular 应用 |
content_script_bundle.js(content-script.ts) |
被检查页面、隔离世界、所有 frame | 打开到后台的端口、注入 backend、中继消息 |
backend_bundle.js(backend.ts) |
被检查页面、主世界 | 通过 ng-devtools-backend 与 Angular 调试 API 通信 |
后台文件 background.ts 的逻辑也印证了这一点:默认使用黑白图标,一旦检测到 Angular 应用就通过 chrome.runtime.onMessage 切换为彩色图标并更新 popup(根据是否 Angular、是否 Ivy、版本是否受支持、是否为 debug 模式分别展示 not-angular.html、unsupported.html、production.html、supported.html)。
调试模式下,上述脚本都应带有 sourcemap 且未被压缩,可以在以下不同位置找到它们:
- 面板主 UI:运行在独立 frame 中,直接在 UI 上点击 "Inspect Element"(检查元素)即可打开其调试器。注意这会检查整个 Chrome DevTools——Angular DevTools 只是以 iframe 形式被加载在里面;正确的入口位于
index.html/ienfalfjdbdpebioblfackkekamfmbnh/...路径下。 - 在被检查页面内直接执行的脚本:位于普通 Sources 面板中 "Angular DevTools" 目录下:
backend_bundle.jsdetect_angular_bundle.js
- content scripts:同样在被检查页面执行,但运行在隔离环境中。它们出现在普通 Sources 面板的 "Content Scripts" 分区(与 "Page"、"Workspace"、"Overrides" 等平级,必要时先点击展开该分区列表):
content_script_bundle.jsng_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.ts 的 SamePageMessageBus)与扩展后台(通过 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_browser、chrome/firefox 取值)。
安装到浏览器
Chrome:从 dist/bin/devtools/projects/shell-browser/src/prodapp 目录,按照 Chrome 官方 "Load unpacked"(加载已解压扩展)的通用步骤安装即可,即在 chrome://extensions 开启开发者模式后选择该目录。
Firefox:需要加载临时附加组件:
- 打开
about:debugging页面; - 点击 "This Firefox" 选项;
- 点击 Load Temporary Add-on(临时载入附加组件) 按钮;
- 直接选择
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 从“装依赖”到“出扩展包”的整条链路,并借助源码级调试能力深入其内部实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00