Files
book-comic-library/docs/bundle-review.md
T

9.6 KiB
Raw Blame History

前端 bundle 审视(2026-09) / Frontend bundle review

由 cd frontend && npm run analyze 生成 dist-stats/stats.html 后人工审视得出;本期只记录结论,不实施优化(spec ④ S4)。 Generated from dist-stats/stats.html (npm run analyze); findings only — no optimization in this batch (spec ④ S4).

现状 / Current state

实测基线(frontend/dist/,rollup/rolldown 构建产物):总计约 2.6 MB(assets 2,609,869 B;du -sh 显示 2.6M,含 PWA precache 全部条目)。stats.html 内嵌数据提取出的每 chunk 构成如下(rendered ≈ stat,与 ls dist/assets 文件大小一致;gzip/brotli 来自 visualizer 输出,宿主机 gzip -c | wc -c 交叉核对一致)。

Measured baseline (frontend/dist/): ~2.6 MB total (assets 2,609,869 B; du -sh 2.6M). Composition per chunk, extracted from the data embedded in stats.html (rendered ≈ stat, matching ls dist/assets; gzip/brotli from the visualizer output, cross-checked with host-side gzip -c | wc -c).

chunk 入口/来源 stat gzip brotli 备注
index-CGr3zft8.js 主入口(src/main.tsx,shelf/login/admin + react-dom/router/query/radix) 462.6 kB 145.3 kB 122.3 kB react-dom 460 kB + react-router 95 kB + tailwind-merge 56 kB + radix-ui 各子包共 ~131 kB + lucide-react(34 个图标) 17.3 kB(gzip 10.1 kB) + (src) 83.9 kB
pdf.worker-C0DQFlrB.js PdfReader.tsx worker(pdfjs-dist/build/pdf.worker.mjs,Vite worker 语法独立产物) 1,189.3 kB 367.0 kB ~ 独立文件,不进 index 主包
PdfReader-Bv6vuw8i.js pages/Reader.tsx → lazy(() => import(...)) 434.4 kB 130.5 kB 137.9 kB* pdfjs-dist 主库 764.3 kB(brotli 135.4 kB);* 宿主机 gzip 交叉核对为 128.9 kB(visualizer 统计含小数位差异)
src-BNcXNGhn.js 共享 chunk:EpubReader 的 await import("epubjs") 链 345.4 kB 103.6 kB 135.5 kB* epubjs 229.0 kB + jszip 205.6 kB + @xmldom/xmldom 97.9 kB + localforage 65.2 kB + marks-pane/path-webpack/lodash 等;* 宿主机 gzip 交叉核对为 102.7 kB
TextReader-CmvYmFNB.js lazy()(txt 与 md 共用) 79.3 kB 27.0 kB 30.1 kB marked 53.7 kB + dompurify 59.7 kB(gzip 14.2/14.5 kB)
CbzReader-BEFlX6jt.js lazy() 12.6 kB 5.1 kB 6.2 kB 仅 (src) 21.1 kB 里的渲染逻辑,jszip 共享 chunk 已含
useProgress-BHPrULZV.js lib/useProgress(多 reader 共享) 7.0 kB 2.8 kB 4.7 kB
EpubReader-BIpid_pd.js lazy() 3.1 kB 1.5 kB 1.5 kB 仅薄壳;epubjs 全链在 src-BNcXNGhn.js
workbox-window.prod.es5-Bd17z0YL.js vite-plugin-pwa(registerSW) 5.7 kB 2.2 kB 2.3 kB
rolldown-runtime-Dd_uD5pT.js 运行时 1.1 kB ~0.9 kB 0.8 kB index.html 里 <link rel="modulepreload">
index-C4Rgqjhl.css Tailwind v4 产物 69.4 kB 12.1 kB ~
  • 五个阅读器(cbz/txt/md/pdf/epub)均已通过 pages/Reader.tsx 的 lazy() 按需加载:是(实测确认——dist/assets 有 Cbz/Pdf/Text/Epub 四个独立 reader chunk;txt/md 共用 TextReader 一个 chunk;pdf 另有独立 pdf.worker 文件)。 Yes (verified): dist/assets contains separate chunks for Cbz/Pdf/Text/Epub readers; txt and md share the TextReader chunk; pdf additionally has a separate pdf.worker file.
  • PWA precache(workbox.globPatterns)当前包含哪些大文件:generateSW 报告 "precache 15 entries (2549.92 KiB)",实测把全部 11 个 JS/CSS 都打进 precache,其中最大的五个:pdf.worker 1,189.3 kB、index 主包 462.6 kB、PdfReader 434.4 kB、src-BNcXNGhn.js(epubjs 链)345.4 kB、TextReader 79.3 kB;sw.js 未设 maximumFileSizeToCacheInBytes(默认 2 MiB,未触发排除)。 The precache (reported as "15 entries, 2549.92 KiB") includes all 11 JS/CSS files; the five largest are pdf.worker 1,189.3 kB, index 462.6 kB, PdfReader 434.4 kB, src-BNcXNGhn.js (epubjs chain) 345.4 kB, TextReader 79.3 kB. sw.js does not set maximumFileSizeToCacheInBytes (default 2 MiB, nothing excluded).

