3000行全塞一个 index.html?四刀瘦身手术,不装 Vite 也能模块化

给"单文件胖前端"做瘦身手术 ------ 从零到一讲清前端模块化

故事背景:你写了一个全栈项目,前端一开始全塞在一个 index.html 里,跑起来了。现在想整理一下,又不想引入一堆新工具。怎么干?


一、问题:为什么会"胖"?正常吗?

正常。

很多项目都这么走:MVP → 先跑起来 → 再重构。MVP 阶段,所有东西塞一个文件,能快速验证想法,这叫「快速原型」,完全合理。问题出在「跑通之后不收拾」。

我们来看看一个"胖单文件"的典型结构:

xml 复制代码
index.html (~3000 行)
  ├── <style> (~1000 行)     → 样式
  ├── <body> 模板(~1500 行) → 页面结构 + Vue 指令
  └── <script>(~500 行)    → 业务逻辑 + 函数

就像你收拾书包------刚放学回来说先放下书包,然后就去玩了;周末得花十分钟把课本、零食、水杯分开,把脏衣服掏出来。项目跑通了,不代表就不用整理了。

二、重构第一刀:把 CSS 从模板里抽出来

操作:

xml 复制代码
- index.html:   删除整个 <style> 块
+ static/css/main.css:  把样式粘进去
+ index.html:   加一行 <link rel="stylesheet" href="/static/css/main.css">

为什么这么做?------关注点分离

「关注点分离」是软件工程最最基础的原则:

  • CSS 管"长什么样"------颜色、字体、间距
  • HTML 管"结构是什么"------有几个按钮、几个输入框
  • JS 管"点了做什么"------发请求、改状态

如果你把这三类东西揉在一起,改样式得去几千行里翻 JS,改逻辑得在 CSS 块旁边找,就像你把课本和脏衣服放一块,找起来能不累吗?

把 CSS 抽出来,一个文件只干一件事 ------这就是最基础的可维护性:每个东西有固定位置,找东西不用翻遍整个屋子


三、重构第二刀:给 HTTP 请求做个"总管"

这一步是给 AI 应用做地基,也是本次重构最重要的一步------因为 AI 应用的接口天然比普通后端更不稳定。

问题:为什么要"总管"?

原来你每个地方发请求,都要写一遍:

javascript 复制代码
async function xxx() {
  const resp = await fetch(url, ...)
  if (!resp.ok) {
    // 解析错误信息... 各种 if-else
    throw new Error(...)
  }
  return resp.json()
}

有 10 个页面,你就得写 10 遍。要是想改"错误怎么解析",就得改 10 个地方。而且:

  • AI 大模型调用容易超时
  • 网络抖一下请求就失败了,得重试
  • 多个请求同时飞,Loading 状态怎么统一?

这些问题每一个页面各写一遍,代码重复量爆炸,改起来累死人。

解法:做一个统一请求层,所有请求都走这里

新建 static/js/api.js,把「发请求、错了怎么办、超时、重试、Loading」这些事全部收在一处:

javascript 复制代码
// api.js 里只干这一件事,但干得很彻底
export async function api(url, opts = {}, cfg = {}) {
  // 1. 错误规范化:后端不管返回什么乱格式,都统一变成一句人话
  // 2. 全局 Loading 计数:多个请求并发,Loading 显示/隐藏不闪烁
  // 3. 超时控制:超时自动掐断,不卡死页面
  // 4. 幂等重试:网络抖动 5xx,自动退避重试几次
}

收益:

  • 一次做好,处处复用 ------以后所有页面都 import { api } from '/static/js/api.js',直接用就行,不用再写一遍错误处理。
  • AI 应用刚需------大模型输出不稳定、超时是家常便饭,统一重试/超时/错误处理就是你的第一道防线。
  • 可测试------请求层独立出来,以后 mock 数据测试不用碰页面逻辑。

四、重构第三刀:搭原生模块化骨架,不引入构建工具

抽完 CSS 和请求层,现在把"纯数据"和"纯工具"也抽出来------用浏览器原生支持的 ES 模块零工具就能拆文件

为什么不引入 Vite/Webpack?------这是主动选择,要讲清楚原因

❓ 现在行业不都是 Vite + React 或者Vue 吗?为什么不用?

✅ 因为我的后端是 FastAPI,它本来就是"直接把 HTML 发给浏览器"。一旦引入 Vite,我就得加一套「开发构建 + 打包产物部署」流程------从"一键 run 就能跑"变成"先开发构建,再部署产物",部署成本直接翻倍。

取舍要主动说出来,不能闷头堆代码

  • 我的项目是个人全栈 MVP,目标是"快速迭代、低成本运行"------不需要工程化的"复杂度升级"。
  • 浏览器早就原生支持 <script type="module"> + import/export了------不用任何工具,就能拆成多个文件,为什么非要引入一堆新依赖?
  • 用造火箭的标准去造自行车当然很好,何不食肉糜?

最终骨架(无构建):

