从零开始构建一个现代化前端项目
从一次项目重构说起:我是如何一步步搭起现代前端项目的骨架的?

去年我加入一个老项目组,接手的是一个运行了五年多的前端项目。说实话,刚打开代码目录那一刻,我就有点懵了——满屏的 jQuery、散落各处的脚本引入、毫无模块化的样式文件,还有那些被复制粘贴过 N 遍的工具函数……这不是项目,这是一座随时会塌的积木塔。
团队里的人对这个项目早已是又爱又恨。业务需求一直在变,但代码却越来越难维护。最要命的是每次上线都像走钢丝,改个小功能可能引发好几个连锁问题。更别说什么构建优化、自动化测试这种“高级货”,压根不在我们考虑范围内。
于是我和团队决定:重新来过,用现代化的方式,搭建一个新的前端架构。虽然不能一步到位推翻重写,但我们希望以最小成本,把项目逐步迁移到新架构中。下面我想跟大家分享一下这段经历中的思考、踩过的坑和最后沉淀下来的技术方案。
为什么需要重新开始
我们的原始项目结构非常典型:
- index.html
- js/
- common.js
- util.js
- page1.js
- page2.js
- css/
- main.css
- reset.css
- images/
每个页面单独引入几个 JS 文件,CSS 直接在 HTML 中通过 <link> 加载。开发流程完全靠人肉检查、手动打包资源,部署则是直接上传所有文件到服务器某个目录。
在这种结构下,遇到的问题包括但不限于:
- 修改一个工具函数会影响多个页面
- 页面加载慢,JS 和 CSS 没有合理拆分
- 代码无法复用,重复代码满天飞
- 没有任何本地开发环境,全靠浏览器刷新调试
- 新人上手门槛极高
这些问题就像一个个慢性病,在不影响系统运转的情况下持续消耗团队士气。最终推动我们动手的原因,是产品提了一个新的交互模块——它需要使用一些现代框架的能力(比如 React),而老结构根本支撑不了。
我们的目标与选型思路
既然要做一次彻底的革新,那我们就得想清楚目标到底是什么:
- 可维护性:代码结构清晰,便于多人协作
- 性能优秀:减少首屏加载时间,提升用户体验
- 开发友好:有热更新、代码提示、类型检查等开发支持
- 渐进式迁移:不抛弃现有业务,逐步替换
- 构建可控:能够灵活拆包、分析体积、监控质量
基于这些目标,我们选用了如下技术栈组合:
| 模块 | 技术选型 | 原因 |
|---|---|---|
| 构建工具 | Vite | 快速冷启动,开箱即用 |
| 模块化 | ES Modules + TypeScript | 现代标准,类型安全 |
| UI 框架 | React (如果已有 Vue 或其他也保留) | 提供组件化能力 |
| 样式管理 | SCSS + CSS-in-JS (可选) | 提高复用性 |
| 包管理 | pnpm | 更快依赖安装 |
| 工程规范 | ESlint + Prettier + Git Hook | 统一风格 |
| 自动化 | GitHub Actions CI/CD | 减少人工失误 |
之所以没有一开始就全面切换到 Vue 或 React 的完整生态,是因为我们要给不同小组选择的空间。但核心架构必须统一,这样才能保证后续的兼容性和协同效率。
实战搭建过程
Step 1:搭建基础工程结构
我们创建了一个新的项目仓库,命名为 frontend-core,作为整个架构的种子项目:
vite create frontend-core --template react-ts
cd frontend-core
pnpm install
这样就快速得到了一个 TypeScript + React + Vite 的基础模板。
接着,我们在原有项目中设置软链接,让新旧两个结构可以共存:
ln -s ../frontend-core/src new-ui
然后通过修改入口 HTML 页面,动态加载新模块,实现逐步替换单页内容的目的。这种方式既降低了风险,也能让业务持续推进。
Step 2:配置构建与开发环境
Vite 已经内置了很多现代构建特性,但为了满足定制化需求,我们补充了一些配置:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: '../dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.includes('lodash') ? 'vendor-lodash' : 'vendor'
}
}
}
}
},
server: {
port: 3000,
open: true
}
})

