从零开始构建一个现代化前端项目
从零开始构建一个现代化前端项目的实战经验分享

去年年底,我加入了一个新的项目组,任务是从零开始搭建一套全新的前端系统。这是一个面向企业内部用户的后台管理系统,需求涵盖权限控制、数据可视化、多环境部署等复杂功能。项目初期看似一切顺利,但随着开发深入,各种问题接踵而至——技术栈选型争议、性能瓶颈暴露、组件复用性差……整个过程就像一场不断“打怪升级”的旅程。
今天想借这篇文章聊聊我是如何一步步走过这个过程的,也希望能给正在面临类似挑战的你一点启发。
背景:为什么我们要重新开始?
我们接手的原系统已经运行了三年,代码结构混乱,组件耦合严重,维护成本极高。虽然可以继续迭代,但团队评估后认为重构更划算。
新项目目标很明确:
- 支持现代浏览器(Chrome/Safari/Firefox/Edge)
- 快速响应交互,FID控制在100ms以内
- 多环境部署:本地开发、测试、预发和生产
- 高可扩展性:未来可能接入PWA或微前端
- 团队技能匹配度高
于是,我开始思考从头搭建这个系统的每一步。
挑战一:技术栈选型的纠结与权衡
第一次会议就陷入争论:React vs Vue?Vite还是Webpack?要不要上TypeScript?
我们最终选择的技术栈如下:
- 框架:Vue 3 + Composition API(部分同学有React经验,但整体更倾向Vue生态)
- 构建工具:Vite,因为启动速度比Webpack快得多
- 类型系统:TypeScript 全项目覆盖
- 状态管理:Pinia(Vuex太重了)
- 路由:Vue Router 4
- UI库:基于Element Plus进行二次封装,适配公司设计规范
- 工具链:ESLint + Prettier + Stylelint + Husky + Commitizen
- 构建部署:CI/CD集成到GitLab Pipeline
这套组合不是凭空拍脑袋决定的。比如为什么不用React?原因很简单——我们团队中Vue背景的同学居多,学习成本更低;为什么不直接用原始Element Plus?因为我们需要统一组件样式,并做了一些自定义封装来提高复用性。
挑战二:构建流程的设计与优化
初期方案:简单粗暴的vite create
刚创建项目的时候,我们用了最简单的命令:
npm create vite@latest my-app -- --template vue-ts
然后手动添加Vue Router、Pinia、UI库等依赖。这种方式在项目规模较小的时候没问题,但到了中期就开始暴露问题:
- 构建时间逐渐变长
- 本地开发服务器有时卡顿
- 生产打包文件体积越来越大
性能优化思路
我们做了几个关键调整:
- 按需加载:使用自动导入插件
unplugin-auto-import和unplugin-vue-components,避免手动引入组件 - 代码分割:配置路由级懒加载 + 组件动态导入
- Tree Shaking:确保生产构建时真正移除无用代码
- Gzip压缩:Node.js服务器端开启gzip压缩,减小传输体积
- 图片优化:上线前自动压缩图片,使用WebP格式替代PNG/JPG
- 字体优化:去掉不必要的中文字体,只保留核心字符集
举个例子,关于代码分割:
// vue-router 的懒加载写法
const Dashboard = () => import('@/views/dashboard/index.vue')
通过这种模式,首页加载时只引入必要模块,大幅提升了首屏加载速度。
挑战三:组件复用与抽象设计
项目初期我们图省事,直接复制粘贴组件,导致后期修改一处就要改十处。
后来我们总结出以下几点:
- 建立基础组件库:把重复使用的按钮、表单控件、表格列渲染器抽象成公共组件
- 使用自定义Hooks:Vue 3的Composition API非常适合封装逻辑
- 采用原子化设计思想:组件尽量保持单一职责,易于组合
- 文档化+示例页:用VuePress为组件库生成文档和演示页面
比如我们封装了一个useTable Hook,用于处理通用的数据列表逻辑:
export function useTable<T>(
fetchFn: (params: any) => Promise<ApiResponse<T[]>>,
defaultParams: Record<string, any> = {}
) {
const data = ref<T[]>([])
const loading = ref(false)
const params = ref(defaultParams)
async function fetchData() {
loading.value = true
try {
const res = await fetchFn(params.value)
data.value = res.data || []
} finally {
loading.value = false
}
}
return {
data,
loading,
params,
fetchData
}
}
这样在多个页面都可以复用相同的数据加载逻辑,大大提升开发效率。
挑战四:调试、测试与质量保障
开发体验优先
使用VS Code + Volar插件获得最佳Vue 3 TypeScript支持
安装Vue Devtools V6 插件,查看组件树和props变化
在浏览器控制台打印日志时,加上命名空间区分:
console.debug('[Table Component] Loading finished', data.value)
自动化测试
尽管是后台系统,我们也坚持写了单元测试和少量E2E测试:
- 单元测试:Vitest + Vue Test Utils
- E2E测试:Cypress + Page Object Model
- CI流程中集成测试脚本,失败即阻止合并
举个例子,一个简单的表单验证单元测试:
test('form validation works', () => {
const form = mount(FormComponent)
form.find('input[name="email"]').setValue('invalid-email')
form.find('button[type="submit"]').trigger('click')
expect(form.text()).toContain('请输入有效的邮箱地址')
})
质量检查
我们在 Git 提交前加了两道防线:
- husky + lint-staged:只对修改过的文件跑 ESLint 和 Prettier
- commitizen + conventional-changelog:保证 commit message 格式统一
这样每次提交都经过检查,减少低级错误。
挑战五:部署与线上问题排查
CI/CD 流程
我们的流水线分为三个阶段:
- 安装依赖 & 构建
- 运行单元测试 & Lint检查
- 打包上传并部署到指定环境
部署方式我们采用了 Docker 包装 Nginx,方便多环境快速切换。
线上问题追踪
刚开始我们忽略了监控,直到一次用户反馈“页面白屏”,我们才意识到没有收集异常信息。
于是我们做了以下改进:
- 前端埋点SDK:记录用户行为和异常日志(自行封装,不依赖第三方)
- 错误边界(Error Boundary)兜底:在 App.vue 中捕获未处理的组件错误
- 添加 Sentry 接入日志服务(可选)
比如错误边界的实现:
<script setup>
import { onActivated, onErrorCaptured, ref } from 'vue'
const errorMessage = ref('')
onErrorCaptured((err) => {
errorMessage.value = err.message
// 上报错误
sendLog({ type: 'error', content: err.message })
return false
})
</script>
<template>
<div v-if="errorMessage" class="error-boundary">
页面出了点问题,请稍后再试。
</div>
<router-view v-else />
</template>
最终效果与收益总结
项目上线三个月后,我们做了复盘,主要收益包括:
- 开发效率提升30%以上:得益于良好的架构设计和组件复用机制
- 首次内容绘制时间(FCP)从4秒降至1.5秒
- 错误率降低80%:静态类型+自动化测试发挥了作用
- 代码可维护性显著增强:多人协作更加顺畅,新人上手更快
更重要的是,整套体系具备良好的扩展性,后续接入SSR、微前端都不是问题。
经验分享:给你的几点建议
- 技术栈要选团队熟悉的,而不是最热门的
- 前期多花时间规划目录结构和编码规范
- 尽早接入Linter,统一代码风格
- 不要忽略类型系统的力量,TS真的能预防很多错误
- 组件抽象要适度,过度设计反而增加理解成本
- 浏览器兼容性不要一开始就追求全支持,先看用户画像
- 监控和日志不能少,哪怕是一个简单的console上报也能救命

小结
从零构建一个前端项目从来都不是一件轻松的事,尤其当你面对的是一群真实的人类用户时。但只要我们认真对待每一个细节,提前做好规划,合理选择技术方案,就一定能构建出既稳定又高效的前端系统。
希望我的这段经历,能帮助你在自己的项目中少走些弯路。如果你也在做类似的项目,欢迎留言交流,我们一起进步!

评论 0