Next.js Bundle Analyzer:基于交互式 Treemap 的 Bundle 体积与依赖分析工具深度解析
本篇技术文章聚焦 Next.js 仓库中的 Bundle Analyzer 应用(apps/bundle-analyzer/README.md):它是一个专门用于可视化 bundle 体积、分析依赖关系的 Next.js 应用。读完后你将掌握它的五大核心功能(交互式 Treemap、路由级分析、依赖链追踪、环境/文件类型过滤、文件搜索)、它与主 next 包"构建期内嵌(vendor)"的分发机制,以及底层二进制分析数据格式(analyze.data / modules.data)的解析原理,并知道如何通过 next experimental-analyze 在实际项目中使用它。
它是什么:一个内嵌于 next 包的独立 Next.js 应用
根据 README 的定义,Bundle Analyzer 是:
A Next.js application for visualizing bundle sizes and analyzing dependencies using interactive treemaps.
从 package.json 可以确认其工程属性:
- 包名为
@next/bundle-analyzer-ui,且声明了"private": true—— 它不会发布到 npm; - README 明确指出:"This package is not published to npm. Instead it's built and vendored into the main
nextpackage during its build process."(它不发布到 npm,而是在主next包的构建过程中被构建并内嵌进去); - 它本身就是一个用 React 19 + Tailwind CSS 4 + Radix UI 组件构建的完整 Next.js App Router 应用,入口为 app/page.tsx 和 app/layout.tsx(页面元数据标题即为 "Next.js Bundle Analyzer")。
从源码结构看"vendor 内嵌"是如何落地的
这一分发机制在仓库中有两处源码证据:
- 构建依赖声明:packages/next/turbo.json 中,主
next包的build任务声明了"dependsOn": ["@next/bundle-analyzer-ui#build", "^build"],即每次构建next包时会先触发 Bundle Analyzer UI 的构建(其构建脚本为next build --no-mangling,见 package.json 的 scripts 字段)。 - 运行时复制:在 packages/next/src/build/analyze/index.ts 中,
analyze函数完成 Turbopack 分析后执行:
await cp(path.join(__dirname, '../../bundle-analyzer'), analyzeDir, {
recursive: true,
})
也就是说,next 包内 dist/bundle-analyzer 目录(即构建期内嵌的静态站点)会被整体复制到目标项目的 .next/diagnostics/analyze 目录下,与 data/ 数据目录放在一起供本地服务器托管。
核心功能:README 五大特性的源码印证
README 的 Features 一节列出了五项核心能力,下文逐项结合源码展开。
1. 交互式 Treemap(Interactive Treemap)
页面主体由 TreemapVisualizer 组件渲染,布局算法实现在 lib/treemap-layout.ts 与 lib/layout-treemap.ts 中。从 page.tsx 的渲染代码可以看到,Treemap 以 SizeMode.Compressed(压缩后体积)作为默认尺寸模式:
<TreemapVisualizer
analyzeData={analyzeData}
sourceIndex={rootSourceIndex}
selectedSourceIndex={selectedSourceIndex ?? rootSourceIndex}
...
sizeMode={SizeMode.Compressed}
/>
交互细节还包括:
- 底部状态栏实时显示悬停文件的名称、压缩体积以及 client / server / traced 徽标(见 page.tsx 底部
hoveredNodeInfo渲染逻辑); - 按
Escape键可将 Treemap 选区重置回当前路由的根 source(handleKeyDown逻辑); - 右侧详情面板(Sidebar)宽度可在 10%–50% 之间拖拽调整。
2. 路由级分析(Route-based Analysis)
分析数据按路由组织存储:根路由读取 data/analyze.data,其余路由读取 data/<route>/analyze.data。page.tsx 中的数据路径推导逻辑:
let analyzeDataPath
if (selectedRoute && selectedRoute === '/') {
analyzeDataPath = 'data/analyze.data'
} else if (selectedRoute) {
analyzeDataPath = `data/${selectedRoute.replace(/^\//, '')}/analyze.data`
}
路由列表来源是 packages/next/src/build/analyze/index.ts 中 collectRoutesForAnalyze 函数生成的 routes.json:它同时扫描 app 与 pages 目录(支持 hybrid 项目),经 discoverRoutes + generateRoutesManifest 得到全部静态/动态路由后序列化写入 analyzeDir/data/routes.json。UI 侧由 RouteTypeahead 组件提供路由输入联想。数据文件类型定义(RouteManifest)见 lib/types.ts。
3. 依赖链追踪(Dependency Tracking)
点击某个文件后,import-chain 组件基于模块图展示导入链路与依赖关系。底层计算依赖 lib/module-graph.ts 中的 computeActiveEntries 与 computeModuleDepthMap,数据则来自全局共享的 modules.data(所有路由共用一份模块表,每路由只持有自己的 analyze.data):
const { data: modulesData } = useSWR<ModulesData>('data/modules.data', fetchModulesData)
4. 过滤控制(Filter Controls)
README 说明可以按环境(client/server)和文件类型(JS/CSS/JSON/Assets)过滤。page.tsx 顶栏提供环境单选(Monitor/Server 图标对应的 Client/Server)与文件类型多选(JavaScript/CSS/JSON/Asset),过滤谓词 filterSource 通过 analyzeData.getSourceFlags(sourceIndex) 判定每个 source 的归属:
const hasEnvironment =
(environmentFilter === Environment.Client && flags.client) ||
(environmentFilter === Environment.Server && flags.server)
getSourceFlags 的判定规则(见 lib/analyze-data.ts)颇具信息量:
- 输出文件以
[client-fs]/开头 → 标记为 client; - 以
[project]/开头 → 标记为 server + traced(即被追踪保留到运行时的服务端文件); - 其余 → server;
- 按扩展名区分类型:
.js/.mjs/.cjs→ js,.css→ css,.json→ json,其他 → asset。
5. 文件搜索(Search Functionality)
FileSearch 组件(基于 cmdk 实现)对 bundle 内文件进行搜索,搜索词 searchQuery 传入 Treemap 高亮匹配项。文件索引由 AnalyzeData 构造时建立的 pathToSourceIndex 路径映射与 ModulesData 的 pathToModuleIndex 映射提供支撑。
数据协议:JSON 头 + 二进制边的 .data 文件格式
这是本应用最有技术含量的部分。lib/analyze-data.ts 注释说明这些类型"匹配 Rust 侧 analyze.rs 的结构"——即 Turbopack(Rust)产出分析数据,TypeScript 侧负责解析与渲染。文件布局为:
[ u32: JSON 头长度 ][ JSON 头 ][ 二进制边数据(u32 数组) ]
其中:
AnalyzeDataHeader(analyze.data):sources(source 树,含parent_source_index组成目录层级)、chunk_parts(每个 chunk 分片记录source_index/output_file_index/size/compressed_size)、output_files(输出文件名)、三组EdgesDataReference(output_file_chunk_parts、source_chunk_parts、source_children)以及source_roots;ModulesDataHeader(modules.data):modules表加上module_dependents、module_dependencies及其 async/traced 变体共 6 组边引用。
readEdgesDataAtIndex 用 CSR(压缩稀疏行)风格读取边表:只读取目标索引所需的前后两个 offset,避免整表反序列化,使单文件依赖查询接近 O(出度) 而非 O(总边数)。在此基础上封装了 getOwnSizes / getRecursiveSizes(递归体积,支持过滤谓词)、sourceChunks(所在 chunk 列表)、getRecursiveModuleCount 等 API,供 Treemap 布局与侧边栏统计调用。
如何使用:next experimental-analyze 的完整链路
虽然本应用"不发布 npm",但它是 next experimental-analyze 命令的可视端。从 packages/next/src/build/analyze/index.ts 可以还原完整调用链与参数:
export type AnalyzeOptions = {
dir: string
reactProductionProfiling?: boolean
noMangling?: boolean
appDirOnly?: boolean
output?: boolean
port?: number
}
| 参数 | 默认值 | 作用 |
|---|---|---|
dir |
- | 项目目录,.next 作为 distDir |
noMangling |
false |
不做符号混淆,便于阅读模块名 |
appDirOnly |
false |
仅分析 app 目录路由(路由收集时传入 discoverRoutes) |
output |
false |
true 时仅产出结果文件到 .next/diagnostics/analyze,不启动服务器 |
port |
4000 |
交互式结果服务器的监听端口(仅监听 localhost) |
执行流程:
- 强制
process.env.TURBOPACK ??= '1'—— analyze 是 Turbopack 专属能力(源码注释:"analyze is Turbopack-only"); - 以
PHASE_ANALYZE阶段加载项目配置,调用turbopackAnalyze执行生产构建级分析并返回耗时; - 将
next包内嵌的bundle-analyzer静态站点复制到.next/diagnostics/analyze,写入data/routes.json; - 若未指定
output,调用startServer:基于serve-handler在localhost:<port>(默认 4000)启动静态服务器,打印Bundle analyzer available at http://<address>后阻塞等待。
开发该应用本身时,next.config.mjs 有一个值得注意的配置:开发模式下将 /data/:path* 重写代理到 http://localhost:4000/data/:path*,即从正在运行的 next experimental-analyze 服务器实时拉取分析数据调试 UI;而构建发布时使用 output: 'export' 导出纯静态站点到 dist。此外构建脚本携带 NEXT_TEST_NATIVE_IGNORE_LOCAL_INSTALL=true 环境变量,表明它使用 next 包内置的原生二进制而非本地工作区安装。
维护约定:重要变更需同步更新 Demo 站点
README 的 Updating 一节给出了一条明确的维护约定:当落地非平凡(non-trivial)的改动时,应考虑同步更新官方 demo 站点(README 中附带了 demo 站点的 URL 与独立仓库地址,此处不再重复外链)。这说明 Bundle Analyzer 的 UI 迭代是以一个独立演示仓库为回归/展示基线的。
小结
- Bundle Analyzer 是 Next.js 仓库中一个私有、构建期内嵌进
next包的工具型 Next.js 应用,服务于next experimental-analyze命令的交互式结果展示; - 其数据协议由 Turbopack(Rust)以"JSON 头 + CSR 边表"的二进制格式产出,TypeScript 端按需解析,兼顾体积与查询效率;
- 功能面覆盖 Treemap 可视化、按路由下钻、import 依赖链、client/server 环境与 JS/CSS/JSON/Asset 类型过滤、文件搜索,并支持
--output离线产出与--port自定义端口; - 相关关键文件:README、数据解析、类型定义、路由与模块图、分析入口。
适用前提说明:analyze 功能基于 Turbopack,且分析的是生产构建(源码日志提示 "Analyzing a production build..."),在开发本仓库内的该应用需按 package.json 中的 scripts 执行并配合 localhost:4000 的数据服务。
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 StartedRust0622
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