这个配置帮助我们将 vendor 分离,并开启源码映射方便调试,同时也设定了本地服务端口。
Step 3:制定编码规范并自动执行
为了避免新架构变成“新瓶装旧酒”,我们在项目中集成 ESlint + Prettier:
pnpm add eslint prettier eslint-config-prettier eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev
然后配置 .eslintrc.js:
module.exports = {
root: true,
env: {
browser: true,
es2021: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['react', '@typescript-eslint'],
rules: {
// 定制规则
}
}
再结合 Husky 设置 Git Commit 时自动格式化:
npx husky-init && pnpm install
npx husky add .husky/pre-commit "pnpm lint"
这样每次提交前都会做一次静态检查,大幅降低低级错误的发生几率。
踩坑记录与解决方案
在整个过程中,我们也遇到了不少实际问题。分享几个印象特别深的:
🧱 CSS 全局污染问题
早期我们尝试在 React 组件中直接引用全局样式,结果导致样式冲突频发。例如:
import './MyComponent.scss'
这个问题的根源在于 SCSS 文件如果没有使用 @use 或 @forward,就会默认注入全局作用域。为解决这个问题,我们做了两件事:
- 使用 CSS Module:
import styles from './MyComponent.module.scss'
function MyComponent() {
return <div className={styles.container}></div>
}
- 推行 BEM 命名规范,即使不使用 CSS Module,也避免类名重叠。
🕳️ 第三方依赖版本冲突
有一个功能需要用到 dayjs,但因为之前项目已经依赖了较老的版本,升级后部分旧代码失效。我们最终采用了以下策略:
- 对新项目强制使用最新版
dayjs - 在老模块中通过别名引入旧版本
resolve: {
alias: {
dayjs$: path.resolve(__dirname, 'node_modules/dayjs')
}
}
不过这只是权宜之计,最终目标还是推动统一版本。
🔌 浏览器兼容问题
尽管现代框架默认支持大多数主流浏览器,但在我们客户的环境中,仍有约 5% 的用户在使用 IE11。我们不得不做一些适配处理:
- 引入 Polyfill:
pnpm add @vitejs/plugin-legacy
并在 Vite 配置中启用:
import legacy from '@vitejs/plugin-legacy'
plugins: [
legacy({
targets: { ie: '11' },
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
]
虽然带来了额外体积,但也让我们能安心交付。
成果与反思
经过三个月的努力,我们现在的新架构已经支撑起了 70% 的新功能开发。老项目仍然在线上运行,但新需求全部优先跑在这个新架构之上。
具体的收益包括:
- 构建速度提升了 80%,开发者体验改善明显
- 首屏加载时间从平均 3.5s 缩短到 1.2s
- 团队代码质量和一致性大幅提升
- 多个项目共享一套架构,协同效率提高
更重要的是,现在我们可以更容易地接入 CI/CD、自动化测试、代码覆盖率监控等工程实践。
我的建议:给正在起步的你
如果你也正准备搭建一个新的前端项目,或者想改造旧有架构,这里是我的几点建议:
不要一开始就追求大而全
我们一开始也想过一次性引入微前端、Monorepo、Serverless 这些热门词,但最终发现,先从小处着手,先把基础打好,是最稳健的做法。架构服务于业务
不要为了“新技术”而“新技术”。比如如果你的项目并不复杂,React 可能反而是一种负担,直接用纯 TS + DOM 操作更轻便。注意过渡期的平稳切换
如果是老项目升级,记得设计好新旧模块通信机制和共存逻辑。你可以借助 Web Component、模块联邦等方式做隔离。重视开发者的体验
一个好架构不仅要运行得好,更要让人用得舒服。所以像热加载、错误提示、IDE 插件支持这类细节,一定要提前规划。保持开放和迭代思维
架构不是一成不变的。半年后回头看当前的设计,你会发现很多可以优化的地方。重要的是保持学习和改进的动力。
写在最后
从零开始搭建一个前端项目,听起来好像很酷,但背后的挣扎和试错只有真正做过的人才知道。不过当我看到同事们第一次用上自动格式化、看到页面秒级热更新的时候,真的觉得这一切都是值得的。
技术架构只是工具,背后真正重要的,是一群愿意一起进步的人。希望这篇文章能给你一些启发,少走点弯路。如果你正在自己的项目中尝试类似的事情,欢迎留言交流,我们一起成长 😊

评论 0