前言
最近一直忙于装修和开发新产品,文章都没时间更新,快速迭代的后果就是架构没有跟上功能增长的步伐,现在隐隐有脱离掌控的感觉,我这几天也把进度放慢下来,思考一下整体的规划。
也顺便整理一下笔记,没有输出心里很不踏实🤣
好了,说回正题,为什么标题叫“前端邪修”呢?在前端高度工程化的今天,各种工具层出不穷,大家都在说别更新了,学不动了,我直接反其道而行:
- 不用 Vite
- 不用 Webpack
- 不跑 dev server
- 不搞 HMR
- 不维护 node_modules
直接在 HTML 文件里引入 React 写界面!
这听起来很像 2025 年还在用 Dreamweaver 写网页哈哈哈🤣
本文记录一次可以称之为「前端邪修」的反工程化开发实践。
第一阶段 Runtime JSX
一开始,我用的是最原始、也最“离经叛道”的 React 用法。
<script src="react.development.js"></script>
<script src="react-dom.development.js"></script>
<script src="babel.min.js"></script>
<script type="text/babel">
function App() {
return <h1>Hello World</h1>;
}
</script>
特点很明显:
- 不需要 Node
- 不需要 npm / pnpm
- 打开浏览器就能写 React
- JSX 由
babel-standalone在浏览器里实时编译
你别说,其实这种方法写起来还挺爽的(逃
- 心智负担极低
- 没有工具链焦虑
- HTML 即入口,所见即所得
但缺点也很明显:
- 各种依赖体积巨大,动辄几MB
- JSX runtime 编译极慢
- 页面一复杂,加载时间肉眼可见地上升
我这个简单的app,本地加载都要半分钟的时间🤣
其实这种用法,是 React 官方早期 Demo + 教学级用法,后来被官方明确标注为:Not recommended for production
我之前也写过一篇文章介绍:在HTML中引入React和JSX
实践也证明了,这种方式只能当本地demo测试一下,根本不能作为production发布。
第二阶段
gulp + babel,我只要 JSX 编译
我选择了一个在今天看来非常“复古”的工具:gulp。
这个工具虽然简单,但非常好用,我一直非常喜欢这个工具,可以在我的很多项目里看到 gulp 的身影:
- AspNetCore开发笔记:使用NPM和gulp管理前端静态文件
- Django项目引入NPM和gulp管理前端资源
- 返璞归真!使用 Alpine.js 开发交互式 web 应用,抛弃 node_modules 和 webpack 吧!
用法是这样的:
把 jsx 组件按照顺序,显示添加到待处理的列表里
const jsxComponents = [
"Alert.jsx",
"Toast.jsx",
"Login.jsx",
"Register.jsx",
"ChatView.jsx",
"DetailView.jsx",
"App.jsx"
];
我放弃了自动依赖分析,这在现代前端里几乎是“原罪”。😂
但在一个边界清晰、规模可控的项目里,它反而是最可控的方式。
gulp 在这里做了简单的几个事情:
- JSX → JS
- 多文件 → 一个 bundle
- 压缩
gulp.task("concat:jsx", () => {
return gulp.src(jsxComponents, { base: "." })
.pipe(babel({
presets: ["@babel/preset-env", "@babel/preset-react"]
}))
.pipe(concat("dist/js/app.bundle.js"))
.pipe(terser()) // Use terser for minification
.pipe(gulp.dest(paths.root));
});
没有 loader,没有 plugin 地狱,没有配置互相打架。
最终产物只有一个:dist/js/app.bundle.js
最终形态
构建完成后,整个应用的入口是一个极其朴素的 HTML 文件。
<script src="lib/react/react.production.min.js"></script>
<script src="lib/react-dom/react-dom.production.min.js"></script>
<script src="lib/axios/axios.min.js"></script>
<script src="dist/js/app.bundle.js"></script>
几个我非常喜欢的点:
所有依赖都是显式的
React 不需要 JSX 才能运行
浏览器缓存是我自己控制的
<script> (function () { const scripts = [ "dist/js/app.bundle.js", ]; const v = new Date().getTime(); scripts.forEach(src => { document.write(`<script src="${src}?v=${v}"><\/script>`); }); })(); </script>
为什么?
这么做的起因其实很简单,我的后端需要一个简单的交互页面,而我既不想创建一个新的 Next.js/Vite 前端项目来做,也不想用后端模板渲染+HTMX/Alpine.js 这种方案。
前者的问题是成本:
为了一个功能边界非常清晰的页面,引入一整套前端工程体系,意味着要额外维护一份项目结构、一套构建配置、以及一整条工具链生命周期。这种成本,对这个需求来说是过度的。
后者的问题则在表达能力:
后端模板渲染配合 HTMX 或 Alpine.js 确实轻量,但当页面开始出现较复杂的状态流转、组件复用和逻辑组合时,我很快就会开始“手动模拟一个组件系统”。与其在模板语法和指令里绕来绕去,不如直接使用一个我已经非常熟悉、心智模型也足够稳定的组件库(React生态)。
于是问题就变成了:
我能不能只使用 React 的“表达能力”,而不引入它背后的整套工程化体系?
这正是这套“前端邪修”方案的出发点。
价值
这套“前端邪修”方案解决了什么?
用一句话总结:如何在不引入额外工程复杂度的前提下,获得一个足够舒适的交互层。
具体来说,这个方案做到了几件事:
- 不需要单独维护一个前端项目
- 不需要 dev server、HMR 和复杂的构建配置
- 不需要为一个小页面承担整套前端工程的长期成本
- 但依然可以:
- 使用组件化思维
- 管理清晰的状态和交互逻辑
- 写出结构清楚、可维护的 UI 代码
这不是在追求最轻量,而是在追求:复杂度与需求规模之间的匹配。
当需求足够简单时,工程化本身就应该是可以被拆解、被克制、甚至被拒绝的。
取舍
👍 我得到的
- 极低的心智负担
- 极稳定的构建过程
- 无运行时构建依赖
- 非常适合:
- Admin 系统
- AI 控制台
- 内部工具
- WebView / 嵌入页面
❌ 我主动放弃的
- HMR
- 自动 code splitting
- TypeScript 全链路
- 生态插件红利
小结
我做的事情其实很简单:把复杂度一层一层拆掉,直到只剩下业务真正需要的那一层。
我不推荐所有人这样做,但我会继续做一些反工程化的尝试,在AI时代反其道而行之,还挺有意思的😄
程序设计实验室
微信公众号