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

两年前,我加入了一个新创业团队,负责从零搭建公司第一个核心产品——一款面向中小企业的数据看板工具。作为一个有着5年开发经验的前端工程师,我本以为这会是一次“熟门熟路”的任务,但真正动起手来才发现,现代前端项目的复杂程度远超预期。
这篇文章我想以第一人称的角度,分享一下我在这次项目中从零开始构建一个现代化前端项目的全过程。不只是技术选型和代码实现,更多的是踩过的坑、积累的经验和对当前前端趋势的一些思考。希望它能成为你开启新项目的实用参考。
初期规划:为什么不能直接“开干”?
刚接到这个项目时,我和产品经理简单过了一遍需求后就准备开工了。可当我打开 VS Code 新建 index.html 文件的一瞬间,脑子里突然冒出一堆问题:
- 我们该用 Vue 还是 React?要不要引入 TypeScript?
- UI 库怎么选?Element Plus、Ant Design Vue 还是自己造轮子?
- 打包工具应该选 Vite 还是 Webpack?
- 如何确保页面在 IE11 上跑得动?(别笑,真的有客户要求)
- 怎么做性能优化?首屏加载要多快才算合理?
- 测试怎么做?写不写单元测试?
- Git 工作流如何设计?
这些问题如果现在不做决定,后面就得为每一个后悔买单。
于是我花了整整一周时间做了技术调研和架构设计,把项目结构、技术栈、开发规范都梳理了一遍。虽然前期花了不少时间,但后来的事实证明,这一步至关重要。
技术选型与架构设计:不是越新越好,而是越合适越好
我们最终确定的技术栈如下:
- 主框架:Vue 3(用了 Composition API + Setup Script)
- 状态管理:Pinia(替代 Vuex)
- 构建工具:Vite(开发体验简直飞起)
- 语言:TypeScript(强类型带来的维护优势太明显)
- UI 组件库:Element Plus(兼顾美观与社区生态)
- 路由:Vue Router(4.x)
- 接口封装:Axios + Axios 拦截器统一处理 token 和错误
- 打包部署:Vite + Docker + Jenkins CI/CD
- 测试:Jest + Vue Test Utils + Cypress
为什么选择这些?
- Vue 3 是因为团队成员大多熟悉 Vue,且 Composition API 更适合组件化开发。
- Pinia 相比 Vuex,API 更清晰,模块组织更灵活,而且默认就是 TypeScript 支持。
- Vite 的热更新速度远胜 Webpack,这对于频繁修改组件的前端开发来说极其关键。
- TypeScript 是因为项目中期会有多人协作,强类型可以大幅减少低级错误。
- Element Plus 是因为客户界面风格偏向企业级仪表盘,自带组件丰富且文档完整。
- Cypress 用来做 E2E 测试,支持异步等待、真实浏览器模拟等高级功能,比 Selenium 好用太多了。
架构搭建:模块划分 & 分层设计
我把整个项目分为以下几个层级:
src/
├── assets/ # 静态资源
├── components/ # 通用业务组件
├── views/ # 页面视图组件
├── router/ # 路由配置
├── store/ # Pinia 状态管理模块
├── services/ # 接口服务层
├── utils/ # 工具函数
├── hooks/ # 自定义 Hooks(Composition API 封装)
├── layout/ # 布局组件
├── App.vue # 根组件
├── main.ts # 入口文件
这种分层方式让每个模块职责明确,也方便后续多人协作。比如 services/ 层专门负责接口调用,utils/ 放一些字符串处理、格式化的方法,hooks/ 就是各种可复用的逻辑封装。
举个例子,我在 hooks/useChart.ts 中封装了一个图表初始化的逻辑:
import * as echarts from 'echarts'
export function useChart(el: HTMLElement) {
const chart = ref<echarts.ECharts | null>(null)
function init() {
if (!el) return
chart.value = echarts.init(el)
}
function setOptions(options: echarts.EChartsOption) {
chart.value?.setOption(options)
}
function resize() {
chart.value?.resize()
}
return { init, setOptions, resize }
}
这样在组件里就可以非常简洁地调用:
<script setup>
const chartDom = ref<HTMLDivElement | null>(null)
const { init, setOptions } = useChart(chartDom.value!)
onMounted(() => {
init()
setOptions({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: {},
series: [{ data: [10, 20, 30], type: 'bar' }]
})
})
</script>
<template>
<div ref="chartDom" style="width: 600px; height: 400px;"></div>
</template>
开发中的挑战与解决方案
🧱 挑战一:IE11 支持
我们有个大客户坚持要用 IE11,这就意味着:
- Vue 3 默认不支持 IE,必须降级到 Vue 2?不,我们选择了 Babel + polyfill 方案。
- async await 不支持?Babel 插件自动转换。
- Promise、Object.fromEntries 等原生方法缺失?加
core-js补丁。 - CSS 变量不兼容?手动替换为 fallback 方案或使用 postcss-preset-env。
最后通过 vite.config.ts 设置:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
vue(),
legacy({
targets: { ie: '11' },
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
]
})
🧪 挑战二:测试覆盖率不高,没人愿意写测试?
我们一开始也没强制写测试,结果上线前出了几处低级 bug(比如某个字段为空时没做校验),才意识到测试的重要性。
于是我们制定了几个策略:
- 单元测试使用 Jest + Vue Test Utils,用于验证组件逻辑是否正确。
- 使用
vitest替代 Jest,更快更轻量。 - 引入 Cypress 做 E2E 测试,在浏览器环境中真实模拟用户操作。
- 在 Jenkins 构建流程中集成测试检查,测试失败则阻止部署。
举个简单的组件测试示例(HelloWorld.spec.ts):
import { mount } from '@vue/test-utils'
import HelloWorld from '../components/HelloWorld.vue'
test('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = mount(HelloWorld, {
props: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
而 Cypress 的测试脚本看起来像这样:
describe('登录测试', () => {
it('成功登录并跳转首页', () => {
cy.visit('/login')
cy.get('#username').type('admin')
cy.get('#password').type('123456')
cy.get('button[type=submit]').click()
cy.url().should('include', '/dashboard')
})
})

⏱️ 挑战三:打包体积过大,首屏加载慢
我们用了几个手段进行优化:
- 代码分割:路由懒加载 + 动态 import。
- 第三方库按需加载:比如只引入 Element Plus 所需的组件。
- Gzip 压缩 + 静态资源 CDN 加速。
- 图标使用 SVG Sprite 方案,避免引入字体图标库。
- 使用
unplugin-icons实现图标按需加载。
例如路由懒加载的写法:
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue')
}
而图标部分我结合了 @vitejs/plugin-react-swc 插件和 unplugin-icons,让 SVG 图标也能像 React Component 一样被按需引入。
踩坑经验分享
Vite + TSX 支持要注意插件顺序
一开始我们在
vite.config.ts中没有正确导入 @vitejs/plugin-react-swc 插件,导致 JSX 解析报错,后来发现顺序也很重要。Element Plus 的国际化需要提前处理
项目中期接入国际化方案时才发现 Element Plus 并非自动适配,需要自己引入 locale 文件,并挂载到 app 上。
CSS Module 冲突问题
最初我们统一用
scoped来控制组件样式隔离,但后来发现某些公共类名(如.container,.clearfix)容易冲突。我们改用 CSS Modules 加命名空间的方式解决了这个问题。本地环境没问题,部署后接口 404?
因为我们用了 History Mode 的 Vue Router,所以 Nginx 需要配置将所有请求回退到
/index.html。否则访问/dashboard会直接报 404。Chrome 控制台显示 Source Map 错误?
后来发现是我们开启了 sourcemap 打包,但在生产环境不该暴露 source map 文件。Vite 默认 dev 环境生成 sourcemap,build 时不生成,这点需要注意。
效果总结:上线后的收益与反馈
项目上线半年后,我们做了几点总结:
- 首屏加载时间从最初的 5s 缩短到 1.2s 左右
- Bug 数量下降明显,得益于 Typescript 和测试保障
- 新同事上手速度快了很多,得益于清晰的目录结构和开发规范
- 维护成本下降,因为模块化良好、复用性高
- 用户反馈说交互流畅、UI 设计专业
最让我自豪的是,在后续多个新项目中,我们都基于这套架构模板快速搭建,极大提升了开发效率。
给读者的经验建议
如果你正准备启动一个新项目,以下是我结合多年经验总结出来的几点建议:
- 不要急着写代码,先想清楚架构。
- 技术选型不要追新,要匹配团队能力和项目需求。
- 尽早集成测试机制,哪怕写简单断言也好过没写。
- 重视打包优化,哪怕是小项目也值得做代码拆分。
- 写好文档和注释,尤其是团队协作项目。
- 学会调试,掌握 Chrome DevTools、Vue Devtools、React Developer Tools 这些工具。
- 持续学习新技术,但要有取舍,避免陷入“技术焦虑”。
- 定期重构代码,保持良好的可维护性。

结语:前端工程化,永远在路上
回过头来看,这次项目不仅仅是完成了一个产品的需求,更是让我重新思考前端工程化的本质——如何用更科学、更高效的方式去交付价值。
技术总是在变,工具链也会不断进化,但我始终坚信一点:好的项目结构 + 明确的开发规范 + 高质量的工程实践,才是持续产出稳定成果的根本。
如果你也在从零构建一个新的前端项目,不妨试试上面提到的思路。愿你在接下来的工作中少走弯路,写出优雅、可维护、又靠谱的代码。共勉!

评论 0