用 Claude Code 做 JavaScript Bundle 分析实战指南
用 Claude Code 分析 Vite/Astro/Next 打包体积、重复依赖、动态导入与 CI 预算。
JavaScript bundle 变重,通常不是因为一次明显的错误,而是因为很多合理需求慢慢叠加:图表、富文本编辑器、日期库、地图、视频播放器、A/B 测试代码、认证 SDK。问题不在于这些功能不该做,而在于它们经常被一起发给了第一次打开页面的用户。
Bundle 分析就是检查生产构建后真正发给浏览器的 JavaScript 和 CSS。用更直白的话说,就是先称行李有多重,再打开箱子看是哪几件东西最占重量。Claude Code 可以帮你读报告、找依赖、改代码、写 CI,但前提是你给它明确的测量流程,而不是只说“帮我优化性能”。
本文面向 Vite、Astro、Next.js 类项目,覆盖 rollup-plugin-visualizer、source-map-explorer、重复依赖检查、dynamic import、CI bundle budget 和 Claude Code review。相关基础可以继续看 代码分割、tree shaking 与 性能优化。
工作流先固定
分析时常见的数字有 raw、gzip、brotli。raw 是压缩前大小,适合看 treemap;gzip 和 brotli 更接近真实网络传输。不要只盯一个数字,因为有些库压缩后不大,但解析和执行仍然耗时。
flowchart LR
A["production build"] --> B["visual report"]
B --> C["duplicate packages"]
C --> D["replace or dedupe"]
B --> E["route-level split"]
D --> F["bundle budget in CI"]
E --> F
F --> G["Claude Code review"]
官方资料要作为判断依据:Vite 的 Building for Production、Astro 的 Analyze bundle size、Next.js 的 Package Bundling、web.dev 的 Performance budgets 101,以及 Claude Code 官方概览。
可视化 Vite 和 Astro
Vite 项目可以先安装 rollup-plugin-visualizer。它会生成一个 HTML treemap,显示每个依赖在构建产物中的占比。
npm install -D rollup-plugin-visualizer
// vite.config.ts
import { defineConfig } from "vite";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
visualizer({
filename: "dist/bundle-stats.html",
template: "treemap",
gzipSize: true,
brotliSize: true,
open: false
})
],
build: {
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
react: ["react", "react-dom"],
charts: ["recharts"],
editor: ["@tiptap/react", "@tiptap/starter-kit"]
}
}
}
}
});
Astro 里放到 vite 配置下:
// astro.config.mjs
import { defineConfig } from "astro/config";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
vite: {
plugins: [
visualizer({
filename: "dist/bundle-stats.html",
template: "treemap",
gzipSize: true,
brotliSize: true
})
],
build: { sourcemap: true }
}
});
npm run build
open dist/bundle-stats.html
Windows PowerShell 使用:
start dist/bundle-stats.html
报告中优先寻找首屏不需要的内容:图表库、管理后台编辑器、地图、视频播放器、Markdown 转换器、过大的日期库、只在后台使用的 UI。
读取 source map 和 Next.js 报告
source-map-explorer 能根据 JS 文件和 source map 展示最终 bundle 的来源。Vite 中先开启 sourcemap: true,再执行:
npm install -D source-map-explorer
npm run build
npx source-map-explorer "dist/assets/*.js" --html dist/source-map-report.html
Next.js 项目要先确认当前版本和构建器,再选择官方 bundle analyzer 或 @next/bundle-analyzer。不要把旧 webpack 教程直接复制到 Turbopack 项目里。
// next.config.mjs
import bundleAnalyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true"
});
export default withBundleAnalyzer({
reactStrictMode: true
});
npm install -D @next/bundle-analyzer
ANALYZE=true npm run build
重复依赖检查
大库容易被看到,重复依赖更容易漏掉。比如 date-fns 同时存在两个主版本,或者 lodash 与 lodash-es 混用,都会让 bundle 变胖。
npm ls date-fns lodash lodash-es
npm dedupe
pnpm 项目:
pnpm why date-fns
pnpm dedupe
给 Claude Code 的请求要把调查和修改分开:
请分析本仓库的生产 bundle。
1. 根据 dist/bundle-stats.html 或 source-map-explorer 输出列出重依赖
2. 用 npm ls 或 pnpm why 检查重复包
3. 把候选项分成 replace、dedupe、dynamic import、remove
4. 保留现有 UI、SEO 文本、CTA 和 analytics 事件
5. 做最小安全修改,并运行 npm run build 与 bundle budget 检查
| 原因 | 处理方式 | 合并前检查 |
|---|---|---|
moment 全站加载 | 改用 Intl.DateTimeFormat 或小型工具 | 时区和语言 |
整包导入 lodash | 改成函数级导入或原生 API | ESM/CommonJS 混用 |
| 后台编辑器很重 | 点击后或后台路由再加载 | loading 与错误状态 |
| 图表库进首页 | 分离到报表页面 | 响应式布局 |
| 依赖多版本 | dedupe 或对齐版本 | peer dependency |
用 dynamic import 移出首屏
dynamic import 不是让代码消失,而是把加载时机推迟到用户真正需要时。适合后台报表、富文本编辑器、地图、小概率使用的弹窗。
// src/features/reports/ReportsButton.tsx
import { useState } from "react";
export function ReportsButton() {
const [html, setHtml] = useState<string>("");
const [loading, setLoading] = useState(false);
async function handleClick() {
setLoading(true);
const { renderRevenueReport } = await import("./renderRevenueReport");
setHtml(renderRevenueReport([12000, 18400, 9300]));
setLoading(false);
}
return (
<section>
<button type="button" onClick={handleClick} disabled={loading}>
{loading ? "生成报表中" : "显示收入报表"}
</button>
<output aria-live="polite">{html}</output>
</section>
);
}
// src/features/reports/renderRevenueReport.ts
export function renderRevenueReport(values: number[]): string {
const total = values.reduce((sum, value) => sum + value, 0);
return `本月合计: ${new Intl.NumberFormat("zh-CN").format(total)} 元`;
}
Next.js 中,搜索需要的正文、价格、购买按钮、咨询 CTA 应保持服务端输出,只延迟后台编辑器:
// app/admin/EditorSlot.tsx
"use client";
import dynamic from "next/dynamic";
const RichEditor = dynamic(() => import("./RichEditor"), {
ssr: false,
loading: () => <p aria-live="polite">正在加载编辑器...</p>
});
export function EditorSlot() {
return <RichEditor initialMarkdown="# Draft" />;
}
在 CI 中设置预算
优化一次不够,下一次 PR 可能又会引入大依赖。把预算放进 CI,超过时必须解释原因。
// scripts/check-bundle-budget.mjs
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
import { brotliCompressSync, gzipSync } from "node:zlib";
const targetDir = "dist/assets";
const maxTotalGzip = 220 * 1024;
const maxSingleGzip = 140 * 1024;
function walk(dir) {
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) return walk(fullPath);
return /\.(js|css)$/.test(entry.name) ? [fullPath] : [];
});
}
if (!existsSync(targetDir)) {
console.error(`Missing ${targetDir}. Run npm run build first.`);
process.exit(1);
}
const rows = walk(targetDir).map((file) => {
const content = readFileSync(file);
return {
file,
raw: statSync(file).size,
gzip: gzipSync(content).byteLength,
brotli: brotliCompressSync(content).byteLength
};
});
const totalGzip = rows.reduce((sum, row) => sum + row.gzip, 0);
const tooLarge = rows.filter((row) => row.gzip > maxSingleGzip);
if (totalGzip > maxTotalGzip || tooLarge.length > 0) {
console.error(`Bundle budget failed. total gzip=${totalGzip} bytes`);
process.exit(1);
}
console.log(`Bundle budget passed. total gzip=${totalGzip} bytes`);
# .github/workflows/bundle-budget.yml
name: Bundle Budget
on: [pull_request]
jobs:
bundle-budget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: node scripts/check-bundle-budget.mjs
初始预算不要设成理想值。先用当前 gzip 结果加约 10% 余量,超过时要求 PR 说明增加原因、影响页面和替代方案。
真实使用场景
第一,SaaS 管理后台。普通用户需要列表、搜索、导航和主要 CTA;管理员才用的图表、审计日志、CSV 导出、账单报表可以拆到独立 chunk。给 Claude Code 指定只改 src/features/admin,避免误改认证和导航。
第二,内容站或课程站。正文、标题、购买链接和咨询 CTA 应尽早显示;Markdown 预览、图片裁剪、活动管理后台可以延迟。这里的性能直接影响广告曝光、联盟链接和咨询转化。可以结合 analytics 实装 一起验证。
第三,带地图、视频、计算器或诊断表单的落地页。首屏保留价值主张、价格、证据和行动按钮,重组件在滚动或点击后加载。媒体 UI 可继续参考 视频播放器 和 可访问性实现。
第四,内部 UI 包。@company/ui 顶层导入可能让 Button 顺带带入 DatePicker、Modal、Chart 和整套图标。应拆分 exports,并明确 CSS、主题初始化等副作用。
常见失败
不要用 dev build 判断体积。Vite dev server、HMR、未压缩代码都不能代表生产 bundle。也不要切得太碎,很多小 chunk 会增加请求和等待时间。source map 只适合分析和错误追踪,公开到 CDN 前要评估安全风险。manualChunks 也不能一劳永逸,依赖升级后要重新看是否还合理。
实施后让 Claude Code 做第二轮 review:
请从 bundle 分析角度 review 这个 PR:
- 首屏依赖是否仍然必要
- 是否存在重复包版本
- dynamic import 是否有 loading 和错误状态
- SEO 文本、价格、CTA、analytics 是否没有被延迟
- bundle budget 失败日志是否能帮助开发者定位原因
- 总结 npm run build 结果和报告路径
收益导线
Bundle 分析不是纯技术整理。首屏更快,正文、商品链接、免费资料和咨询 CTA 就更早出现。对 ClaudeCodeLab 这类内容站来说,它会影响广告收入、模板购买和培训咨询。
个人练习可以从 免费速查表 开始,需要模板时看 产品列表。团队如果要一起整理 Vite、Astro、Next.js、CLAUDE.md、CI 预算和 review 规则,可以从 Claude Code 培训与咨询 进入。
实际验证结果
本文示例按 Vite/React 风格检查过:visualizer 会输出 HTML 报告,source-map-explorer 需要 source map,Node 预算脚本只用内置 fs、path、zlib。实际项目中最有效的不是盲目替换所有大库,而是先区分“首屏必须出现”和“可以稍后加载”。富文本编辑器和图表通常值得拆,正文、价格、购买链接和咨询 CTA 通常不该延迟。
总结
可靠的 Claude Code bundle 优化从证据开始:生产构建、可视化、重复依赖检查、动态导入、CI 预算、二次 review。Bundle analysis、tree shaking、code splitting 不是三件孤立任务,而是让产品在流量增长后仍保持速度和转化的同一套工程流程。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
Claude Code Permission Receipt Pattern:记录权限、证据和回滚方式
Claude Code 权限 receipt:记录允许动作、需要批准的边界、验证命令、回滚说明,以及 Gumroad 和咨询 CTA 检查。
Claude Code/Codex 安全 Agent Harness 实战:权限、验证与回滚
用权限策略、执行计划、验证脚本和回滚日志,为 Claude Code 与 Codex 搭建更安全的 AI Agent 工作流。
Claude Code 子代理实战指南:安全委派并行文章与代码工作
用 Claude Code 子代理安全拆分文章和代码工作:委派规则、提示词模板、失败模式与检查清单。