Lighthouse Treemap Viewer 深度指南:JavaScript 体积可视化工具的开发、构建与数据链路 Lighthouse Treemap Viewer 深度指南JavaScript 体积可视化工具的开发、构建与数据链路【免费下载链接】lighthouseAutomated auditing, performance metrics, and best practices for the web.项目地址: https://gitcode.com/GitHub_Trending/lig/lighthouseLighthouse Treemap 是 Lighthouse 生态中的一款独立 Web 应用用于把一次性能审计结果LHR中的 JavaScript 脚本体积、未使用字节与重复模块等信息以矩形树图treemap的形式可视化呈现。本文以仓库中的 treemap/README.md 为骨架结合 前端入口、数据生成审计 与 构建脚本 的源码实现完整讲解它的使用方式、本地开发流程、数据模型与内部原理帮助你掌握如何把它跑起来、看懂它的界面以及理解一份 LHR 是如何变成矩形树图的。Treemap 在 Lighthouse 中的定位Lighthouse 审计结束后会产出一份 JSON 格式的 Lighthouse ResultLHR。其中有一个专门为 Treemap 应用准备的审计项script-treemap-data它的details字段就是树图所需的全部数据const scriptTreemapData options.lhr.audits[script-treemap-data].details; if (!scriptTreemapData || scriptTreemapData.type ! treemap-data) { throw new Error(missing script-treemap-data); }上面这段代码出自 treemap/app/src/main.js是 Treemap 应用加载数据的硬性前提一份 JSON 里如果没有script-treemap-data这个审计或者它的details.type不是treemap-data应用会直接报错。数据生成端在 core/audits/script-treemap-data.js该审计的元信息定义如下id: script-treemap-data, scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, requiredArtifacts: [Trace, DevtoolsLog, SourceMaps, Scripts, JsUsage, URL, SourceMaps]也就是说script-treemap-data是一个不参与打分的信息型审计它需要 Trace、DevtoolsLog、SourceMaps、Scripts、JsUsage 等工件作为输入最终输出一个type: treemap-data的树结构。这个审计的输出示例可以直接查看仓库自带的样例数据 treemap/app/debug.json它是一份完整的 Lighthouse 12.5.1 LHR采集于 coursehero.com其中就包含script-treemap-data审计。快速使用三种加载数据的方式Treemap 的入口页面是 treemap/app/index.html。页面在无数据时显示一个占位界面提示你粘贴 Lighthouse 结果 JSON 或 Gist URL直接拖拽 LHR 文件到页面点击占位区域弹出文件选择器选择application/json文件。从 treemap/app/src/main.js 的main()逻辑可以看出应用启动时会按优先级依次检查以下数据来源window.__treemapOptions最高优先级如果 HTML 文档内已经内嵌了序列化后的 options保存页面时注入直接用它初始化debug查询参数请求同目录下的debug.json并加载这是本地开发调试最常用的入口lhr查询参数把参数对象直接交给coerceToOptions处理gist查询参数通过GithubApi.getGistFileContentAsJson从 Gist 拉取 JSON。coerceToOptionstreemap/app/src/main.js非常宽容它依次尝试把输入当作完整 LHR、{lhr: ...}或{lighthouseResult: ...}三种结构来解析只要找到带audits对象的那一层就认为是 LHR随后再校验其中是否存在script-treemap-data审计。如果传入了顶层字符串字段initialView它还会被作为初始视图模式all/unused-bytes/duplicate-modules传给渲染器。此外页面还支持剪贴板粘贴treemap/app/src/main.js粘贴的内容会先被当作 Gist URL 解析失败后再尝试当作 JSON 解析两者都不成功才报错提示。而 URL 中出现的gzip1参数则配合 URL hash 中的 Base64 编码数据一起使用。本地开发与构建README 核心流程原文档给出了三条开发命令这里结合仓库 package.json 中的脚本定义做完整说明。1. 启动本地静态服务yarn serve-treemapserve-treemap实际指向serve-gh-pages其定义为serve-gh-pages: cd dist/gh-pages python3 -m http.server 7333即把构建产物目录dist/gh-pages通过 Python 自带的 HTTP 服务器跑在7333端口上无需额外安装 Node 服务器依赖。2. 启动构建监听文件变更时自动重建# dependency: brew install entr find treemap | entr -s DEBUG1 yarn build-treemapentr是一个监听文件变化并执行命令的工具macOS 下用 Homebrew 安装。find treemap | entr会把treemap目录下所有文件的变化喂给entr每次有文件保存就重新执行yarn build-treemap。DEBUG1环境变量与?debug查询参数配合用于让页面加载debug.json样例数据。build-treemap的脚本定义是build-treemap: node ./build/build-treemap.js3. 打开调试页面open http://localhost:7333/treemap/?debug带?debug参数访问时treemap/app/src/main.js 会fetch(debug.json)并加载仓库自带的样例 LHR立刻呈现一张可交互的树图无需准备任何自己的数据。构建脚本做了什么build/build-treemap.js 是整个 Treemap 应用的打包入口。它基于GhPagesApp完成以下工作把 treemap/app/index.html 作为 HTML 模板收集styles/*样式按顺序打包 JS首先生成一份strings全局变量把所有 locale 的本地化 UI 字符串从 shared/localization/locales.js 提取出来只保留util.js中定义过的键然后依次内联idb-keyval、webtreemap-cdt树图渲染库、pakogzip 解压以及通过 esbuild 打包的 treemap/app/src/main.js复制images/**/*与debug.json作为静态资源。如果执行时带有--deploy参数对应yarn deploy-treemap定义见 package.json还会在构建后把产物部署到 GitHub Pages。值得注意的是构建产物中并不包含外部 JavaScript 框架——树图渲染完全依赖webtreemap-cdt这个依赖声明在 package.json 的dependencies中应用的其余逻辑都是自研的。而pako的存在是为了在 URL hash 里传输压缩后的数据时能够解压。运行测试仓库为 Treemap 提供了两类测试脚本见 package.jsonyarn unit-treemap # 运行 treemap/**/*-test.js 单测 yarn test-treemap # 单测 puppeteer 端到端测试其中 treemap/test/util-test.js 覆盖工具函数如路径遍历、稳定哈希等treemap/test/treemap-test-pptr.js 则启动真实浏览器验证页面交互。界面构成与交互模式treemap/app/index.html 中的主界面main区域由四部分组成头部左侧是 Lighthouse logo 与 Lighthouse Treemap 标题中间显示当前文档 URL 与总字节数悬浮提示区分 Transfer bytes 与 Resource bytes右侧是三个控件——视图模式下拉框view-mode-selector、脚本/分包下拉框bundle-selector、表格开关按钮树图区域div.lh-treemap由webtreemap渲染的矩形树图本体明细表格div.lh-table与树图联动的文件名/字节数列表日志区div#lh-log用于展示解析错误等提示信息。交互方面treemap/app/src/main.js 注册了完整的事件监听点击树图节点会重新着色每个深度一级节点及其子树使用同一色系按Enter触发同样操作按Escape调用treemap.zoom([])回到根节点鼠标悬停树图节点或表格行时另一侧会同步高亮对应的节点ResizeObserver监听树图容器尺寸变化并自动重新布局。表格中每行都通过tableRowToNodeMap与树图节点关联实现双向联动在 Unused bytes 视图下表格还会为每行绘制一条覆盖条coverage bar用绿色/红色直观显示已用与未用字节的占比。三种视图模式View Mode视图模式由 types/lhr/treemap.d.ts 中的ViewMode类型约束可用的id只有三种all、unused-bytes、duplicate-modules。它们的创建逻辑在 treemap/app/src/main.jsAll全量显示所有脚本节点的总体积始终可用Unused bytes未使用字节只有当数据中至少有一个节点带unusedBytes字段时才出现。此模式下树图会按未使用字节重新划分面积并通过--pctUnusedCSS 变量给节点叠加阴影未使用比例越高颜色越深表格列切换为 Unused bytes并附覆盖条Duplicate modules重复模块遍历所有叶子节点把duplicatedNormalizedModuleName相同的节点聚成一组如果某个模块存在多份拷贝则累加除最大拷贝外的所有拷贝字节数作为潜在节省量只有潜在节省量超过阈值DUPLICATED_MODULES_IGNORE_THRESHOLD 1024 * 0.5即 512 字节见 treemap/app/src/main.js的组才会被高亮。此模式下重复模块的所有拷贝会以相同颜色高亮表格则按模块名分组展示哪几个 bundle 重复了、能省多少字节。三种模式下表格的排序键也会变化unused-bytes模式按未使用字节排序duplicate-modules模式按重复组聚合展示其余情况按节点大小排序。树图的分区与着色树图面积的分区依据在 treemap/app/src/main.js 初始化时确定如果所有顶层节点的encodedBytes都有定义则默认按Transfer bytesencodedBytes分区否则退化为Resource bytesresourceBytes。这种取舍与数据来源有关从网络记录network record直接生成的脚本节点带encodedBytes传输大小而从 source map 拆分出的模块节点可能没有此时会用顶层脚本的压缩比encodedBytes / resourceBytes来估算每个模块的传输大小。相关计算集中在getNodeSizes、getNodeCompressionRatio、getNodeDisplaySizetreemap/app/src/main.js中并通过nodeToSizesMap缓存避免反复计算污染原始数据。着色方面treemap/app/src/util.js 提供了一个stableHasher它根据节点名的字符码之和从固定的COLOR_HUES色相表中稳定地给每个顶层节点分配一个色相保证同一个脚本每次渲染颜色一致、不同脚本尽量不同。色相通过hsl()生成子节点根据深度调整饱和度和明度让层级关系一目了然同时从同一顶层节点派生出的所有子节点共享同一色相通过nodeToDepthOneNodeMap关联便于区分不同的 JS 文件。节点标题caption的格式为文件名 · 字节数 (百分比)根节点还会额外标注分区类型Transfer bytes / Resource bytes。当选择 All scripts 分组视图时根节点标题会被删除因为头部已显示总字节数避免信息重复。数据是怎么生成的script-treemap-data 审计树图的数据源 core/audits/script-treemap-data.js 是理解整个链路的钥匙。它的makeNodes方法处理每个脚本核心逻辑是1. 有 source map 的脚本 → 按模块拆树。使用JSBundles计算出的 bundle 尺寸bundle.sizes.files来源见 core/computed/js-bundles.js为每个源文件记录resourceBytes结合UnusedJavascriptSummary见 core/computed/unused-javascript-summary.js写入unusedBytes再结合ModuleDuplication见 core/computed/module-duplication.js给重复模块标记duplicatedNormalizedModuleName。makeScriptNode方法把按斜杠分隔的源路径逐段转成树节点并在最终把只有单个子节点的中间层折叠合并生成类似webpack:// → react.js / app.js的目录树。2. 没有 source map 的脚本 → 单节点。只能产生一个叶子节点大小取unusedJavascriptSummary.totalBytes ?? script.length未使用字节取wastedBytes。3. 内联脚本 → 按 frame 合并成 HTML 节点。所有内联脚本被聚合到以文档 URL 命名的顶层节点下若存在则加(inline)标记不同 iframe 的内联脚本各自独立成组。4. 传输大小encodedBytes的计算。对非内联脚本从NetworkRecords中找到对应请求用transferSize - responseHeadersTransferSize即 body 部分的传输字节数作为encodedBytes对内联脚本则按内联脚本字节数占 HTML 文档资源大小的比例乘以 HTML 文档 body 传输字节数来估算。最终audit方法把所有节点包成{type: treemap-data, nodes}返回score: 1表示该审计永远满分为信息型展示。数据结构一览树图中每个节点的结构定义在 types/lhr/treemap.d.tsinterface Node { name: string; // 脚本 URL、source map 路径段或任意标识 resourceBytes: number; // 资源字节数解压后 encodedBytes?: number; // 传输字节数仅非内联顶层脚本节点会有 unusedBytes?: number; // 未使用字节数按资源大小计 duplicatedNormalizedModuleName?: string; // 若存在说明该模块是重复的 children?: Node[]; }应用层的Options则要求提供带script-treemap-data审计的lhr含mainDocumentUrl/finalUrl/finalDisplayedUrl与configSettings.locale以及可选的initialView。URL 的显示优先级在 treemap/app/src/main.js 中有明确注释Lighthouse 10.0 之后的导航报告用mainDocumentUrl10.0 之前用finalUrl而 Timespan / Snapshot 报告两者都没有则退化为finalDisplayedUrl。常见问题与排查思路missing script-treemap-data说明传入的 JSON 不是包含该审计的 LHR或者details.type不是treemap-data。请确认这份 LHR 是由当前版本 Lighthouse或 DevTools、PageSpeed Insights生成的完整结果。provided json is not a Lighthouse resultcoerceToOptions在json、json.lhr、json.lighthouseResult三层里都没找到带audits的对象常见于把 report HTML 当 JSON 解析。数据没有未使用字节信息showUnusedBytes取决于是否有节点带unusedBytes。例如 DevTools 的 RPPRecording Performance Panel模式不会传递 unused bytes此时 Unused bytes 视图不会出现在下拉框中。界面显示的都是 Transfer bytes只要顶层脚本都带encodedBytes应用就会默认按传输大小展示这是自 2025 年 4 月左右起script-treemap-data包含传输大小数据后的正常行为。小结Lighthouse Treemap 把审计出的 JavaScript 体积数据变成了一张可交互、可下钻、可排序的矩形树图配合视图模式可以快速定位体积大头、未使用代码和重复打包的模块。从 treemap/README.md 给出的三条开发命令出发配合debug.json样例数据、script-treemap-data审计与webtreemap渲染链路开发者既可以把它当作独立工具日常使用也可以顺着 treemap/app/src/main.js 和 core/audits/script-treemap-data.js 的源码理解整条LHR → 树图的数据管线甚至在自己的项目中复用这套数据结构与渲染思路。【免费下载链接】lighthouseAutomated auditing, performance metrics, and best practices for the web.项目地址: https://gitcode.com/GitHub_Trending/lig/lighthouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考