@lhx-kit/vite-plugin 实现详解本章面向想看懂源码的维护者,解释插件每个钩子在做什么、为什么这么做。 阅读对象:
packages/vite-plugin/src/plugin.ts(约 1200 行)
用户侧只写这一行:
但内部 lhxKit() 返回 一个插件数组:
plugins: [[a, b]] 是合法的——用户写法不变,内部可以按责任分层。
config(userConfig, env) — 配置翻译时机:Vite 启动第一步,UserConfig 还没和默认值合并。
职责:
Vite MPA 模式要求 input 指向真实存在的 HTML 文件(不能是虚拟模块)。
我们在 .lhx-kit/pages/<name>.html 写一份带 CDN loader 注释块 + 页面 entry <script> 的 HTML,让 Vite 照常处理。
resolveId(id) / load(id) — 虚拟模块只做一件事:支持 @lhx-kit/virtual:config 虚拟模块。
用户代码可以:
好处:在客户端运行时拿到编译时快照的 project.config.ts。不需要运行时 jiti 重新加载 TS 配置。
transform(code, id) — CDN import 改写时机:Vite pre-bundle 完成后,Rollup 正式 bundle 前。
绝大多数模块不 import 任何 CDN external。code.includes(name) 是 C 实现的快速子串检查,把 per-file transform 调用量砍到 5% 以下。
rewriteCdnImports — 4 种改写改写规则详见 🌐 CDN 外挂 §3。
曾经有 if (id.includes('node_modules')) return null; 跳过。结果 react-router-dom bundle 打进来后里面的 import {createContext} from 'react' 没有被改写,浏览器报:
原因:Rollup 的 external 只从用户模块图里剥离,third-party 包经过打包后的产物仍保留 import 语句。必须让 transform 也进入 node_modules。
修复:去掉 node_modules 跳过,靠 early exit 控制性能。
generateBundle(options, bundle) — chunk 分配 + HTML 重写时机:所有 chunk 已生成,写盘前最后一步。
五步工作:
lhxCompress 副插件shouldCompress 规则:
.js / .css / .html / .svg / .json)compress: false 关闭ContextFAMILY_GROUPS经验数据:这四组里的包几乎总是"全家桶一起用",分开打只增 HTTP 请求数。
其他包(antd / echarts / zod / lodash / dayjs ...)彼此独立,分开打缓存粒度更好。
resolveLocalVendor — 找 UMD 本地兜底| 场景 | 问题 |
|---|---|
| 某些包把 UMD 放在非标准位置 | dist/umd/production.min.js |
某些包的 package.json#exports 封闭子路径 |
require.resolve('preact/hooks/dist/hooks.umd.js') 直接抛错 |
| 旧版包结构不规范 | 完全没有命名规律 |
resolveLocalVendor 这层"启发式"把 20+ 主流包的 UMD 都能自动找到。
enforce: 'pre'aliasArray 已经生效
:::输出:
:::info 测试覆盖 roadmap 当前覆盖率约 40%,主要缺失:
generateBundle 的 chunk ownership BFSresolveLocalVendor 的文件系统兜底compress 在大文件下的正确性补全计划在 Migration Guide。
generateBundle 改造与事故复盘