说实话,"vue2创建项目"看起来是个特别入门的话题,但我带过的小组里,几乎每一个新人都在这件事上栽过跟头。不是vue create跑不起来,而是跑起来之后发现各种版本冲突、依赖报错、目录混乱、联调环境配不明白——等到项目真正跑顺,一天就过去了。这篇文章我想把这套流程从头到尾捋一遍,结合我这些年实际创建Vue2项目的经验,把我踩过的坑、验证过的方案、以及最终稳定可复用的配置方式都写出来。无论你是刚接触Vue2的新手,还是准备给团队搭一个规范项目基座的开发者,这篇内容应该能帮你少走很多弯路。
1. 动手之前,先想清楚脚手架选型
很多教程上来就是一句"运行npm install -g @vue/cli,然后vue create项目名",但真正落到企业项目里,选型这一步才是决定后面顺不顺利的关键。我先说结论:Vue2项目长期维护、需要稳定生态的,优先选Vue CLI 5;追求极致启动速度的,可以考虑Vite + 兼容插件的方式;纯手动搭建Webpack不是不行,但我个人不推荐,除非你有充分的定制需求。
1.1 三种方式的取舍逻辑
先看官方脚手架。Vue CLI从4.x升级到5.x之后,底层Webpack升级到了5,对现代Node版本的支持也好了很多。Vue2项目的标准姿势,现在普遍是Vue CLI 5.0.8 + Vue 2.7.x,这是最被广泛验证过、遇到问题社区资料最多的组合。你用Vue CLI创建项目时,默认会安装Vue 3.x模板,但可以在交互选项里手动选择Vue 2版本,也可以通过指定preset来快速生成,后面我会详细说。
Vite方案在Vue3时代是主流,但在Vue2场景下,需要靠官方插件@vitejs/plugin-vue2来支持SFC编译。它的优点是开发服务器冷启动快、热更新快,这在大型项目里的体感差异非常明显;缺点则是生态成熟度不如Vue CLI,部分Vue2生态里的老库(尤其是那些需要特殊Webpack处理的)可能没法直接用。我的态度是:如果是全新的、知道自己在做什么的Vue2项目,Vite可以尝试;如果是维护老项目或者团队普遍熟悉Vue CLI,那还是选Vue CLI,省心。
手动搭建Webpack + Vue2的方案,理论上可以做到完全定制,但你要自己去配置vue-loader、vue-template-compiler、CSS处理、HMR等一系列东西。我自己只在做一个需要深度定制构建流程的组件库时用过这种方式,普通业务项目完全没必要。
1.2 版本兼容性检查表
这里直接给出一份我实测过的版本搭配方案,按推荐程度排序:
| 场景 | 推荐Node版本 | 脚手架/构建工具 | 备注 |
|---|---|---|---|
| 标准Vue2项目(长期维护) | 16.20.x 或 18.19.x | Vue CLI 5.0.8 | 最稳,资料最多 |
| Vue2老项目升级现有CLI | 14.x 或 16.x | Vue CLI 4.5.x | 老项目慎上Node 20 |
| 追求开发体验的新项目 | 18.x | Vite 4 + @vitejs/plugin-vue2 | 冷启动快,但要注意兼容性 |
| Node 20.x + Vue2 | 20.x | Vue CLI 5.0.8 | 可用,但node-sass等老依赖会报错 |
踩过的坑必须提一句:千万别在Vue2项目里用node-sass,除非你确认Node版本和它编译的二进制文件完全匹配。我自己后来全部换成了sass(也就是Dart Sass),配合Vue CLI 5完全没问题。你在创建项目选择CSS预处理器时,也尽量选"sass"而不是"node-sass"(Vue CLI 5里选项名已经不怎么区分了,但安装依赖时要注意看装的是哪个)。
1.3 为什么企业项目普遍锁死Vue2
很多人会问,新项目为什么不用Vue3?这里不讨论技术优劣,只说现实因素:很多现存业务系统的依赖库、组件库、内部脚手架都是基于Vue2写的,升级到Vue3意味着整套基础设施要重新适配,成本极高。这种情况下,"创建Vue2项目"并不是从零开始,而是要在一堆老链路里插入一个新的子应用或者重构入口。所以你会看到,即使社区都在推Vue3,Vue2项目在企业里依然大量存在。
2. vue create初始化过程中的选项决策
确定用Vue CLI之后,就进入实际创建流程了。先确保你已经安装了Vue CLI:
# 全局安装或升级到指定版本 npm install -g @vue/cli@5.0.8 # 查看版本,验证安装成功 vue --version如果之前装过Vue CLI 3或4,最好先卸载再安装5,避免命令版本混乱。卸载命令是npm uninstall -g @vue/cli。另外,如果你的日常Node版本是20(比如你要同时维护Vue3项目),我建议用nvm或fnm这类Node版本管理工具,在Vue2项目目录下切到Node 16。后面所有命令都在Node 16环境下跑。
2.1 交互模式还是预设模式
直接运行vue create my-vue2-project,会进入交互式选择。这里逐个说一下我推荐的选项:
第一步选择预设,建议选"Manually select features",不要用默认的"Default ([Vue 3] babel, eslint)",因为默认预设是Vue3。手动勾选时,我一般这样选:
- Babel:必选,编译ES6+语法;
- TypeScript:如果项目需要就选,不需要就别选,Vue2 + TS需要额外的
vue-class-component配置,复杂度会上去; - Router和Vuex:根据实际需要选。这里有个版本陷阱:Vue2项目必须用vue-router@3和vuex@3,Vue CLI会自动装对应版本,但你如果手动作业很容易装成vue-router@4或vuex@4,这在Vue2里直接跑不起来;
- CSS Pre-processors:我选"Dart Sass";
- Linter / Formatter:推荐选"ESLint + Prettier",这是当前最普及的组合;
- Unit Testing / E2E Testing:根据需求选,不需要就不勾,避免初始化时装一堆用不上的东西。
选完特性之后,还会问你要不要把这些选择存成preset,方便下次复用。团队协作场景我强烈建议存一下,并且把preset文件提交到代码仓库,后续新成员创建项目时直接选相同的preset,能保证所有子应用的基础配置完全一致。
2.2 使用preset命令行创建
如果你已经存了preset,或者想通过命令直接指定Vue版本,可以用这种方式:
vue create my-vue2-project -p my-preset-name如果不想提前存preset,也可以这样:先创建一个带预设的配置文件,比如vue.config.js(实际上preset可以放在项目根目录的preset.json里),然后通过--preset参数指定:
vue create my-vue2-project --preset ./preset.jsonpreset.json的内容大致长这样:
{ "useConfigFiles": true, "cssPreprocessor": "dart-sass", "plugins": { "@vue/cli-plugin-babel": {}, "@vue/cli-plugin-eslint": { "config": "prettier", "lintOn": ["save", "commit"] }, "@vue/cli-plugin-router": { "historyMode": true }, "@vue/cli-plugin-vuex": {} }, "vueVersion": "2" }这样创建的my-vue2-project直接就是Vue2版本,不用再进去改。这里注意useConfigFiles设为true,代表把Babel、ESLint配置都放在独立配置文件中,而不是塞进package.json,我个人更喜欢独立文件方式,团队里review配置改动时更清晰。
2.3 初始化完成后的第一轮目录检查
项目创建成功后,最好先别急着写代码,先敲一遍这些命令:
cd my-vue2-project npm install npm run serve浏览器打开localhost:8080,看到Vue2欢迎页,说明基础链路通了。如果这一步出错,大多是Node版本问题或npm源问题。npm源我建议先检查一下:
npm config get registry # 如果输出不是官方源或企业内网源,建议设置一下 npm config set registry https://registry.npmmirror.com国内网络环境用镜像源能快非常多。但有一点要提醒:镜像源和官方源切换可能会导致锁文件变化,团队协作时最好统一配置源,并把package-lock.json提交到仓库,避免每个人本地装出来的依赖树不一样。
3. npm install阶段最常见的两座大山
初始化完成并不代表万事大吉。你在npm install时碰到的报错,90%以上可以归成两类:ERESOLVE依赖树冲突,以及peerDependencies没有被正确安装导致运行时报找不到模块。
3.1 ERESOLVE野生错误与它背后的依赖树逻辑
npm 7之后对依赖冲突的检测严格了很多,老项目经常出现这种报错:
npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! While resolving: project-name@0.1.0什么意思呢?简单说就是两个包之间对某个第三方库的版本要求不一致。比如A包要求webpack@^5.0,B包要求webpack@^4.0,npm按照最严格规则无法自动调和,就会整体拒绝安装。这在Vue2老项目里尤其常见,因为很多老依赖的peerDependencies范围写得比较窄。
解决办法有两个层级:
第一层,看能不能升级或降级其中一个依赖,让它们的版本范围交叠。这是最健康的解法,但需要时间排查。第二层,如果你确认冲突的包之间实际兼容,可以使用--legacy-peer-deps或--force来绕过:
npm install --legacy-peer-deps这个参数被问得最多,我对它的态度是:能用但要用得明白。它本质上是让npm退回到npm 6的依赖解析逻辑,忽略部分peer依赖冲突。但这不代表冲突不存在,后续如果依赖间真的不兼容,运行时才会暴露。所以我只在排查后确认冲突无关紧要时才会这么做,并且会在项目的README里记录这个命令以及为什么需要它。
其实更省心的做法,是在项目的.npmrc里加上:
legacy-peer-deps=true这样团队成员执行npm install时就不用手动加参数了,行为完全一致。
3.2 cnpm引发的"半残"node_modules
我有个同事在某公司用cnpm装依赖,装完后npm run serve能跑起来,但一执行ESLint就直接报Cannot find module 'eslint-config-standard',查了一圈发现node_modules里根本没这个包。这就是cnpm早期版本的经典问题:它对peerDependencies的处理逻辑跟npm并不完全一致,经常导致部分peer依赖缺失。
我个人的经验是:Vue2项目经过多年沉淀,依赖树本来就非常复杂,peerDependencies、optionalDependencies、各种deprecated的包混在一起,用非npm官方工具安装,风险真的不小。如果你不是对cnpm的依赖处理机制有十足把握,建议一律用npm,网络慢就换镜像源,不要图方便直接上cnpm。实在要用,也得用最新版cnpm,并且装完后跑一遍npm ls --depth=0检查顶层依赖有没有缺失。
3.3 锁文件与CI环境安装
架构稳定的Vue2项目,本地和CI用的安装命令最好统一:
- 本地:
npm install,它会在已有package.json和package-lock.json基础上增量安装,速度快; - CI环境:
npm ci,它严格按照锁文件全新安装,不会做任何增量解析,能保证构建环境可复现。
如果本地和CI之间存在依赖树不一致,优先检查是否有一方用了--force或者--legacy-peer-deps。这个细节影响非常深远——很多"本地能跑、CI挂了"的诡异问题,最后都查到了依赖树不一致上。
4. 统一IDE与项目目录结构规划
Vue CLI只负责生成骨架,真正让项目变得"像企业级"的,是IDE配置、目录结构和命名约定。很多项目创建完就开写业务,结果代码量上来之后,光找文件就很费劲。
4.1 Vue2项目的IDE插件选择
VS Code是事实标准,但Vue2和Vue3的插件选择有个大坑:
- Vue3项目主流是Volar(Vue Language Features);
- Vue2项目老牌插件是Vetur。
在Vue2项目里装上Volar而不做任何配置,有概率出现模板类型检查不一致的问题。团队协作时更要统一:要么全都用Vetur,要么统一用Volar并开启其针对Vue2的支持模式。我建议Vue2项目直接用Vetur,最省心。另外,ESLint、Prettier、EditorConfig for VS Code这三个插件也是必须统一装的。配合项目根目录的.editorconfig文件,能保证不同操作系统、不同编辑器下代码格式一致:
root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true.editorconfig的优先级其实高于编辑器自身设置,只要团队成员都装了对应的IDE插件,格式基本不会乱。再加上ESLint和Prettier配置,等于做了双保险。
4.2 src目录怎么划分才不容易乱
这是我个人一直在用的Vue2项目目录结构,不算权威,但经过多个项目验证后足够清晰:
src/ ├── api/ # 接口请求 │ ├── modules/ # 按业务模块拆分 │ └── request.js # axios实例封装 ├── assets/ # 静态资源(图片、全局样式) ├── components/ # 公共组件 │ ├── base/ # 纯展示型基础组件 │ └── business/ # 业务型复合组件 ├── directives/ # 自定义指令 ├── filters/ # 全局过滤器(Vue3里已移除的概念,Vue2仍常用) ├── mixins/ # 混入逻辑 ├── router/ # 路由配置 ├── store/ # Vuex状态管理 │ ├── modules/ # 按业务模块拆分 │ └── index.js ├── styles/ # 全局样式变量与公共样式 ├── utils/ # 纯工具函数 ├── views/ # 页面级组件 ├── App.vue └── main.js这个结构最重要的原则是:把"容易变化"的模块拆分出去。比如接口请求单独放api/,页面就不会直接依赖具体的url;状态管理拆模块,就不会在store/index.js里堆几千行。组件分base和business,是为了让基础组件可以无脑复用,而业务组件知道从哪个store取数据、调哪个接口。
4.3 别名配置,让import路径不再地狱
组件层级深了之后,相对路径../../../components/...非常容易写错。Vue CLI项目可以在根目录jsconfig.json里配置路径别名,让编辑器和构建器都认:
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@api/*": ["src/api/*"], "@components/*": ["src/components/*"] } }, "exclude": ["node_modules", "dist"] }Vue CLI内部已经默认支持@指向src,但如果团队习惯用更多语义化别名,就在这个文件里定义。注意配置完需要重启VS Code才能生效。这个文件不是webpack配置,它只是给编辑器做智能提示用的;真正让Webpack认识这些别名,需要确认vue.config.js或者内部默认配置里有对应的resolve配置。好消息是Vue CLI 5内置了@别名,其余自定义别名需要在vue.config.js中配置chainWebpack才能让Webpack也识别。两者一定要保持一致。
5. 环境变量与代理配置,开发联调顺畅的关键
Vue2项目创建好之后,马上会面临一个问题:前端怎么对接后端接口?开发环境调测试环境,构建之后走生产环境域名,这个过程没配置好,不但联调效率低,还会出线上事故。
5.1 环境变量文件怎么写
Vue CLI约定根目录下的.env文件用来存环境变量,文件名后缀代表不同环境:.env.development对应开发环境,.env.production对应生产构建。变量名必须以VUE_APP_开头才会被注入到客户端代码中:
# .env.development VUE_APP_BASE_API=/api VUE_APP_ENV=development # .env.production VUE_APP_BASE_API=/prod-api VUE_APP_ENV=production代码里可以通过process.env.VUE_APP_BASE_API拿到对应值。这个机制的底层逻辑是:Vue CLI会把VUE_APP_开头的变量,在编译阶段直接替换进代码里,而不是运行时读取,所以不要在代码里动态拼接process.env变量名。
5.2 devServer代理,跨域问题的正解
前后端联调最常见的跨域问题,实际上不需要后端开CORS,也不需要前端装代理插件,直接利用Vue CLI的devServer代理就能解决:
// vue.config.js module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://192.168.1.100:3000', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }这样你在前端代码里请求/api/user/list的时候,devServer会把请求转发到http://192.168.1.100:3000/user/list。changeOrigin: true很关键,它会把请求头里的Host字段改成目标域名,很多后端服务会校验这个字段,不改容易收到403。
pathRewrite要不要写,取决于后端接口路径里是否带/api前缀。如果后端接口本身就是/api/user/list,那就不需要重写;如果后端接口是/user/list,才需要把前缀剥离。这个对应关系一定要跟后端确认清楚,我见过因为前缀没对齐而排查了一整天的情况。
5.3 记住"两个环境,三份文件"的思维
实际项目里,我习惯至少维护三份环境文件:
.env:全局基础配置,所有环境都生效;.env.development:开发环境专属;.env.production:生产构建专属。
如果有测试环境、预发布环境,可以再加.env.staging、.env.test,并用--mode参数来指定构建模式:
npm run build -- --mode staging这个mode可以直接通过process.env.NODE_ENV或自定义变量来区分。环境变量文件的命名和运行模式是一一对应的,如果文件名带点,比如.env.local,要注意它是否会被git跟踪,.env.local这种通常应该写进.gitignore,因为它往往包含本地敏感配置。
6. 代码规范与提交规范,创建项目时就要架好防线
只把项目跑通不算完成,一个能长期维护的Vue2项目,必须在初始化阶段就搭好代码规范和提交规范。这一步做不做,直接决定项目3个月之后是整洁还是灾难。
6.1 ESLint + Prettier的联动配置
Vue CLI在初始化时如果勾选了Linter,会自动生成eslintrc相关配置。我推荐选择"ESLint + Prettier"预设。这两个工具的分工要搞清楚:
- ESLint管代码质量:未使用变量、未定义变量、禁止console提交等;
- Prettier管代码格式:缩进、引号、分号、换行。
它们在个别规则上有重叠,所以需要用eslint-config-prettier把两者可能冲突的规则关掉,再用eslint-plugin-prettier把Prettier作为ESLint的一条规则运行。Vue CLI5预设里已经处理好了这些联动,你只需要注意团队里别有人单独改.prettierrc导致格式失配。
lintOnSave建议保持默认的true,这样npm run serve时ESLint会在保存时给出提示。再配合.prettierrc里统一的双引号、分号、每行最大宽度等配置,全团队的代码风格就能收敛。
6.2 Husky与lint-staged实现提交前自动检查
光有ESLint还不够,因为新人完全不看编辑器警告直接提交的情况时有发生。拦截线下移,放在git commit这个动作上,才是可靠的防线。现在的husky(10.x及以上)用法跟老版本不同,初始化命令是:
npm install -D husky lint-staged # 在package.json中定义prepare脚本 # "prepare": "husky install"执行npm install时,prepare脚本会自动运行并在项目内生成.husky目录。然后添加pre-commit钩子:
npx husky add .husky/pre-commit "npx lint-staged"lint-staged的配置写在package.json中:
{ "lint-staged": { "*.{js,vue}": ["eslint --fix", "prettier --write"], "*.{css,scss}": ["prettier --write"] } }这样每次提交,只会对暂存区里变化的文件做检查修复,不会把整个项目的代码全部检查一遍,速度很快。eslint --fix能自动修复的问题(比如分号、引号)会直接改掉,不能自动修复的会抛出错误,终止提交,逼开发者手动处理。
6.3 commitlint统一提交信息规范
提交信息一乱,后续回滚、生成变更日志都会很难受。建议再装@commitlint/cli和@commitlint/config-conventional,并用husky的commit-msg钩子校验信息格式:
npx husky add .husky/commit-msg "npx commitlint --edit $1"在根目录添加commitlint.config.js:
module.exports = { extends: ['@commitlint/config-conventional'] };支持的信息格式是type(scope?): subject,比如feat(login): 增加扫码登录功能。常用的type包括feat、fix、docs、style、refactor、perf、test、chore。这样提交历史就从散文变成了有结构的日志,后续出CHANGELOG.md或者按功能回溯提交都很方便。
6.4 把核心配置沉淀成团队脚手架
到这里你会发现,一个稳定的Vue2项目骨架,真正花心思的其实不是vue create那一步,而是后续这一整套配套配置。如果团队经常要开新项目,建议把这些配置抽到一个基础仓库或者内部脚手架工具里。我的做法是维护一个vue2-base-template私有仓库,里面已经包含了所有依赖、配置、目录结构、ESLint规则、Husky钩子、环境变量样本。新项目直接基于它复制,然后改改项目名和描述,一天的初始化工作压缩到10分钟之内。
这个模板仓库需要注意一点:不要频繁改动。每次改动都要走一次完整的创建-构建-提交流程验证,然后打tag发版本。团队成员既可以直接用最新模板,也可以基于特定tag生成项目,保证可控。
7. 最后分享几个真实的兜底经验
写到这里,基础的Vue2项目创建流程算是走完了,但我还想再补几个实战中容易踩到、且一般教程不会写的点。
第一个是关于Vue2.7的。如果你在创建项目时选了Vue2.7.x,它已经内置了composition-api的部分能力,但又没完全移植,跟Vue3的写法有些差异。团队写代码时最好明确到底是走Options API还是Composition API。在Vue2.7里混用两种风格虽然技术上允许,但review和后期维护会很痛苦。我的标准是:老项目继续用Options API,新页面如果追求代码组织可以用<script setup>,但要靠lint规则统一约束,不要随手写。
第二个是关于package.json里的engines字段。建议显式声明团队认可的Node版本范围:
{ "engines": { "node": ">=16 <19" } }这样在错误版本的Node下跑install时,npm会有明确提示,而不是等到编译时才报一堆看不懂的错误。配合volta或nvm的.nvmrc文件,成员切换目录时能自动切到合适版本,我强烈建议团队这么做。
第三个是关于首次提交。项目创建好、依赖装好、几个基础页面能跑通之后,第一件事就是做一次干净的git提交。不用提交大而全,但package-lock.json、.editorconfig、.husky这些基础设施一定要在第一次提交里就进去。很多东西拖到后面,文件名、目录结构一变动,对比工作量会成倍增加。
最后一个经验有些反直觉:Vue2项目创建完毕之后,不要着急把依赖全部升级到最新。Vue2生态已经稳定,很多包的最新版本反而不一定兼容老配置。我之前遇到过把某个工具库从^1.2.0升到^1.5.0后,ESLint规则、构建流程直接崩掉的情况,最后回滚版本才恢复。Vue2项目的原则是"能不动就不动",升级之前一定要先看changelog和peerDependencies变化,同时在分支上先验证一遍npm run build再合入主分支。这在多人协作的长期项目里,是避免半夜接到"线上挂了"电话的最好保障。