
一看到“从 0 到 1 搭建 Vue 项目”这个标题很多刚转前端或者第一次接触工程化的人会先慌一下项目到底是怎么“变”出来的为什么我照着教程敲命令总是卡在一堆报错里其实把 Vue 项目从零搭起来这件事本质上是两件事一是搞清楚脚手架帮你干了什么二是把你自己的配置整理顺。这篇就按我这两年实际带新人、自己也重装过 N 多次环境的经验从头到尾把“Vue 项目搭建”拆开讲清楚适合所有刚入门 Vue 或者被工程化配置折磨过的人参考。1. 从 0 到 1 的起点先搞清楚“搭 Vue 项目”到底在搭什么1.1 脚手架帮我们做了哪些重活很多人会把“搭项目”理解为从零写 webpack 配置其实在 2025 年这个时间点官方推荐的路线早就不是亲手去调 loader 和 plugin 了。Vue 官方提供的create-vue脚手架底层是 Vite它把开发服务器、热更新、依赖预构建、CSS 处理、静态资源处理这些脏活累活全包了。你要做的只是回答几个交互式问题然后得到一个可以直接改代码跑起来的工程骨架。为什么我更推荐create-vue而不是老的vue-cli原因很直接vue-cli基于 webpack冷启动慢、配置层厚、现在基本进入维护状态而create-vue基于 Vite秒级启动后续如果想调整构建行为只需要改vite.config.js/ts一个文件。说白了脚手架的价值不是帮你写业务代码而是帮你把“能跑起来”这件事变成一分钟内完成的操作。这里的“0”其实不是空文件夹而是一台干净的开发机。所以第一步不是打开编辑器而是先确认环境。1.2 环境准备Node.js 和包管理器怎么选Vite 5 以上要求 Node.js 18 以上Vite 7 则要求 20.19 以上。我现在固定用 Node 20 LTS编译 Vite、Vitest、Rollup 生态都稳定没必要追最新大版本。检查方式很简单node -v npm -v如果你装了 nvm建议在自己的机器上保留 Node 18 和 Node 20 两个版本方便切换。团队项目如果锁了 engines 字段npm install时会直接提示当前 Node 版本不匹配这时候切换版本比硬装依赖省事得多。包管理器我推荐 pnpm当然这只是个人偏好。pnpm 的依赖是硬链接式存储装同一个依赖只在全局磁盘上放一份几百个项目的机器上差别非常明显。如果你当前没装一行命令搞定npm i -g pnpm安装完跑pnpm --version确认版本。在国内环境如果遇到 npm 官方源安装缓慢可以临时切 npmmirror 镜像源装完再切回来或者直接配.npmrc。这一步属于环境习惯不算项目本身但耽误的时间全在这。2. 实战记录用 create-vue 一步步初始化项目2.1 创建命令与交互式选项如何选择环境准备好之后真正“从 0 到 1”的操作其实就一条命令npm create vuelatest my-vue-app如果项目名不确定也可以先只执行npm create vuelatest它会在交互式流程里挨个问你。整个过程是逐项确认我刚接触的时候每次都会在选项这里犹豫很久这里给一个我实际验证过的默认推荐交互提示推荐选项说明项目名称符合 npm 命名规范不能有大写字母和空格TypeScript视团队情况个人项目建议直接选 Yes早适应JSX 支持NoVue 用模板语法为主不需要 JSXVue RouterYes单页应用基本必选PiniaYes官方状态管理方案简单直观Vitest看需求想写单测就选后面也能补ESLintYes代码规范从第一天就养成PrettierYes配合 ESLint 统一格式这里最容易被忽略的是 TypeScript 选项。很多人担心 TS 增加学习成本但实际上create-vue生成的 TS 工程默认配置已经很温和而且数据流、路由参数的类型提示能帮你少踩一堆低级错误。新手如果实在不想在初期碰类型选 No 生成纯 JS 版本也行后期迁移成本没有想象中那么高。选完之后脚手架会创建一个新目录还会提示你接下来执行的命令通常是cd my-vue-app pnpm install pnpm dev这三个步骤就是项目“跑起来”的最小闭环。2.2 安装依赖、启动开发服务器执行pnpm install之后node_modules 会出现在项目根目录。这里我想强调一个习惯不要手动把node_modules拷来拷去或者传给同事它是可再生的产物只要有package.json和锁文件就能安装回来。接着启动开发服务器pnpm dev默认情况下 Vite 会开在http://localhost:5173。第一次看到这个地址能正常出页面意味着从 0 到 1 的基建已经完成。如果 5173 端口被占用Vite 会自动尝试下一个端口所以不用担心。想固定端口的话在vite.config.js/ts里加server: { port: 5173 }就行。// vite.config.ts export default defineConfig({ plugins: [vue()], server: { port: 5173, open: true // 启动时自动开浏览器 } })这个open配置虽然只是锦上添花但每次不用自己手动输地址实测下来团队内部都挺喜欢。2.3 目录结构逐层拆解与脚本含义脚手架生成的项目结构非常干净别把这里当成黑盒逐层认识一下会对后面所有开发都顺手很多my-vue-app ├── public/ # 静态资源构建时原样拷贝 ├── src/ │ ├── assets/ # 需要打包处理的资源 │ ├── components/ # 通用组件 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia 状态 │ ├── views/ # 页面级组件 │ ├── App.vue # 根组件 │ └── main.ts # 入口 ├── index.html # Vite 的 HTML 模板 ├── package.json └── vite.config.tspublic和src/assets的区别经常有人问放进public的文件会被原封不动拷到dist目录引用时写绝对路径放进src/assets的文件会经过 Vite 打包、压缩、指纹命名引用时要通过 import 或相对路径。我个人的经验是真正的静态文件比如 favicon、外部页面用的图片放public组件内会用到的资源走assets。package.json里的 scripts 也是一定要读懂的{ scripts: { dev: vite, build: vite build, preview: vite preview, lint: eslint . --fix } }build是生产构建产物输出到dist目录preview是把构建产物在本地起一个静态服务来检查这个命令我几乎每次上线前都会跑一遍。3. 核心配置与工程化细节路由、样式、环境变量3.1 入口文件与路由挂载逻辑项目跑起来之后第一件要弄清的事就是页面是怎么被挂载上去的。src/main.ts入口非常直接import { createApp } from vue import { createPinia } from pinia import App from ./App.vue import router from ./router const app createApp(App) app.use(createPinia()) app.use(router) app.mount(#app)index.html里有一个div idapp/divApp.vue里则包含路由占位符template RouterView / /template所有页面组件都由router决定渲染位置。src/router/index.ts里配置路由表常用写法有两种静态 import 和动态 import。动态 import 的意思是页面组件按需加载首屏只加载当前路由需要的代码代码体积会明显更小。import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: HomeView }, { path: /user/:id, name: user, component: () import(../views/UserView.vue) } ] }) export default router我在项目里最常用到的就是动态路由参数。比如用户详情页/user/123里的123在页面里通过useRoute()拿到import { useRoute } from vue-router const route useRoute() const userId route.params.id搭建阶段只要知道这个机制就够了。至于更复杂的动态路由权限控制、路由守卫可以等项目真正需要时再加别在最开始就把路由架构设计得过重。3.2 样式方案scoped 与全局样式如何处理样式冲突是新手遇到最多的麻烦之一。create-vue生成的组件里style标签默认带scoped属性它的原理是给组件内所有需要考量的 DOM 元素加上一个>style scoped .card { border: 1px solid #ddd; } /style同样的.card类名写在两个不同的 scoped 组件里不会互相干扰。如果你确实想在一个 scoped 样式里覆盖全局样式比如第三方组件库的内部类名可以用:global()包一层style scoped .card :global(.ant-table) { background: #fff; } /style还有两个值得提前养成的习惯。第一全站都会用的基础样式放在src/assets或者单独建一个styles目录里作为全局 CSS 引入第二如果你想用 SCSS、Less 之类的预处理器create-vue默认不会安装编译器需要手动装上pnpm add -D sass装完就能直接写style scoped langscssVite 会自动编译不用改任何配置。3.3 多环境变量与构建产物一个项目只用一个环境配置基本做不成事。开发环境调接口、测试环境验证、生产环境上线用的接口地址大概率不同。Vite 约定以VITE_开头的变量才会暴露给前端代码你可以在项目根目录建几个文件# .env.development VITE_API_BASE/api # .env.production VITE_API_BASEhttps://your-api.example.com在代码里直接通过import.meta.env.VITE_API_BASE读取import axios from axios const request axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 10000 })这里有个细节.env.development在pnpm dev时生效.env.production在pnpm build时生效。vite build生成的dist目录就是可以直接部署的产物可以用pnpm preview在本地模拟线上环境检查一遍。我不建议把敏感密钥放到VITE_前缀的环境变量里因为这类变量会随着打包产物暴露给浏览器。真正的服务端密钥应该放到后端或者构建服务器的环境配置里前端只保留它需要的公共信息。4. 高频问题排查与典型集成场景实录4.1 我踩过的高频编译报错速查表搭项目的阶段报错比业务代码还多这是正常现象。下面这些是我在带人时反复遇到的类型整理成速查表给你报错信息原因处理方式Failed to load tsconfig vue/tsconfig/tsconfig.web.jsonvue/tsconfig没安装或版本与脚手架不一致pnpm add -D vue/tsconfig再检查tsconfig.*里的 extends 路径Syntax Error: SassError缺少 sass 编译器pnpm add -D sassModule not found: ./App.vue路径大小写或扩展名写错确认文件路径和大小写Vue 组件import时建议带上扩展名Cannot find module vue在错误的目录下运行命令或依赖没装全确认在项目根目录重新执行pnpm installPort 5173 is already in use端口被其他进程占用改server.port或者关掉占用进程还有一个很多人问的问题Vue 项目在 Edge 浏览器里出现页面旧内容不刷新、HMR 不生效的情况我排查过几次多数不是 Vue 本身的问题而是浏览器缓存或者油猴脚本、广告拦截类扩展拦截了本地 WebSocket。处理方式是先禁用浏览器扩展试一下再清一下站点数据基本能解决。4.2 项目里常被问到的集成场景项目基础搭好后大概率会碰到几个高频集成需求我把最近被问得最多的两个写在这里。第一个是 m3u8 视频流播放。很多场景下产品要播放视频流直接放video标签在部分浏览器里不兼容这时可以用hls.jspnpm add hls.jstemplate video refvideoEl controls playsinline/video /template script setup import { ref, onMounted } from vue import Hls from hls.js const videoEl ref(null) onMounted(() { const video videoEl.value const url https://example.com/live/demo.m3u8 if (Hls.isSupported()) { const hls new Hls() hls.loadSource(url) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () video.play()) } else if (video.canPlayType(application/vnd.apple.mpegurl)) { video.src url } }) /script第二个是 ECharts 图表。想在 Vue 里画两个柱状统计图最省事的方式是直接封装一个图表组件template div refchartEl styleheight: 300px/div /template script setup import { ref, onMounted, onBeforeUnmount } from vue import * as echarts from echarts const chartEl ref(null) let chart onMounted(() { chart echarts.init(chartEl.value) chart.setOption({ xAxis: { type: category, data: [订单数, 销售额] }, yAxis: { type: value }, series: [{ type: bar, data: [1200, 8600] }] }) window.addEventListener(resize, chart.resize) }) onBeforeUnmount(() { window.removeEventListener(resize, chart.resize) chart.dispose() }) /script注意 ECharts 实例一定要在组件销毁时dispose否则多次进出页面会有内存泄漏。还有一个被问得很频繁的场景Vue 项目打包后放进 Spring Boot 里。做法很简单执行pnpm build得到dist目录把它复制到 Spring Boot 的src/main/resources/static下然后让后端接口走另一个路径前缀即可。如果前端的路由是 history 模式还要在后端加一个忘掉favicon的路径转发确保用户直接访问/user/123时能回到index.html。这个配置经常被漏掉导致刷新页面变成 404。4.3 把源码交付给别人的正确姿势很多人问“Vue 项目源码怎么发给别人”最好的方式不是压缩包拖来拖去而是走 Git 仓库。如果只是临时协作压缩包也必须排除掉node_modules和dist这两个目录都可以通过pnpm install和pnpm build重新生成。发之前顺手检查.gitignore是否包含这两项别让几百 MB 的依赖目录淋到同事的硬盘上。另外接手别人的项目时第一件事也不是开编辑器读代码而是先看README和package.json弄清楚它的启动命令、Node 版本要求、环境变量文件有哪些。我见过太多人上来就pnpm install然后报错结果只是漏看了环境变量示范文件。现在create-vue项目里没有手写 README建议你自己拿到项目后先补一份三分钟的事后面能省三小时。搭 Vue 项目说到底没有玄学就是把环境、脚手架、目录结构、路由和样式这几件事串起来。我个人实际做下来的体会是第一遍跟着默认选项走通第二遍再回来改配置比一开始就追求完美方案要快得多。卡住了就先查报错关键字再看锁文件里有没有版本冲突剩下的大部分问题其实都是依赖没装对或路径写错了。下次再听到“从 0 到 1 搭建 Vue 项目”这句话希望你已经能笑着说就这点事。