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

60 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端 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`.