可优化点 / Optimization backlog

# 问题 证据(chunk/体积) 建议动作 归属
1 PWA precache 把全站 JS 全量缓存:pdf.worker(1.19 MB)+PdfReader(434 kB)+epubjs 链(345 kB)+TextReader(79 kB) 均在首装/首启 precache 清单,未打开对应阅读器的用户也要下载这 ~2.05 MB sw.js precache 15 entries (2,549.92 KiB);globPatterns **/*.{js,css,html,svg,woff2} 未排除 首装 precache 仅保留 index/css/主包,reader chunk 改 runtime caching 或自定义 precache 过滤 后续 spec
2 EpubReader 的 epubjs 依赖链以共享 chunk src-BNcXNGhn.js(345.4 kB) 形式被拉进首屏 module 依赖(EpubReader 是 lazy 入口,但 rolldown 把它 import 的整条链合并进一个共享 chunk,与 CbzReader 路径耦合) src-BNcXNGhn.js 345.4 kB:epubjs 229.0 kB + jszip 205.6 kB + @xmldom/xmldom 97.9 kB + localforage 65.2 kB 观察 CbzReader 是否真正共享 jszip;若不共享,用 manualChunks/advancedChunks 把 epubjs 链单独成 chunk,只在 epub 入口加载 后续 spec
3 react-dom 459.8 kB(gzip 87.1 kB)占 index 主包 462.6 kB 的 99.4%,无 vendor 拆分;brotli 后 71.4 kB 仍是首屏最大单块 index chunk rendered 明细:react-dom 459.8 kB / react-router 94.9 kB / query-core 64.2 kB 评估 build.rollupOptions.output.manualChunks(或 vite advancedChunks)拆 vendor,提升缓存命中(业务代码改动不会使 react-dom 失效) 后续 spec
4 lucide-react 34 个按需 import 的图标最终以完整组件体形式进 index(17.3 kB stat / gzip 10.1 kB——gzip 压缩率仅 42%,说明包内未 treeshake 干净);且每个 reader chunk 各自带少量 lucide 模块(CbzReader 1.0 kB、PdfReader 0.7 kB、useProgress 2.8 kB)导致重复 index 内 lucide-react rendered=17.3 kB(gzip 10.1 kB) mods=34;各 reader chunk 再零散重复 核查 treeshake 行为(rolldown 下 lucide-react named import 是否保留死代码);必要时统一走本仓库 components/icons.tsx 自绘 SVG 后续 spec
5 radix-ui 单包多模块:radix-ui 包内 Select/Menu/Dialog/DropdownMenu/AlertDialog 等 ~29 个子模块共 ~131 kB(其中 react-select 43.0 kB、react-menu 22.1 kB、floating-ui 46.5 kB)进 index 主包 index chunk 内 @radix-ui/* 合计 rendered ≈ 131 kB(gzip ≈ 30 kB) 非首屏必需的 Select/DropdownMenu 所在页面(如 Shelf 筛选、admin 页)可评估下沉到对应 route chunk;仅在使用处再 import 后续 spec
6 marked(53.7 kB)+dompurify(59.7 kB) 只被 TextReader(md 格式) 用,却进了 txt 也复用的 TextReader chunk——纯 txt 用户也下载 113 kB TextReader-CmvYmFNB.js 79.3 kB 内 marked 53.7 kB + dompurify 59.7 kB md 渲染再拆一层:TextReader 内对 md 分支用 await import("marked")/await import("dompurify"),txt 分支零依赖 后续 spec

结论 / Verdict

当前总体体积(首屏 gzip 约 145 kB + CSS 12 kB)对个人书库场景可以接受,五阅读器 lazy 拆分也确实生效,pdf.worker 已是独立文件不进主包。最优先的两项是 #1(precache 全量缓存 2.05 MB reader 代码,与 spec 的离线策略直接冲突)和 #2(epubjs 链 345 kB 共享 chunk 的归属需要用数据确认),建议在下一个 spec 批次(S5 之后的优化轮)落地;#3–#6 属于收尾打磨,可与 #1 同期或更晚处理。

Total size is acceptable for a personal library (first-load gzip ≈145 kB + 12 kB CSS), and all five readers are properly split via lazy(), with pdf.worker already a standalone file. The two top priorities are #1 (precache ships ~2.05 MB of reader code users may never open — conflicts with the offline spec) and #2 (confirm via data which reader actually shares the 345 kB epubjs-chain chunk); both belong to the next optimization round after S5. Items #3–#6 are polish that can land together with #1 or later.


实测数据来源 / Measurement provenance

  • npm run analyze(容器内 vite build + rollup-plugin-visualizer 7.1.1,gzip/brotli 选项开启)→ frontend/dist-stats/stats.html;chunk/依赖级 stat、gzip、brotli 数值用 node 脚本从 stats.html 内嵌 nodeParts/nodeMetas JSON 提取(nodeParts[uid] = {renderedLength, gzipLength, brotliLength},nodeMetas[uid].moduleParts 给出模块归属 chunk)。
  • 产物文件级数值:ls -la frontend/dist/assets/(与 stats.html renderedLength 一致)。
  • gzip 交叉核对:宿主机 gzip -c <file> | wc -c(index 143,763 / PdfReader 128,873 / src-BNcXNGhn 102,696 / TextReader 26,677 / pdf.worker 366,988 / CbzReader 5,061 / EpubReader 1,500 / useProgress 2,778 / index.css 12,113 字节)。
  • precache:grep url:"..." frontend/dist/sw.js + 容器内 build 输出 "precache 15 entries (2549.92 KiB)"。
  • dist 总计:du -sh frontend/dist/ = 2.6M;assets 目录 du -b 求和 2,609,869 字节。
  • Reader.tsx lazy() 现状与 dist/assets 中四 reader chunk + pdf.worker 独立文件直接核对。

Provenance: values come from (a) the JSON embedded in dist-stats/stats.html produced by npm run analyze (rollup-plugin-visualizer 7.1.1 with gzip+brotli), extracted with a node script over nodeParts/nodeMetas; (b) ls -la frontend/dist/assets/; (c) host-side gzip -c | wc -c cross-checks; (d) sw.js precache URL list and the build log line "precache 15 entries (2549.92 KiB)"; (e) du -sh frontend/dist (2.6M) and per-file du -b sum (2,609,869 B); (f) direct comparison of Reader.tsx lazy() sources with the four reader chunks and the standalone pdf.worker file in dist/assets.