第一章:单文件时代
什么是单文件应用
说白了,单文件应用就是「一个 HTML 文件搞定一切」。
我刚入行那会儿,前端项目就是这么干的。一个 index.html,里面塞着 CSS 样式、JavaScript 逻辑,再加上 HTML 结构。所有代码都在一个文件里,打开就能跑,简单粗暴。
你想想看,那时候没有 Node.js,没有 Webpack,甚至连 jQuery 都还没普及。写个页面,就是新建一个 .html 文件,然后往里写东西。浏览器直接打开,效果立竿见影。
这种模式,我称之为「单文件时代」。它有几个明显特征:
- 一个文件 = 一个页面:所有资源都在一个 HTML 里
- 零构建:不需要打包、编译,保存即刷新
- 直接运行:双击文件或用浏览器打开即可
- 适合小项目:几十行代码的页面,用这种方式最省事
小提示:现在很多原型设计、快速演示、在线工具,依然在用单文件的方式。比如 CodePen、JSFiddle 上的 demo,本质上就是单文件应用。
HTML/CSS/JS 混写的痛点
单文件应用虽然简单,但一旦项目变大,问题就来了。
我记得有一次,我接手一个同事留下的单文件项目。那个 HTML 文件有 2000 多行,CSS 和 JS 混在一起,找一段逻辑要翻半天。我当时就想:这玩意儿怎么维护?
具体来说,混写模式有这几个硬伤:
- 可读性差:HTML 结构、样式定义、业务逻辑全搅在一起。你想改个按钮颜色,得先找到对应的 CSS 在哪;想修个点击事件,又得在 JS 堆里翻。
- 复用困难:同样的导航栏、同样的弹窗组件,如果多个页面都需要,只能复制粘贴。改一处,所有地方都得跟着改。
- 协作冲突:两个人同时改一个文件,Git 合并时全是冲突。我经历过一次,两个人改了同一个 HTML 的不同部分,结果合并后页面直接崩了。
- 加载性能差:所有代码一次性加载,哪怕用户只看了首页,也得把整个页面的 JS 和 CSS 都下载下来。
- 调试体验糟糕:浏览器开发者工具里,所有代码都在一个文件里,断点调试时找函数位置都费劲。
避坑指南:我曾经在一个小项目中坚持用单文件模式,结果项目从 3 个页面膨胀到 20 多个页面。最后不得不花两天时间重构,把所有代码拆成独立文件。那两天我改代码改到凌晨两点,教训深刻。
一个简单的单页面计算器案例
光说不练假把式。咱们写一个单文件计算器,感受一下这种模式。
这个计算器很简单:两个输入框,一个下拉选择运算符,一个按钮,一个结果显示区域。所有代码都在一个 HTML 文件里。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>单文件计算器</title>
<style>
/* 所有样式写在这里 */
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
padding: 20px;
background: #f0f4ff;
}
.calculator {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
h2 {
text-align: center;
color: #2c3e50;
margin-top: 0;
}
input, select, button {
width: 100%;
padding: 10px;
margin: 8px 0;
border: 1px solid #d0d7de;
border-radius: 6px;
font-size: 16px;
box-sizing: border-box;
}
button {
background: #4a90d9;
color: white;
border: none;
cursor: pointer;
font-weight: bold;
}
button:hover {
background: #357abd;
}
.result {
margin-top: 16px;
padding: 12px;
background: #e8f4fd;
border-radius: 6px;
text-align: center;
font-size: 20px;
color: #2c3e50;
}
</style>
</head>
<body>
<div class="calculator">
<h2>简易计算器</h2>
<input type="number" id="num1" placeholder="输入第一个数字">
<input type="number" id="num2" placeholder="输入第二个数字">
<select id="operator">
<option value="+">加 (+)</option>
<option value="-">减 (-)</option>
<option value="*">乘 (*)</option>
<option value="/">除 (/)</option>
</select>
<button onclick="calculate()">计算结果</button>
<div class="result" id="result">等待计算...</div>
</div>
<script>
// 所有 JavaScript 逻辑写在这里
function calculate() {
const num1 = parseFloat(document.getElementById('num1').value);
const num2 = parseFloat(document.getElementById('num2').value);
const operator = document.getElementById('operator').value;
let result;
// 检查输入是否合法
if (isNaN(num1) || isNaN(num2)) {
document.getElementById('result').textContent = '请输入有效数字';
return;
}
// 执行运算
switch (operator) {
case '+': result = num1 + num2; break;
case '-': result = num1 - num2; break;
case '*': result = num1 * num2; break;
case '/':
if (num2 === 0) {
document.getElementById('result').textContent = '除数不能为0';
return;
}
result = num1 / num2;
break;
default: result = '未知运算符';
}
// 显示结果
document.getElementById('result').textContent = `结果:${num1} ${operator} ${num2} = ${result}`;
}
</script>
</body>
</html>
你看,这个文件里 HTML、CSS、JS 全都有。保存成 calculator.html,双击就能用。
但问题也很明显:
- 如果我想给这个计算器加个「历史记录」功能,代码会越来越长
- 如果我想在另一个页面也用这个计算器,只能复制整个代码块
- 如果团队里两个人同时改这个文件,合并时大概率会冲突
核心认知:单文件模式适合「一次性使用」或「快速原型」。但一旦项目需要维护、扩展、多人协作,就必须走向组件化和工程化。
从单文件到工程化的思考
写这个计算器的时候,我就在想:如果能把 HTML 结构、CSS 样式、JS 逻辑拆成三个独立文件,是不是更好维护?
如果能把计算器封装成一个「组件」,在其他页面直接引用,是不是更省事?
如果能把代码压缩、合并、按需加载,是不是性能更好?
嗯,这些想法,就是前端工程化的起点。后面的章节,我们会一步步把这些想法变成现实。
下一章,咱们聊聊「如何把单文件拆成多文件,以及这中间会遇到哪些坑」。
个人建议:初学者可以先从单文件入手,感受一下「原始」的开发方式。等你真正体会到痛了,再学工程化工具,理解会更深刻。我当年就是这么过来的。