9.6 KiB
前端 bundle 审视(2026-09) / Frontend bundle review
由
cd frontend && npm run analyze生成dist-stats/stats.html后人工审视得出;本期只记录结论,不实施优化(spec ④ S4)。 Generated fromdist-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/assetscontains 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.worker1,189.3 kB、index主包 462.6 kB、PdfReader434.4 kB、src-BNcXNGhn.js(epubjs 链)345.4 kB、TextReader79.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.jsdoes not setmaximumFileSizeToCacheInBytes(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/nodeMetasJSON 提取(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.tsxlazy() 现状与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.