bash 复制代码
static/
  css/main.css        # 样式(关注点分离)
  js/
    api.js            # HTTP 请求总管(AI 应用地基)
    constants.js      # 纯数据:比如选项数组、配置、枚举(不依赖状态)
    utils.js          # 纯函数:工具方法(输入→输出,不依赖页面状态)
templates/index.html  # 只剩模板 + 组件状态逻辑 + 引入口

五、重构第四刀:什么该抽?什么不该抽?------区分纯函数和有状态函数

抽完小的零碎,你会遇到一个问题:一堆函数摆在面前,是不是全抽出去?别瞎抽,先分类:

🔹 第一类:纯函数 / 纯数据 ------ 放心抽出去

纯函数 就像家里的万年历 ------你放客厅、放书房、放门口,它都一样能用:给它一个输入,就给你一个输出,不依赖任何别的东西,也不改变外面的世界。

举例子:

函数/数据 为什么是纯的? 抽去哪里
formatDate('2026-08-13')"2026/08/13" 只跟输入日期有关,跟页面状态没关系 utils.js
highlightWords(sentence, words) → 带高亮的 HTML 只跟输入的句子和单词有关 utils.js
TTS_MODELS = [{name, label, group}, ...] 写死的配置,不会变 constants.js

✅ 结论:万年历放公共抽屉,哪都能用,抽出去!

🔹 第二类:有状态函数 ------ 别瞎抽,留在原地

有状态函数 就像你书桌上的台灯------它得插你桌上的插座才能亮,你不能把它硬搬到客厅去用。它要读页面上的响应式状态,抽出去就得把状态当参数一层层传进来。

举例子:

javascript 复制代码
// 这个函数要读 `imageModels.value`(这是页面上的响应式状态,后端加载后才有的)
function getImageModelNote(model) {
  const m = imageModels.value.find(...)
  return ...
}

要是硬抽去 utils.js,调用的时候就得这么写:

scss 复制代码
// 调用者每次都得把 imageModels 当参数传进来,啰嗦死了!这就是"传参地狱"
getImageModelNote(imageModels.value, model)

什么是「传参地狱」? 就是为了满足"抽出去"这个形式,硬生生把一堆参数从调用者传到被调用者,每调用一次就得背一遍参数,又容易漏,又啰嗦。为了模块化而模块化,反而更难维护了

✅ 结论:只抽纯的,不抽依赖状态的,既练了手,又不制造新麻烦。


六、重构完成:我们得到了什么?

原先 现在
一个文件 3600 行 分成 5 个文件,最大的 index.html 瘦身约 1200 行
样式、结构、逻辑揉在一起 每个文件只干一类事,改样式不用翻 JS
到处是重复的错误处理代码 统一处理,改一次全项目生效
想加新功能不知道在哪加 新增功能有固定位置放

关键的工程感悟(给即将实习的你)

  1. 先跑通,再重构 ------ MVP 先堆出来能跑,再整理,别一开始就追求"完美架构"。
  2. 取舍要透明 ------ 选"简单还是规范",选"不用工具还是用工具",自己心里要有数,并且要能说出来为什么。闷头堆代码才是最糟的
  3. 抽纯不抽杂 ------ 先把纯函数/纯数据抽出去,有状态的留在原地,避免"传参地狱"。边界感是工程能力的核心。
  4. AI 应用要先做请求层 ------ LLM 输出不可信、接口不稳定,统一把错误/超时/重试做了,这就是地基。

七、上线体检:重构完还能用吗?

重构完必须实测:

  • ✅ 控制台零 JS 报错
  • ✅ 所有页面功能正常(列表加载、日期显示、下拉选项、高亮都正常)
  • ✅ 所有新模块 HTTP 200 正常加载
  • ✅ 不改变原有交互,用户完全感知不到重构------这叫「不改变行为的重构」,是正确姿势。

重构完成。胖文件变成清晰骨架,功能一点没少,以后改起来快多了。

相关推荐
用户6919026813392 小时前
用浏览器 WebGPU跑DPSK大模型(1) - 模型的下载和前端下载进度的显示
javascript·react.js·架构
xiaoshuai10242 小时前
编译管不着的跨层 bug:用 4 道脚本闸守住 API/SQL/权限/迁移
架构
Dr.kangder3 小时前
嵌入式面试总结(八)——大小端
嵌入式硬件·面试·职场和发展·架构·嵌入式
李白客3 小时前
分布式集群与数据库产业:从单机到集群的架构跃迁与市场重构
数据库·分布式·架构
画中有画4 小时前
Kappa 架构在大数据实时处理系统中的应用
大数据·架构
cfm_29145 小时前
了解Sentinel
分布式·架构·sentinel
雨白5 小时前
Android AOP 切面编程实战:优雅地处理全局断网拦截
android·架构
阿标在干嘛6 小时前
从数据碎片到决策引擎:政策快报背后的政策大数据架构逻辑
大数据·架构
Sylvia33.6 小时前
篮球数据API的技术架构与工程实践:基于火星数据WebSocket实时推送体系
java·python·websocket·网络协议·架构