60 lines
9.6 KiB
Markdown
60 lines
9.6 KiB
Markdown
# 前端 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`.
|