Vue2工程脚手架搭建指南:Vue CLI创建项目与配置详解
1. 为什么还在用 Vue2脚手架选型背后的考量1.1 Vue2 的处境与现实选择先说说我在实际工作中遇到的一个很典型的场景。某天接到一个需求要给一个企业的旧系统做模块扩展打开对方的代码仓库一看Vue2 Vue CLI Webpack 全家桶用了几年了跑得稳稳当当。这时候我面临两种选择把整套东西推倒重来升级到 Vue3或者沿用 Vue2 技术栈继续叠加新模块。推倒重来的成本是非常实际的。业务逻辑要重写、依赖库要逐个做兼容性排查、组件库要换、团队成员要重新适应还有一整套测试用例要跟着改。而沿用 Vue2 创建新工程意味着我可以复用团队已有的技能积累、现成的通用组件、沉淀多年的业务代码新模块可以直接融进老系统的运行节奏里。这就是很多团队至今仍在用脚手架创建 Vue2 工程的根本原因不是不知道 Vue3 更好而是业务连续性、团队熟悉度、历史资产复用这几项加起来性价比远超迁移成本。另外从生态角度来看Vue2 时代产出了大量经过生产环境检验的组件库、工具链和解决方案。Element UI、Vant 2、各类图表库的 Vue2 版本在很多企业系统里仍然在继续维护和使用。只要项目本身还在迭代基于脚手架快速搭建 Vue2 工程就会一直是一个实际需求。这不是技术上的退步而是工程视角下的理性选择。1.2 为什么不用 Vite 也不用手动搭我见过不少新手直接手搓一套 Vue2 工程Webpack 配置写了几百行loader 装了一堆最后跑起来发现样式加载不对、热更新失效、打包体积大得离谱。手动搭建不是不行而是可重复性太差。今天这个项目这样配下个项目换个目录结构又要重新调每次都在踩同样的一堆坑。脚手架的价值恰好在于把这一堆重复劳动封装起来。Vue CLI 在 Vue2 时代完成了三件事把 Webpack、Babel、ESLint 等工具的配置规范化让新人不用理解底层也能把工程跑起来把模式化的选择流程固化下来不同项目之间的工程规范高度一致维护成本大幅降低把插件机制做成了生态需要什么能力往 configuration 里加就行。也有人会问都什么时候了还用 Vue CLIVite 不是更快吗Vite 的定位是面向现代浏览器和 Vue3 的构建工具对 Vue2 的支持一直不是它的主路径。Vue2 项目的浏览器兼容要求通常更高需要兼容老版本的 Chrome、Safari 甚至 IE 环境Vite 默认的 ESM 加载方式在这些环境里就会出问题。而 Vue CLI 基于 Webpack 的完整打包链路配合 Babel 的 polyfill 策略在这方面是经过大量生产环境考验的。所以基于 Vue CLI 创建 Vue2 工程不是我守旧而是它本来就是 Vue2 的最稳路径。2. 环境准备与创建 Vue2 工程的完整步骤2.1 Node 版本是第一个隐藏的门槛很多人拿到 Vue CLI 就直接全局安装然后vue create一气呵成结果在依赖安装环节就报一堆错。这里最容易被忽略的是 Node 版本兼容性。Vue CLI 5 的官方要求是 Node 12.13 以上但实际踩下来Node 14 和 Node 16 是运行最舒服的区间。Node 17 以上一些依赖的编译链可能就会出问题尤其是 node-sass 这个老冤家版本对不上就报安装失败。我现在的习惯是先用node -v看当前的 Node 版本。如果本机版本偏高或者偏低用 nvm 做版本切换最省事。推荐直接切到 Node 14.21.3 或 Node 16.20.2 这两个长期维护版本。不要用最新版去挑战的兼容性这不是激进不激进的问题是为了让整个开发链路少一些不可控因素。提示如果本机还没有 nvm建议先装好。nvm 常出现的问题有两个一个是切换版本后全局包失效这是因为命令所在目录没进入当前版本的全局 node_modules另一个是 Win 系统下 nvm 安装后无法识别命令通常把路径加到系统环境变量就行。装好 nvm 后执行nvm install 14.21.3和nvm use 14.21.3就能切到目标版本。2.2 全局安装 Vue CLI 的正确姿势Node 版本就绪之后安装 Vue CLI 这一环反而简单。执行npm install -g vue/cli注意包名是vue/cli不是vue-cli。vue-cli 是 Vue2 早期的全局脚手架包现在已经不维护了如果你搜到老教程让你装vue-cli那是历史遗留内容会装出完全不同的命令行为。安装完成后用vue --version验证能输出版本号就说明装好了。正常情况下输出的是 5.x 版本比如 5.0.8。有个细节值得注意全局安装之后vue命令默认是不带 sudo 或管理员权限的。如果你在 Linux 或 macOS 下遇到了权限报错不要直接加 sudo 绕过更合理的做法是把 npm 的全局安装目录调整到当前用户有权限的路径。Windows 下如果全局命令不生效检查一下 npm 的前缀目录有没有被加入系统 PATH。2.3 vue create 交互选项逐个拆解执行vue create 项目名后会进入一个交互式的选项流程。我第一次用的时候对着屏幕发了一会儿呆因为每一步选什么会影响后续整个工程结构。这里把我实际操作下来最稳的选择路径写出来第一个问题是Please pick a preset这里我从来不用默认预设选Manually select features原因很简单默认预设只有 Babel 和 ESLint连 Router 和 Vuex 都没有后面加装又要折腾一遍。手动选择一步到位反而省事。进入特性选项后我通常按这样的组合选择Babel必选。Vue2 源码默认是 ES Module 的写法目标环境不一定支持Babel 负责把语法转译到兼容版本。Router如果新工程是单页应用这个核心路由能力要提前选上省得后面再手写 Router 配置。Vuex状态管理。小型页面可以不用但业务模块一多、组件层级一深全局状态共享迟早要上。建议创建时就直接加上。CSS Pre-processors选 Sass/SCSS。写嵌套样式、定义全局变量都比纯 CSS 顺手太多。Linter / Formatter选 ESLint Prettier。团队协作时代码风格统一问题靠这个兜底。Unit Testing看情况。如果是给核心业务系统扩展模块直接选上 Jest后面补测试的成本比现在高好几倍。如果只是出个 Demo 或者活动页可以不选减轻初始模板负担。选完特性后Vue CLI 会继续问细节比如Use class-style component syntax?选 No。类写法在 Vue2 里需要额外装饰器和配套依赖不够纯粹普通选项式写法足够清晰。Use Babel alongside TypeScript?如果不选 TS 就不涉及。如果团队有 TS 需求这个选 Yes但要知道 Vue2 TS 组合的维护成本交换出来更复杂的类型体操。Pick a linter / formatter config我一般选ESLint Standard config。Standard 风格比较收敛要求没有分号、两个空格缩进代码整体观感干净。Pick additional lint features一般可以跳过默认就行。Where do you prefer placing config for Babel, ESLint, etc.?选In dedicated config files。独立配置文件比挤在 package.json 里更清晰改动也不容易互相干扰。Save this as a preset for future projects?如果这个组合是符合团队规范的选 Yes 并取个名字比如公司名缩写。下次再创建工程就一行命名直接完成。全部选完以后Vue CLI 会先跑npm install。这里有一个大概率会踩的坑如果公司的网络环境导致默认 npm 源连接慢依赖安装会卡很久。我一般在安装前先执行npm config set registry https://registry.npmmirror.com换成国内镜像源以后速度会快非常多。2.4 工程跑起来后的第一次验证依赖装完Vue CLI 在最后会提示你执行启动命令。我习惯按这个顺序验证工程是否正常cd 项目目录 npm run serve看到终端输出App running at: Local: http://localhost:8080/这行日志后先别急着关终端。打开浏览器访问这个地址如果能看到 Vue2 默认的欢迎页脚手架创建的第一步就成功了。接着做两个小操作确认工程完整性按一下热更新的开关改一下 App.vue 里的标题文本看页面是否自动刷新再在 DevTools 的 Vue 面板里确认当前应用实例已经正常挂载。注意如果npm run serve启动后浏览器访问失败优先检查是否端口被占用。Vue CLI 默认端口是 8080如果被别的进程占了终端会提示你选择其他端口按提示输入一个空闲端口即可。这个我在第五节会详细展开。这一步的核心目的不是看欢迎页而是确认整个链条——Node、npm、Vue CLI、Webpack 编译、浏览器运行——已经全部打通。链条上任何一个环节断了后续写业务代码时都会加倍返工。3. 工程目录结构与关键文件配置逐项说透3.1 标准 Vue2 工程目录究竟长什么样创建完的工程会自动生成一套标准目录我刚开始用的时候总觉得这结构没什么好研究的后来帮别人排查问题时才发现很多人其实没搞懂每个目录是干嘛的。这里把关键目录逐个梳理一遍。根目录下的public文件夹放静态资源和不参与 webpack 编译的全局文件。public/index.html是最终的页面入口模板SPA 单页应用整个生命周期只有这一个 HTML 页面。public里的文件会被直接复制到打包产物的根目录所以图片、静态脚本、第三方插件的初始化文件放这里都能直接通过相对路径引用。src目录才是写代码的地方。src/main.js是整个应用的入口文件Vue 应用从它开始创建实例并挂载到 HTML 页面上。src/App.vue是根组件整个组件树从这里往外扩展。src/components放公共组件src/router存放路由配置文件src/store放 Vuex 相关模块src/assets放需要被 webpack 加工的静态资源src/views或src/pages放页面级别的组件比如首页、列表页、详情页。根目录里的babel.config.js管 Babel 转译规则.eslintrc.js是 ESLint 的配置文件.prettierrc定义格式化规范vue.config.js是对 Vue CLI 的构建行为做定制的入口。有个非常实用的小经验src目录下的views和components不要混用views 只放路由直接加载的页面组件components 只放被页面引用的公共组件。这个习惯能在项目文件涨到几十个上百个时有效降低查找成本。3.2 main.js 入口文件是如何启动整个应用的src/main.js默认内容比较短import Vue from vue import App from ./App.vue import router from ./router import store from ./store Vue.config.productionTip false new Vue({ router, store, render: h h(App) }).$mount(#app)这段代码有几个关键点值得展开。import Vue from vue是从 node_modules 里引入 Vue 核心库Vue2 这里默认是完整版还是运行时版本取决于 webpack 的 alias 配置脚手架默认给的是运行时版本所以模板不能直接挂到实例的template选项里而是要使用render函数。上面代码里的render: h h(App)本质上是把 App 组件作为根组件渲染出来。router和store这两个属性会被挂到根实例上之后所有子组件里都能通过this.$router和this.$store访问到它们这就是为什么不必在每个组件里手动 import router 和 store 也能使用路由和状态管理的原因。$mount(#app)干的事情就是把整个应用实例挂载到public/index.html里的div idapp/div元素上。如果这个 div 不存在挂载就会失败页面一片空白。很多人遇到白屏问题第一个排查点往往不是这里而是div idapp被误删了或者改名了。我刚接触 Vue2 时也犯过迷糊觉得模板写在.vue文件里不是能跑吗何必非要用 render。后来理解了main.js里没有 template 属性所以也无法用 template 选项渲染根组件脚手架走的是 render 函数这个分支这是运行时版 Vue 的标准做法。这一环理解透了后面排查“页面空白却不报错”这类问题时思路会快很多。3.3 组件注册与 App.vue 的角色定位App.vue是根组件它的 template 部分就是整个页面的骨架。普通组件的注册通常分为局部注册和全局注册。局部注册在script里引入组件文件然后在components: {}里声明每个用到它的组件都要声明一次。全局注册则是Vue.component(组件名, 组件对象)在main.js里执行一次之后所有组件都能直接用。实际开发中我的偏好是公共组件和业务通用组件做成全局注册一次性引入省去在几十个页面里反复 import 和注册的重复劳动页面内的局部区块组件则使用局部注册保持依赖清晰。这两种方式并不冲突关键是要在项目里约定好边界。App.vue 里还有个容易忽略的地方就是它在script中引入的全局公共样式像是字体、reset CSS、全局变量等一般通过import ../assets/style/base.scss在 App.vue 或 main.js 里引一次之后所有组件的 scoped 样式和公共变量就能正常使用。这个“全局样式入口”的放置位置一定要在工程早期就想清楚否则样式文件散落在各处后期改一个主题色要满项目找选择器。组件内部的样式推荐使用style scopedscoped 会给当前组件的每个选择器加上一个唯一的属性标识让样式只对当前组件内的元素生效。这个机制在多人协作时能避免大量意外的样式串扰。不写 scoped 的全局样式要尽量集中管理不要散落在组件里。3.4 vue.config.js 里最值得提前配好的几个选项vue.config.js是 Vue CLI 工程里最重要且最容易忽略的配置文件。还没有这个文件时可以手动创建创建后会跟package.json一起被 Vue CLI 读取帮你覆盖 webpack 默认配置。我几乎是创建工程后第一时间就会写的配置长这样const port 8080 module.exports { publicPath: ./, outputDir: dist, assetsDir: static, devServer: { port: port, proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } }publicPath是部署时要格外注意的一个参数。默认值是/也就是说打包后的资源路径都是绝对路径。如果你的应用要被部署到某个子路径比如https://example.com/subapp/这里就得设置成./或具体的子路径否则页面打开后 JS 和 CSS 全部 404。我见过不止一次生产环境白屏的问题最后定位到就是publicPath忘了改。outputDir控制打包输出目录默认是dist。这个不是非改不可但有部署要求的项目建议提前约定好。devServer.proxy是开发环境跨域问题的最优解法。前端的开发服务器和后端的接口服务器通常不是同一个域名如果直接发请求会因为跨域被浏览器拦截。配置 proxy 后前端所有/api开头的请求都被转发到后端的localhost:8000同时保持浏览器的源不变跨域问题直接消失。changeOrigin 必须在 true 的情况下才能让后端认为请求来自匹配的源。提示配置了 proxy 后代码里的 HTTP 请求不要写完整的http://localhost:8000/...绝对地址而是要写成/api/...这种相对路径。否则 proxy 的匹配规则不会生效等于白配。4. 开发调试中的常见问题排查与避坑记录4.1 Node 版本过高引发的编译问题与 OOM现代 Node 版本迭代非常快Node 18 和 Node 20 在某些依赖编译链上已经出现过各种兼容问题。Vue CLI 工程执行业务代码编译时如果遇到以下报错大概率就是 Node 版本环境的锅JavaScript heap out of memoryError: EACCES: permission deniedModule build failed: TypeError: this.getOptions is not a functionnode-sass或fibers相关的编译失败第一个问题JavaScript heap out of memory是常见的一种。我在换电脑后第一次跑旧 Vue2 工程时踩到过。Vue CLI 通过 webpack 编译时如果源码量达到一定规模默认的 Node 内存限制可能不够用。解决方案有两种一种是在package.json的启动脚本里加上内存参数{ scripts: { serve: node --max_old_space_size4096 node_modules/vue/cli-service/bin/vue-cli-service serve } }另一种更省事直接用 nvm 切回 Node 14 或 Node 16。依赖安装阶段的报错要留意ERR_OSSL_EVP_UNSUPPORTED这一类Node 17 及以上版本在安装某些老依赖时会因为 OpenSSL 版本问题直接失败。这个问题改用 Node 14 几乎没有遇到过。4.2 端口被占用时如何快速切换vue create创建的工程默认端口是 8080。如果你同时开了几个前端工程或者本地有别的服务在跑第二次运行时会提示端口被占用。Vue CLI 的实际行为是自动询问你是否切换到另一个端口通常是在终端里冒出一个提示输入 y 确认后自动换端口。如果不想每次都被问可以在启动脚本里指定固定端口{ scripts: { serve: vue-cli-service serve --port 8090 } }或者在vue.config.js的 devServer 配置里写死端口。要注意的是端口换掉后如果代码或浏览器缓存里有写死 8080 的地址引用比如如果你把某个回调地址写死了端口就会一直回调到错误的端口上页面功能异常但不报明显错误。排查时先检查地址栏的端口号和终端输出的端口号是否一致。4.3 ESLint 规则卡住编译如何处理Vue CLI 默认启用 ESLint 在开发模式下做校验代码风格不合规时页面会渲染一层全屏报错。很多新手会被这个套路整到心态崩。规则卡住编译时不需要急着把所有文件改成符合规则的样子先理解这个机制ESLint 报错是分 error 和 warning 的error 会阻止编译warning 只是提示。所以先处理 error 级别的比如未定义的变量、缺少分号这类硬性问题warning 级别的可以先放着不影响功能。如果团队对某些风格规则有不同看法可以通过.eslintrc.js的规则覆盖来关闭。举两个我实际用到的例子module.exports { rules: { vue/max-attributes-per-line: off, space-before-function-paren: off } }这两个规则一个是限制单行标签属性数量一个是要求函数括号前加空格都是比较常见到想关掉的规则。关掉后记得重启 dev server 才生效。注意不要一上来就把所有规则全关掉那等于放弃了 ESLint 防止低级错误的价值。更好的做法是让 ESLint 在提交前把明显不合规的代码揪出来而不是让它在每轮编译时把新人吓跑。4.4 npm 安装依赖卡顿与依赖冲突的处理新手刚接触 Vue2 脚手架时最容易在依赖安装阶段卡住原因五花八门但八成是网络问题。我现在的做法是全局把 registry 切到国内镜像源并且固定下来npm config set registry https://registry.npmmirror.com换了镜像源之后再执行npm install速度往往从几分钟压缩到几十秒。还有个小技巧如果在安装阶段冒出来依赖冲突尤其是版本冲突提示时不要盲目删锁文件。优先用npm install而不是npm ci因为npm ci需要package-lock.json完全匹配任何版本不一致都会直接失败。本地调试时npm install的弹性要大得多。另外遇到node-sass报安装失败的问题时不要执着于让老版本 node-sass 在当前 Node 版本下重新编译直接把 node-sass 替换成sass和sass-loader往往更省事。Vue2 工程对 sass 的承载能力完全没有问题替换后重新安装依赖编译速度还会更快。5. 从工程创建到业务开发的衔接建议脚手架创建完工程还只是万里长征的第一步。我见过太多人把工程搭起来了但到写业务代码时又开始手忙脚乱根源在于没有把工程的规范和边界提前想好。这里分享几个我实际操作中沉淀下来的习惯。第一件事创建完工程后先写一个最小可用的业务闭环。比如放一个包含路由跳转、组件通信、请求接口的最小页面。这个闭环不需要太多代码但它能逼着你把 vue.config.js 里的 proxy、路由的懒加载、公共组件的注册方式这些基础能力都用一遍相当于给工程做冒烟测试。等你将来要扩展大业务时这些基础能力都已经验证过可用不需要临时翻文档。第二件事尽早约定目录间的前后端接口规范。我在 vue.config.js 里写好了/api的代理后会立刻和后端对齐接口的返回结构比如统一{ code, message, data }。前端的请求封装、拦截器逻辑、错误提示组件都能基于这个统一的返回结构做出来。工程早期把接口约定好后面写业务页面时几乎不用为接口层的脏活分心。第三件事不要太依赖脚手架默认模板的演示代码。创建完成后App.vue 里的默认样式和 logo 图片不是业务要用到的东西尽早清理掉。默认模板的定位只是展示工程可用而不是给你保留一套无用的样式和结构。6. 实操总结与个人心得基于脚手架创建 Vue2 工程这件事看着是几行命令的事实际展开后每一层都有值得打磨的细节。我这几年用下来最大的感受是脚手架本质上是在扮演“工程规范执行者”的角色——它把你手动配置容易出错的部分收拢起来给你一个可重复、可持续维护的起点。无论你未来是继续维护 Vue2 老项目还是准备将来迁移 Vue3从脚手架开始建立一个能跑通最小闭环的工程都是一切后续工作的地基。最后再分享一个小技巧创建完工程后把项目里所有配置文件的注释都加一遍自己的理解尤其是 vue.config.js、babel.config.js、.eslintrc.js。这个习惯一开始会花一点时间但它能帮你建立起对工程细节的掌控感。我见过很多开发者代码能力很强但工程配置一问三不知卡在环境问题上的时间比写业务代码还多。这个状态真要避免。