把整个前端工程折叠成一个 HTML 文件
做交互式演示或原型时,我通常面临一个尴尬的处境:想展示的东西稍微复杂一点——有状态管理、有组件树、需要 shadcn/ui 的 Dialog 或 DataTable——但又不想为它单独起一个 repo、跑一个 dev server、给对方发一个链接,然后解释「需要先 npm install」。
一个自包含的 HTML 文件是这个问题最直接的答案。所有依赖内联进去,双击打开,没有任何运行时依赖。问题是:用 React + TypeScript + Tailwind 写的东西,怎么变成这样一个文件?
构建链路的折叠
常规做法是手写内联 JSX,用 Babel standalone 在浏览器里实时转译,Tailwind 用 CDN。这条路走起来很快,但一旦需要 shadcn/ui 组件(它们依赖 Radix UI,几十个包),或者你想用 TypeScript 的类型保护,就会碰壁——CDN 版 Babel 处理不了模块解析,import 语句会直接报错。
更稳的思路是反过来:先用完整的工具链开发,再把产物压缩成单文件。
具体流程分三步:
- 初始化:脚本一键创建配置好的 Vite + React + TypeScript 工程,预装 40+ shadcn/ui 组件和所有 Radix UI 依赖,路径别名(
@/)开箱可用。 - 开发:正常写代码,本地
vite dev热更新,和任何 React 项目没有区别。 - 打包:换用 Parcel 做 bundle,再用
html-inline把所有 JS/CSS 内联进index.html,输出一个bundle.html。
Parcel 的选择不是随意的——Vite 的产物天然是多文件(code splitting + asset hash),不适合内联场景;Parcel 默认产出单 bundle,和「最终要内联」的目标对齐得更好。
开发阶段用 Vite(HMR 快、DX 好),打包阶段换 Parcel(单文件输出、原生支持 TS 路径别名)。同一份源码,两个阶段各取所长,互不干扰。
「AI slop」是一个值得认真对待的警示
这套工具有一条设计备注,单独拿出来说:避免过度居中布局、紫色渐变、统一圆角、Inter 字体。
这不是美学偏好,是一个关于 AI 生成 UI 的结构性问题。当 LLM 被要求「生成一个演示页面」时,它有极强的倾向收敛到某种「安全的视觉模板」——居中卡片、紫蓝渐变 Hero、大圆角 Button、font-sans 统配。这个模板在技术上无懈可击,但千人一面。
把这条警示写进工具配置,是在用约束对抗惰性——提醒自己(和 AI):演示的目标是传达具体信息,不是展示「我会用 Tailwind」。
这种交付形态适合什么场景
单文件 HTML 有明确的边界:
适合:给非技术受众展示交互原型、在对话中嵌入可运行的 UI demo、快速验证某个组件组合是否可行、作为设计稿的可交互版本分发。
不适合:需要后端 API、需要持久化状态、需要 SEO、需要多人协作开发的正式产品。把它当正式产品的起点是个常见误区——它是演示载体,不是工程基础。
文件体积也是一个现实约束。一个带 shadcn/ui 的 React 应用内联后通常在 500KB 到 2MB 之间,对于需要快速传递的单文件来说可以接受,但不要期望它像 CDN 分发的生产应用一样轻量。
一句话心法
好的工具不只是「能用」,还要让使用者在正确的场景用它——把适用边界写进文档,和把核心功能写进代码同等重要。