前端项目初始化,说出来好像没什么技术含量,但以前端开发这个岗位的日常来看,真正拉开项目质量差距的,往往就是从初始化这一脚开始的。项目初始化不只是执行一句脚手架命令,它需要回答的问题其实很多:代码放在哪个目录、用什么规范约束提交、依赖版本怎么锁定、测试和构建从哪一步开始介入。这些问题如果不在初始阶段拍板,后面就会出现一人生病全家吃药的局面——有人习惯把接口封装散在任意页面里,有人用组件文件又有人用逻辑拆分,等到多人协作时,光是文件位置就能吵上半天。
所以这个系列的第一篇,我打算把“项目初始化”这件事掰开揉碎讲清楚。它适合刚接触前端工程的新人,也适合写过几个项目但一直靠手工维护、想系统梳理一遍流程的老手。不管你用的是React、Vue还是别的框架,初始化的核心逻辑都是共通的。我们要做的不只是把页面跑起来,而是搭出一套后续开发、调试、发布都能舒服运转的底子,能在三个月后不返工、不重构的那种底子。
1. 为什么项目初始化值得单独写一篇
1.1 项目初始化到底在做什么
很多人把项目初始化理解成“脚手架生成一下就完事”,但一次完整的初始化至少包含四层工作。
第一层是环境决策,包括Node版本、包管理器、脚手架工具的选择;第二层是工程配置,包括TypeScript、构建工具、路径别名、环境变量等;第三层是协作规范,包括ESLint、Prettier、Git提交规范、分支约定;第四层是基础代码的组装,比如路由结构、状态管理、请求层、通用组件的初始形态。这四层一层都不能缺,缺了哪一层,后面都会以各种形式找补回来。
空下来看这几个问题的布局,听起来都不复杂,但真正把它们落地到具体文件、具体命令、具体的钩子脚本里,需要跨很多知识面。这一篇的价值就是把这些散落在日常经验里的环节串成一条完整的流程,告诉你每一步在解决什么问题,以及为什么这一步不能跳过。
1.2 初始化阶段决定了项目后续的开发体验
一个初始化粗糙的项目,通常会在三个月后爆发这些症状:新成员找不到某个工具函数应该放在哪里、ESLint报错没人敢改配置、依赖升级后无法回退、团队成员被不同的格式化风格反复折磨。而一套扎实的初始化流程,能把这些坑提前填平。
我见过一个真实的例子,某项目从仓库克隆下来后,新成员按照文档安装依赖,结果一跑就报错。排查了半天发现,package.json里的依赖版本写的是^1.2.3这种带脱字符的范围版本,而当前包管理器解析出来的版本和当初开发时完全不一样。这就导致行为表现不一致。这就是初始化时没有锁定依赖版本、也没约定包管理器版本的隐患。反过来,如果初始化时就引入锁文件机制,并把包管理器版本也一并说明清楚,这类问题根本不会出现。
另外一个常见的现象是项目里混用了多种代码风格。有人说自己只写三目运算符,有人写if判断,有人喜欢在组件文件里直接写样式,有人非要全部抽到独立样式文件。这些差异在初始化阶段如果通过工具统一掉,后面就不需要靠人肉评审来纠正,省下大把沟通成本。所以说,初始化做得好的项目,后面每个成员都是在固定的轨道上“添砖加瓦”;初始化做得差的项目,后面每一步都是先“清理废墟”。
2. 技术选型:先把地基打好
2.1 脚手架:为什么现在默认选Vite
初始化项目的第一步是选脚手架。前几年提到前端工程化,大家默认就是webpack全家桶;后来出现了一些更便捷的一体化创建工具。到现在,我的观点很直接:新项目不用纠结,优先考虑Vite。
Vite的核心思路是开发环境下利用浏览器原生ES模块,启动时不用预先打包,冷启动速度和热更新速度都比传统方案快一个数量级。举个例子,一个上百个页面规模的项目,用传统方案冷启动可能要去倒杯水,Vite往往一两秒就能给出响应。对于需要频繁验证效果的日常开发来说,这个差异体感极其明显。生产构建阶段Vite默认基于Rollup完成打包,产物体积和分包能力也在持续进步。
我用一张表格大概对比一下Vite、webpack和传统一体化工具在新项目里的感受:
| 维度 | Vite | webpack | 传统一体化工具 |
|---|---|---|---|
| 冷启动速度 | 秒级,依赖按需加载 | 分钟级起步,随项目膨胀变慢 | 中等,受底层工具限制 |
| 热更新 | 模块级、非常快 | 项目大时会有明显延迟 | 稳定性一般 |
| 配置复杂度 | 低,零配置即可跑通 | 高,需要手动管理大量配置 | 低到中 |
| 生态兼容 | 主流插件基本齐全 | 插件最丰富但老插件多 | 相对封闭 |
| 适合场景 | 新项目、中小型业务、快速原型 | 遗留项目、复杂定制构建 | 一体化简单场景 |
表里没有提到的一个点是,Vite也支持wrapper兼容很多旧插件,所以即使项目里有一些历史包袱,也不是完全不能迁移。只不过新项目初始化没必要背着这些包袱上路。如果你的团队还没用过Vite,我建议先拿一个小型项目试水,跑通之后再推广。
2.2 TypeScript与框架的取舍
语言层面,我现在基本不做第二种选择,直接用TypeScript。很多刚接触的人会担心TypeScript增加心智负担,但在项目初始化阶段引入它,长期收益远大于短期成本。类型系统相当于一份可执行的接口文档,多人协作时能省下大量沟通成本。一个函数的参数是string还是number,不需要点进源码逐行看,编辑器里的类型提示已经告诉你答案。
TypeScript的配置文件tsconfig.json值得在初始化阶段花十分钟过一遍。比如开启strict模式,从一开始就避免隐式any泛滥;比如配置路径别名,让@/指向src目录,避免项目里出现大量../../这样的相对路径。路径别名在大型项目里对可维护性的提升非常明显,改一个文件的位置不需要牵一发动全身地改路由和组件引用的相对路径。
框架层面,React和Vue各有拥趸,这里不争哪家更好。关键是初始化时把项目定位想清楚:管理后台、内容展示站点、移动端H5,对应适合的方案并不相同。如果是移动端且需要同时覆盖小程序,小型团队可以优先考虑跨端框架;如果是传统中后台系统,建议把状态管理和权限模型的设计放到初始化阶段一并考虑,而不是等页面写了一半再补。
2.3 包管理器:pnpm还是npm
包管理器看似小事,实际影响非常大。npm是Node环境自带的默认选择,兼容性最好,但node_modules体积大、安装速度慢、幽灵依赖的问题让人头疼——你明明没有在package.json里声明某个依赖,代码里却能直接引用到,这就是因为某层依赖把它带出来了,很容易造成环境之间不一致。pnpm用全局内容寻址存储加硬链接的方式,不仅节省磁盘空间,而且严格的项目隔离能避免不少隐性问题。现在的pnpm也基本兼容npm的生态。
从经验来看,新项目我更推荐直接用pnpm。如果团队对pnpm不熟,npm也完全够用,但要在初始化时约定好版本。npm 8以上的版本在性能和锁文件处理上还算稳定。包管理器版本不一致会导致锁文件格式不同,这也是很多人踩过的一个坑。可以在package.json里加一个packageManager字段,声明项目使用哪个包管理器及其版本,再配合包管理器的corepack机制,就能尽量避免成员各自用默认环境折腾出不同锁文件。
对比一下常见包管理器:
| 特性 | npm | pnpm | yarn classic |
|---|---|---|---|
| 默认内置 | Node自带 | 需单独安装/开启 | 需单独安装 |
| 安装速度 | 一般 | 快 | 较快 |
| 磁盘占用 | 高 | 低,硬链接共享 | 中等 |
| 幽灵依赖 | 存在 | 默认隔离 | 存在 |
| 锁文件 | package-lock | pnpm-lock | yarn.lock |
3. 环境准备与项目搭建
3.1 Node.js版本管理
项目初始化第一个容易翻车的点,是Node.js版本不匹配。不同项目对Node版本要求不同,一台机器上经常需要同时维护多个Node版本。我建议用nvm这类版本管理工具,允许随时切换全局Node版本,也能通过.nvmrc文件让项目锁定Node版本。初始化的时候先执行一下node -v看看当前版本,如果偏低或者跟团队不一致,就先切换。
Node版本到底选多高?这得看脚手架和依赖库的支持情况。以Vite搭建的React项目为例,常见做法是选18或20这个长期支持主线。长期支持版本稳定性更好,开发环境和生产环境都建议保持一致。我见过有项目本地用Node 22,服务器还停在Node 16,结果某个语法或内置API在新旧版本里表现不一致,只在一方报错,排查半天才发现是版本差异。所以初始化时把Node版本写进README,最好让CI里的构建环境也锁定到同一版本。这里有一个细节:nvm的使用并不复杂,但要注意在CI或Docker构建里也得同步指定Node版本,不能只在本地装。
3.2 用Vite创建项目
当我需要开始一个新项目时,最常执行的命令是:
pnpm create vite my-project --template react-ts这条命令会创建一个使用React和TypeScript的Vite项目。如果用的是Vue,把模板参数换成vue-ts即可:
pnpm create vite my-project --template vue-ts执行过程中,Vite会询问几个问题,包括是否安装依赖、是否启动,直接确认就好。生成后的项目自带基本的配置文件、入口文件和一个示例页面。很多人在这一步会直接把默认的示例代码删掉,然后开始写业务。我建议先不要急着删,先把目录结构和配置文件看一遍,理解脚手架帮你做了哪些决策。
Vite生成的配置都在vite.config.ts里,里面通常已经预设了开发服务器的地址、代理和插件配置。如果项目里会用到路径别名,比如@指向src,也需要在这个文件里配合TypeScript的paths一起配置。看着简单,但漏配国内很常见,结果代码里用@/导入,编辑器不报错,运行或构建时却找不到模块。初始化阶段顺手配好,能避免很多后续的魔改。
3.3 基础依赖安装
脚手架生成的项目依赖很少,通常只有框架本身和开发构建相关的工具。真正跑一个业务应用,还需要补齐路由、状态管理、样式方案等基础依赖。以React项目为例,我通常会装这些:
pnpm add react-router-dom @tanstack/react-query zustand路由负责页面跳转,状态管理负责跨组件共享数据,请求层负责和接口交互。这几个库之间并不是完全互斥,比如React Query和请求函数对应,Zustand负责全局状态,两者可以共存。关键是这些决策要在初始化阶段统一,不要等页面写了一半再引入,不然每个人对“数据该放哪”的答案都不一样。
样式方案也是同理,是CSS Modules、Tailwind还是Sass,建议早点决定。很多项目后期出现样式混乱,都是因为初始化时没有统一方案,各人写各人的。比如有人用纯CSS文件,有人在组件标签里写style,有人引入Tailwind的原子类,最后维护起来异常痛苦。初始化阶段把方案写进文档,团队照着一个方式来写,才能保证后续样式可预期。
4. 工程化配置:让项目跑得更稳
4.1 目录结构设计
初始化阶段最值得花时间的是目录结构设计。一套好的目录结构要满足的核心原则是“可预期”:任何人接手代码,不用问别人就能找到要找的文件。我常用的目录是源码放在src目录下,按功能模块而非按文件类型组织,公共组件、页面、请求、工具函数各自独立。一个参考结构如下:
src/ api/ // 接口请求封装 assets/ // 静态资源 components/ // 公共组件 composables/ // 逻辑复用 layouts/ // 页面布局 pages/ // 页面级组件 router/ // 路由配置 store/ // 全局状态管理 styles/ // 全局样式 types/ // 类型定义 utils/ // 工具函数这个结构不是标准答案,但它解决了几个关键问题:新增页面知道放哪、公共代码不会散落得到处都是、类型定义集中管理。如果项目规模真的很小,先只划分pages、components和utils三个目录也可以,等规模成长了再做拆分。目录结构调整起来其实不难,但项目大了之后调整成本会指数级上升,所以初始化阶段值得把这些目录建好。
另外,目录命名要统一用单数还是复数、用驼峰还是短横线,也要一开始约定。比如components还是component,userInfo还是user-info。这类细节看着琐碎,但影响到每个文件的导入路径和记忆成本。团队约定好之后,尽量写进README或贡献指南。
4.2 ESLint与Prettier
没有代码规范的项目,最终会变成代码风格的大杂烩。初始化时就引入ESLint和Prettier,成本很低,收益很高。ESLint负责检查代码质量和潜在错误,Prettier负责统一格式。好用的做法是在ESLint里集成Prettier规则,让代码保存时自动格式化。
我举个例子,团队里有人习惯双引号加末尾分号,另一个人偏好单引号且不写分号,两人改同一份文件的时候,diff里面全是格式化噪音,真正的业务改动反而看不清。引入统一格式化后,这类问题彻底消失。ESLint还能查出一些真正的逻辑问题,比如==和===混用、未使用的变量、不安全的条件赋值。
Vite脚手架生成的模板里通常已经包含了ESLint配置。我们需要确保编辑器在保存时自动执行格式化,并配置Prettier的规则,比如单引号、不写分号、缩进宽度等。下面是一份比较基础的Prettier配置示例:
{ "semi": false, "singleQuote": true, "printWidth": 100, "trailingComma": "es5" }ESLint里也可以配置一些项目级规则,比如禁止在代码里出现console.log、强制显式返回类型、限制函数复杂度。不要一上来就加太多规则,规则多了会让人反感且难以维护。初始化阶段先配置最核心的检查项,后续团队有共识再增量添加。Prettier的哲学是“可争论的规则越少越好”,初始化阶段把规则定下来,后续就不要再频繁调整,避免全仓库大范围diff。
4.3 Git初始化与提交规范
代码写得再规范,协作流程不规范也白搭。初始化Git仓库时,第一件事是写好.gitignore文件。Vite模板自带的.gitignore一般已经覆盖了node_modules和dist目录,但还要注意忽略本地环境文件,比如.env.local、日志文件、编辑器临时目录。这些文件一旦进了版本库,处理起来很麻烦,提前忽略是王道。
提交规范方面,我建议用husky配合lint-staged,在提交前自动跑检查和格式化。husky能在Git提交或推送时触发钩子脚本,lint-staged则只对暂存区的文件执行检查和格式化,这样即使项目变大,提交前检查的速度也不会明显拖慢。一个典型配置是:
pnpm add -D husky lint-staged然后在package.json里声明lint-staged的规则,比如:
{ "lint-staged": { "*.{ts,tsx}": ["eslint --fix", "prettier --write"] } }提交信息规范我也建议在初始化阶段约定。社区里常用的格式是feat、fix、docs、refactor等类型前缀,配合commitlint来强制校验。这样生成的提交历史清晰、方便回溯,也为后续自动生成变更日志留好了基础。如果你不想一开始就上commitlint,至少可以在项目文档里约定commit格式,让团队先养成习惯。提交信息写得好,将来用命令回溯一行代码为什么被改,节省的时间非常可观。
5. 实操过程:完整跑通初始化流程
5.1 从零到一的操作清单
我整理了一份比较顺手的操作清单,照着做基本不会踩坑:
- 用nvm把Node切到指定版本,确认
node -v和npm -v都是预期结果; - 用
pnpm create vite命令创建项目,选择React/Vue和TypeScript模板; - 进入项目目录,执行包管理器安装依赖;
- 补充路由、状态管理等基础依赖;
- 配置ESLint和Prettier,统一编辑器设置;
- 调整目录结构,建好src下常用文件夹;
- 初始化Git仓库并配置.gitignore;
- 启动开发服务器验证HMR和页面访问;
- 执行一次生产构建,确认没有报错;
- 提交第一个commit。
有人会觉得这些步骤太琐碎,不就是一个项目吗。但实际操作中,很多人就是在第五步之后开始放飞自我,结果过几天就要推倒重来。初始化流程的完成标志不是“页面能显示”,而是“后续加代码的路径是明确的”。所以第八步和第九步不能省略。先启动一次开发服务器确认HMR正常,再跑一次构建确认生产构建没有报错,最后提交一次commit确认Git钩子都生效。这些验证都通过,才算真正完成初始化。
5.2 启动开发服务器并验证
执行开发命令很简单:
pnpm dev如果用的是Vite,控制台会打印本地的访问地址。打开浏览器后,把首页渲染、路由跳转、页面热更新都依次试一遍。热更新的标准是:修改一个组件里的文字,浏览器应该在不刷新的情况下自动变化。如果改了代码页面没反应,八成是HMR配置或依赖监听问题。
接着跑一次类型检查和构建:
pnpm build构建成功后会生成dist目录,这就是要部署到服务器的产物。如果构建阶段就报错,说明项目配置还没到位,别等到上线前才发现。构建时也可以顺手看看产物提示,比如chunk大小、依赖包体积。如果某个依赖特别大,可以考虑后续做代码分割或异步加载。初始化的项目跑一次构建的价值就是确认整个链条是通的,而不是等到业务开发了几千行代码之后再暴露问题。
5.3 第一次提交的完整流程
开发服务器验证通过后,进入Git提交流程。先执行git init初始化仓库,然后git add把所有文件加入暂存区,再执行git status检查有没有不该提交的文件,比如本地的.env.local、日志文件,或者某个临时目录。
如果全局没有配置过Git用户信息,当前项目里要单独设置:
git config user.name "your-name" git config user.email "your-email@example.com"如果配置了husky和commitlint,第一次提交时它们会自动执行检查。可能遇到提交信息不合法、ESLint报错等情况,这恰恰说明钩子生效了。第一次提交信息我通常写feat: project init。提交完成后,用git log --oneline查看一下提交记录,确认结构没问题,再推送到远程仓库。这里要注意,远程仓库如果已经有一些代码,比如自动生成的README,务必先git pull --rebase再push,避免无意义的合并分支或覆盖别人的提交。
6. 常见问题与排查技巧实录
6.1 Node版本不匹配
最常见的报错是类似“Unsupported engine”或者“requires node >=18”这样的提示。遇到这种情况,先确认当前Node版本:node -v。如果版本偏低,用nvm安装并切换;如果版本过高,也未必是好事,某些老依赖在Node新版本下会有兼容问题。
建议让项目的Node版本和CI环境保持一致,并在README里写清楚最低版本要求。有人会问,为什么不直接在package.json里固定Node版本?实际上约定加工具才能生效,比如在.nvmrc里写好版本号,再用包管理器的引擎校验来兜底。初始化阶段就把这套联动配好,后续基本不会再遇到“本地跑得动,大家都在报错”的局面。
6.2 依赖安装失败
依赖安装失败的原因多种多样,网络原因、镜像源问题、node_modules缓存损坏都可能导致。我常用的排查步骤是:先删掉node_modules和锁文件重新安装,再看报错信息是否指向某个具体包。如果反复不行,考虑切换镜像源或检查代理配置。另一个容易忽略的点是包管理器版本不统一,有人用pnpm 6,有人用pnpm 9,生成的锁文件格式不同,协作时非常容易冲突。解决方法是统一包管理器,并把版本信息写入package.json的packageManager字段。
另外,在Windows和macOS/Linux不同的环境里,某些依赖的安装脚本行为也可能不一样。比如一些原生的二进制包,在Windows上可能需要预编译版本,否则尝试本地编译会报缺工具链。初始化阶段如果团队成员已经跨平台,建议提前明确每个平台的额外依赖要求,避免新成员一上来就卡在安装这一步。
6.3 端口被占用
启动开发服务器时提示端口被占用,一般是之前的开发进程没有退出,或者同时跑着另一个项目。Vite会默认递增端口,所以换个端口也能继续开发。如果希望项目始终用固定端口,可以在vite.config.ts里的server.port字段设置。排查时可以用系统命令查看端口占用进程,确认没有残留后再重新启动。
还有一个相关坑:前端项目常用端口号和本地代理绑定。如果代理配置里写错了目标端口,控制台不会直接报错,但所有接口请求都会失败。初始化阶段如果项目需要对接后端服务,最好把代理配置和约定端口一并写入vite.config.ts和团队文档,减少每个人各自猜测的情况。
6.4 husky钩子不生效
装了husky后提交时钩子却没有执行,大部分原因是只安装了依赖但没跑husky的初始化命令。现代版本的husky需要执行npx husky init来生成.husky目录和钩子脚本。如果项目是从某个模板拷贝的,.husky目录没有被提交到Git,新克隆的仓库自然没有钩子。
验证钩子是否生效,可以临时在文件里加一行故意不合规的代码,再尝试提交一次,看钩子是否拦截。如果拦截了,说明配置正常;如果没有,就要检查husky的安装状态和Git的core.hooksPath设置。另外一个常见的误操作是,直接把旧的钩子脚本复制进.husky,但钩子脚本里找不到可执行命令,比如npx在某些环境下的路径问题。初始化阶段把这些跑通一次,后续成员就不会被这种问题反复折腾。
6.5 热更新不生效
热更新失效的体验非常难受。常见原因包括:编辑器保存触发的是文件变更但已经被忽略;项目目录在虚拟磁盘或共享目录中,文件监听失效;某些缓存问题导致状态残留。我的排查顺序是:先看控制台有没有报错,再试着重启开发服务器。如果重启之后恢复正常,多半是监听问题。
使用某些网络驱动器或同步盘同步代码的开发环境,Vite的HMR常常不稳定,这也是这类工具链的一个已知短板。项目初始化时尽量让代码库放在本地磁盘,远程同步目录只做备份,能省掉很多诡异问题。如果项目本身很大,还可以调整Vite的server.watch配置,比如关闭轮询的开销,或者把node_modules排除在监听范围之外。初始化阶段不需要调这些优化,但知道排查方向,遇到问题时不至于一脸懵。
7. 一些初始化过程中的经验补充
写完主体流程,再顺手补几个我踩过的坑,都是文档里通常不写的东西。
第一,初始化的第一个commit一定要“干净”。意思是不管项目多小,第一次提交前先确认没有大文件、没有密钥文件、没有本地临时文件。一旦密钥或大文件进了Git历史,后面清理起来极其麻烦,因为每次checkout都会把历史带出来。初始化那天就养成用git status的习惯,比任何时候都重要。
第二,别急着引入一堆花哨的库。项目初始化阶段最容易冲动安装各种“看起来有用”的依赖,结果很多用不上,还白白增加体积和安全风险。我现在的习惯是“用到才装”。比如没做图表之前不装图表库,没做拖拽之前不装拖拽库。基础依赖控制在最小必要范围内,后续再按业务需求增量添加,反而更清爽。
第三,把初始化决策记录下来。可以是在README里建一个“项目约定”章节,也可以单独写一份工程规范文档。内容包括Node版本、包管理器、目录结构、命名规范、提交规范、样式方案。这些信息看着琐碎,却是新成员接入项目的“接口说明书”。项目做久了,文档比代码更稀缺,初期随手记几句,后面能少回答无数次重复问题。
第四,如果你所在团队有多个项目,可以考虑把初始化流程沉淀成内部模板。当模板跑通一次之后,新成员创建项目的时间能从半天压缩到十分钟。但模板本身也要定期维护,不要一劳永逸。依赖升级、插件变更、团队规范调整,都需要同步更新模板。把初始化当作一个持续维护的工程资产来对待,收益和投入是完全成比例的。
写到这里,这一篇初始化流程算是踩出了一个相对顺滑的版本。我个人实际操作中的体会是,前端项目初始化最重要的不是用多新的工具,而是把决策都提前做完并写下来。哪怕某些工具组合不是最流行,只要团队达成一致并且文档清楚,项目推进起来都会踏实很多。如果你正准备开一个新前端项目,照着这份流程先跑一遍,大概率能少走很多弯路;等跑完一遍,再根据团队习惯调整目录和规则,很快就会形成属于你自己的初始化标准。