(第一章)从零搭建 Vue3 + Vite 项目,这次终于整明白了!
写在前面:这是我花了一周时间整理的教程,把自己踩过的坑都写出来了。如果你也在学 Vue3,希望能帮到你。后续会推出一系列的vue3相关的内容。
先说说为啥推荐用 Vue3 + Vite
我之前也是 Vue2 的死忠粉,Vue3 刚出来的时候觉得没必要升级。但真正用过之后,是真香,现在vue最新已经到了3.5的版本,很多写法都有更新,咱们也得跟上版本不是!
简单对比一下:
Vue3 比 Vue2 好在哪?
- 性能确实提升了不少,我测试过,大概快了有 2 倍
- 打包体积小了很多,从 30 多 KB 降到 10 KB 左右
- TypeScript 支持好太多了,不用再纠结类型问题
- Composition API 写起来是真舒服,特别是逻辑复用
Vite 比 Webpack 快在哪?
我给你看看我项目的真实数据(一个大概 50 个页面的管理后台):
| 对比项 | 用 Webpack | 用 Vite | 我的感受 |
|---|---|---|---|
| 启动速度 | 45 秒左右 | 1 秒多 | 简直了! |
| 热更新 | 要等 4-5 秒 | 基本秒更新 | 改代码不用等了 |
| 打包速度 | 3 分钟+ | 20 多秒 | 提测快多了 |
说实话,用过 Vite 之后,我是真回不去 Webpack 了🤣
这篇文章能帮你解决什么?
跟着我这篇文章做下来,你会得到:
1. 一套完整的项目模板
不用每次都从头开始配置了,直接复制就能用。我现在做新项目就是用这个模板,能省不少时间。
2. 代码规范配置
ESLint + Prettier + Husky 这一套,配好之后团队开发不会再因为代码风格吵架了(真事儿😂)
3. 一些常见问题的解决办法
我踩过的坑都写在这了,比如路径别名不生效、热更新有问题这些,都有解决方案。
4. 能直接用的代码
Axios 封装、环境变量配置这些,直接复制粘贴就行,不用再自己写了。
废话不多说,开始吧!
🛠️ 一、环境准备
1.1 检查 Node.js 版本
Vite 需要 Node.js 版本 >= 14.18+,推荐使用 16+ 或 18+ 版本。
# 检查 Node.js 版本
node -v
# 检查 npm 版本
npm -v
如果版本过低,请前往 Node.js官网 下载最新LTS版本。
我使用的node版本是v20。

1.2 配置国内镜像源(可选但推荐)
如果你在国内,强烈建议配置淘宝镜像源,加速依赖安装:
# 设置淘宝镜像源
npm config set registry https://registry.npmmirror.com
# 验证是否设置成功
npm config get registry
或者使用 pnpm(推荐):
# 安装 pnpm
npm install -g pnpm
# pnpm 设置镜像源
pnpm config set registry https://registry.npmmirror.com
💡 为什么推荐 pnpm?
- 更快的安装速度
- 节省磁盘空间(硬链接机制)
- 更严格的依赖管理
开始搭建项目
第一步:创建项目
打开终端,运行下面的命令:
# 用 pnpm 创建(我习惯用这个,快)
pnpm create vite my-vue3-project
# 如果你用 npm,就这样
npm create vite@latest my-vue3-project
运行之后会让你选择框架和语言:
? Select a framework: › Vue # 选 Vue
? Select a variant: › TypeScript # 建议选 TypeScript,后面会省很多事
💡 关于 TypeScript:
我知道有些人觉得 TypeScript 麻烦,但真的建议用。项目大了之后,没有类型提示真的很痛苦。而且现在 Vue3 对 TS 支持很好,用起来不会有太多负担。

第二步:启动项目
# 进入项目
cd my-vue3-project
# 安装依赖(这步可能要等一会)
pnpm install
# 启动!
pnpm dev
浏览器打开 http://localhost:5173,能看到 Vue 的欢迎页面就成功了!
看看项目结构
创建好的项目长这样:
my-vue3-project/
├── public/ # 放静态资源的,比如图标啥的
├── src/
│ ├── assets/ # 图片、样式这些
│ ├── components/ # 组件放这
│ ├── App.vue # 主组件
│ ├── main.ts # 入口文件(最重要)
│ └── style.css
├── index.html # HTML 模板
├── package.json
├── vite.config.ts # Vite 的配置文件
└── ...
简单看几个关键文件:
src/main.ts - 入口文件
这个文件很简单,就是创建 Vue 实例然后挂载:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 创建应用
const app = createApp(App)
// 挂载到页面上的 #app 节点
app.mount('#app')
vite.config.ts - Vite 配置
默认配置很简单,就一个 Vue 插件:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
})
后面我们会给它加点料,别急😄
开始配置项目
现在项目是能跑了,但还不够好用。我们来做一些优化。
路径别名配置(超级实用!)
你有没有遇到过这种情况:
import Button from '../../../components/Button.vue'
这种 ../../../ 看着就头疼,而且一不小心就写错。所以我们要配置路径别名。
修改 vite.config.ts:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path' // 记得导入 path
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'), // @ 就代表 src 目录
'@components': resolve(__dirname, 'src/components'),
'@assets': resolve(__dirname, 'src/assets'),
'@utils': resolve(__dirname, 'src/utils'),
// 根据你的项目需要,想加多少加多少
}
},
server: {
port: 3000, // 我习惯用 3000 端口,你随意
open: true, // 启动自动打开浏览器,省得手动打开
cors: true, // 允许跨域
// 代理配置(解决开发环境跨域问题)
proxy: {
'/api': {
target: 'http://localhost:8080', // 后端服务地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist', // 输出目录
sourcemap: false, // 不生成 sourcemap
minify: 'terser', // 使用 terser 压缩
chunkSizeWarningLimit: 1500, // chunk 大小警告的限制
rollupOptions: {
output: {
// 分包策略
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus'],
}
}
}
}
})
还要改一下 tsconfig.json,不然 TypeScript 会报错(最新的vite的配置文件是分开管理的,我们浏览器端就使用 tsconfig.app.json 文件就好了):
{
"compilerOptions": {
// ... 其他配置保持不变
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@assets/*": ["src/assets/*"],
// 和 vite.config.ts 里保持一致
},
// ... 其他配置
}
}

配置完之后,导入就方便多了:
// 以前这样写,看着难受
import Button from '../../../components/Button.vue'
// 现在可以这样
import Button from '@components/Button.vue'
我们修改组件引入的方式:
重新启动,页面依旧可以成功渲染。
清爽多了吧!😊
配置自动导入(vite.config.ts):
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
// 自动导入 Vue API
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
dts: 'src/auto-imports.d.ts', // 生成类型声明文件
}),
// 自动导入组件
Components({
resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts', // 生成类型声明文件
}),
],
})
这样配好之后,ref、computed 这些就不用每次都手动导入了,写起来爽多了!
配置自动导入(vite.config.ts):**
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
// 自动导入 Vue API
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
dts: 'src/auto-imports.d.ts', // 生成类型声明文件
}),
// 自动导入组件
Components({
resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts', // 生成类型声明文件
}),
],
})
这样配好之后,ref、computed 这些就不用每次都手动导入了,写起来爽多了!
来看看自动导入的效果
没配置自动导入之前:
<script setup lang="ts">
// 每次都要手动导入,烦死了
import { ref, computed, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElButton } from 'element-plus'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>
<template>
<el-button @click="count++">点击</el-button>
<div>{{ doubleCount }}</div>
</template>
配置自动导入之后:
<script setup lang="ts">
// 啥都不用导入,直接用!
const count = ref(0) // ref 自动导入
const doubleCount = computed(() => count.value * 2) // computed 自动导入
const router = useRouter() // useRouter 自动导入
onMounted(() => { // onMounted 自动导入
console.log('mounted')
})
watch(count, (newVal) => { // watch 自动导入
console.log('count changed:', newVal)
})
</script>
<template>
<!-- ElButton 组件自动导入,不用 import -->
<el-button @click="count++">点击</el-button>
<div>{{ doubleCount }}</div>
</template>
看到没?代码清爽了很多!不用写一堆 import,TypeScript 类型提示也都有,爽歪歪😎
配置代码规范(重要!)
这一步真的别跳过,我吃过亏😭
以前有个项目,一开始没配规范,后来团队来了新人,代码风格五花八门:
- 有人用单引号,有人用双引号
- 有人加分号,有人不加
- 缩进有2格的、4格的、甚至还有用tab的…
最后合并代码的时候那叫一个乱,git diff 全是这种无意义的改动。所以,一开始就把规范配好!
安装 ESLint 和 Prettier
# 一次性装完这些
pnpm add -D eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier
创建 ESLint 配置文件
新建 .eslintrc.cjs 文件:
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['vue', '@typescript-eslint', 'prettier'],
rules: {
'prettier/prettier': 'error',
'vue/multi-word-component-names': 'off', // 允许单词组件名
'@typescript-eslint/no-explicit-any': 'warn', // any 类型警告
'@typescript-eslint/no-unused-vars': 'error', // 未使用变量报错
},
}
Prettier 配置
新建 .prettierrc.json:
{
"semi": false, // 不加分号(我个人习惯,你可以改成 true)
"singleQuote": true, // 用单引号
"printWidth": 100, // 一行最多 100 个字符
"trailingComma": "es5", // 对象最后一个属性后面加逗号
"tabWidth": 2 // 缩进 2 格
}
这些配置看你个人喜好,关键是团队统一。
添加 npm 命令
在 package.json 里加上这些:
{
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"format": "prettier --write src/**/*.{js,ts,vue,json,css,scss}"
}
}
配好之后,可以运行试试:
# 自动修复代码格式
pnpm lint
# 格式化所有文件
pnpm format
看看实际效果
比如你写了这样一段代码(格式很乱):
// 格式化之前(各种不规范)
const user={name:"张三",age: 18, email:"test@example.com", }
function getUserInfo( ){
return user
}
const list=[1,2,3,4,5]
const result=list.map(item=> {
return item*2
})
运行 pnpm format 之后:
// 格式化之后(整整齐齐)
const user = {
name: '张三',
age: 18,
email: 'test@example.com',
}
function getUserInfo() {
return user
}
const list = [1, 2, 3, 4, 5]
const result = list.map((item) => {
return item * 2
})
是不是舒服多了?以后不用纠结格式问题,pnpm format 一键搞定😎
📁 六、推荐的项目目录结构
my-vue3-project/
├── public/
│ └── favicon.ico
├── src/
│ ├── api/ # API 接口
│ │ ├── user.ts
│ │ └── product.ts
│ ├── assets/ # 静态资源
│ │ ├── images/
│ │ └── styles/
│ │ └── global.scss
│ ├── components/ # 公共组件
│ │ ├── common/ # 通用组件
│ │ │ ├── Button.vue
│ │ │ └── Modal.vue
│ │ └── business/ # 业务组件
│ │ └── UserCard.vue
│ ├── composables/ # 组合式函数
│ │ ├── useUser.ts
│ │ └── useAuth.ts
│ ├── directives/ # 自定义指令
│ │ └── loading.ts
│ ├── layouts/ # 布局组件
│ │ ├── DefaultLayout.vue
│ │ └── AdminLayout.vue
│ ├── router/ # 路由配置
│ │ └── index.ts
│ ├── stores/ # Pinia 状态管理
│ │ ├── user.ts
│ │ └── cart.ts
│ ├── types/ # TypeScript 类型定义
│ │ ├── user.ts
│ │ └── api.ts
│ ├── utils/ # 工具函数
│ │ ├── request.ts # axios 封装
│ │ ├── storage.ts # localStorage 封装
│ │ └── validate.ts # 表单验证
│ ├── views/ # 页面组件
│ │ ├── home/
│ │ │ └── index.vue
│ │ ├── user/
│ │ │ ├── login.vue
│ │ │ └── register.vue
│ │ └── product/
│ │ └── detail.vue
│ ├── App.vue
│ ├── main.ts
│ └── env.d.ts
├── .eslintrc.cjs
├── .prettierrc.json
├── .gitignore
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
🎯 七、创建基础功能模块
7.1 封装 Axios 请求
创建 src/utils/request.ts:
import axios, { type AxiosInstance, type AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
// 创建 axios 实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 15000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 从本地存储获取 token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
console.error('Request error:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const res = response.data
// 根据后端返回的 code 判断
if (res.code !== 200) {
ElMessage.error(res.message || '请求失败')
// 401: 未授权,跳转登录页
if (res.code === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
return Promise.reject(new Error(res.message || '请求失败'))
}
return res
},
(error) => {
console.error('Response error:', error)
ElMessage.error(error.message || '网络请求失败')
return Promise.reject(error)
}
)
export default service
7.2 配置环境变量
创建 .env.development(开发环境):
# 开发环境配置
VITE_APP_TITLE=My Vue3 App - Dev
VITE_API_BASE_URL=http://localhost:8080/api
创建 .env.production(生产环境):
# 生产环境配置
VITE_APP_TITLE=My Vue3 App
VITE_API_BASE_URL=https://api.yourdomain.com/api
怎么用这些环境变量?
举个实际例子,比如在标题栏显示不同环境的标识:
<script setup lang="ts">
// 获取环境变量
const appTitle = import.meta.env.VITE_APP_TITLE
const apiUrl = import.meta.env.VITE_API_BASE_URL
const isDev = import.meta.env.DEV // 开发环境是 true,生产环境是 false
// 开发环境会看到:My Vue3 App - Dev
// 生产环境会看到:My Vue3 App
console.log('当前环境:', appTitle)
console.log('API地址:', apiUrl)
</script>
<template>
<div>
<h1>{{ appTitle }}</h1>
<!-- 开发环境显示调试信息 -->
<div v-if="isDev" class="debug-info">
API: {{ apiUrl }}
</div>
</div>
</template>
这样切换环境时,不用改代码,Vite 会自动读取对应的 .env 文件👍
7.3 创建 API 接口模块
创建 src/api/user.ts:
import request from '@utils/request'
// 用户登录
export function login(data: { username: string; password: string }) {
return request({
url: '/user/login',
method: 'post',
data,
})
}
// 获取用户信息
export function getUserInfo() {
return request({
url: '/user/info',
method: 'get',
})
}
// 更新用户信息
export function updateUserInfo(data: any) {
return request({
url: '/user/update',
method: 'put',
data,
})
}
在组件中怎么调用这些 API?
创建一个登录页面 src/views/login/index.vue:
<script setup lang="ts">
import { login } from '@api/user' // 看,用上 @ 别名了
import { ElMessage } from 'element-plus'
// 表单数据
const formData = reactive({
username: '',
password: ''
})
const loading = ref(false)
// 登录处理
const handleLogin = async () => {
if (!formData.username || !formData.password) {
ElMessage.warning('请输入用户名和密码')
return
}
try {
loading.value = true
// 调用登录 API(就是刚才在 api/user.ts 里定义的)
const res = await login({
username: formData.username,
password: formData.password
})
// 登录成功
ElMessage.success('登录成功')
localStorage.setItem('token', res.data.token)
// 跳转首页
router.push('/')
} catch (error) {
// 错误处理已经在 request.ts 的拦截器里统一处理了
// 这里不用再写 ElMessage.error
} finally {
loading.value = false
}
}
</script>
<template>
<div class="login-page">
<el-form>
<el-form-item label="用户名">
<el-input v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="formData.password"
type="password"
placeholder="请输入密码"
/>
</el-form-item>
<el-button
type="primary"
:loading="loading"
@click="handleLogin"
>
登录
</el-button>
</el-form>
</div>
</template>
看到没?API 调用就是这么简单:
- 在
api/user.ts里定义接口 - 在组件里导入函数
- 直接调用,等待结果
- 错误处理都在拦截器里统一搞定了
后面要加新接口,按这个模式来就行,很规范👌
🚨 八、常见问题解决
问题1:Vite 启动报错 EADDRINUSE
原因: 端口被占用
解决:
# 方法1:修改 vite.config.ts 中的 port
server: {
port: 3001, // 换一个端口
}
# 方法2:杀掉占用端口的进程(Windows)
netstat -ano | findstr :5173
taskkill /PID <进程ID> /F
# 方法2:杀掉占用端口的进程(Mac/Linux)
lsof -ti:5173 | xargs kill
问题2:路径别名 @ 不生效
原因: TypeScript 配置不正确
解决: 确保 tsconfig.json 中配置了 baseUrl 和 paths
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
问题3:Element Plus 样式丢失
原因: 没有导入样式文件
解决: 在 main.ts 中导入:
import 'element-plus/dist/index.css'
或者使用 unplugin-vue-components 自动导入(推荐)。
问题4:热更新不生效
解决:
// vite.config.ts
export default defineConfig({
server: {
watch: {
usePolling: true, // 启用轮询模式
},
},
})
🎉 九、测试运行
启动开发服务器
pnpm dev
访问 http://localhost:3000,应该能看到页面正常运行。
[配图占位:项目运行成功截图]
构建生产版本
# 构建
pnpm build
# 本地预览构建结果
pnpm preview
构建完成后,会在项目根目录生成 dist 文件夹,这就是可以部署到服务器的静态文件。
写在最后
如果你一路跟下来了,那恭喜!你现在有了:
- 一个配置完整的 Vue3 + Vite 项目
- 代码规范配置(不会再被 leader 说代码乱了)
- 一些实用的工具封装(Axios、环境变量这些)
说点实在的
很多人搭建项目就是 npm create vite 一把梭,然后啥规范都不配。等项目大了,各种问题就来了:
- 有人用单引号,有人用双引号
- 有人缩进 2 格,有人缩进 4 格
- git 提交信息五花八门,想找个提交记录都费劲
所以别嫌麻烦,这些配置真的很有用。我之前维护过一个没配规范的项目,那叫一个痛苦😭
关于这个系列
这是我 Vue3 系列的第一篇,后面还会写:
- Vue3 的组合式 API 怎么用
- Pinia 状态管理
- 路由配置和权限控制
- 性能优化
- 打包部署
- …
每篇都是我实际项目中用到的东西,不会讲那些花里胡哨用不到的。
下一篇写啥?
下一篇准备讲 Vue3 的组合式 API,主要是这几个:
ref 和 reactive 到底怎么选?
说实话,我一开始也搞不清楚。现在算是总结出一套自己的用法了,下篇会详细说。
computed 和 watch 的一些技巧
这俩我用得比较多,有些小技巧挺实用的,到时候分享给你们。
自定义 Hooks(Composable)
这个真的很好用!把一些通用逻辑封装起来,到处都能用。我会分享几个我常用的,比如 useTable、useForm 这些。
从 Options API 迁移到 Composition API
如果你项目还是 Vue2 或者 Options API,我会教你怎么一步步迁移。
大概 3-5 天后发吧,我得好好整理下。
完整源码在哪?
这个项目的完整配置我放在网站上了(私信我也可以)。因为平台限制,我不能直接贴链接😅
你可以这样找到:
百度或 Google 搜索:代码小库
然后在网站首页搜「Vue3 Vite 项目搭建」就能找到了,里面有完整的源码和配置文件。
关于我
我是【代码小库】的作者,做前端 6 年多了。
这几年经历过:
- 外包公司的加班地狱(每天改到凌晨那种)
- 创业公司的从 0 到 1
- 大厂的各种规范和流程
踩过的坑真的太多了,所以想把这些经验分享出来,帮大家少走点弯路。
为啥要写这个系列?
说实话,网上的 Vue3 教程很多,但质量参差不齐。有的太基础,照着做完还是不会;有的太高深,新手根本看不懂。
我想写的是那种:
- 看得懂 - 用大白话讲,不搞那些高大上的术语
- 跟得上 - 跟着做一定能跑起来
- 用得到 - 都是实际项目中会用到的东西
想找我交流?
搜索「开发者代码仓」关注服务号就行,我基本每天都会看消息。
有问题可以留言,我会尽量回复(工作日可能会慢点,周末回复快)
最后说两句
如果这篇文章对你有帮助,麻烦帮我:
- 点个赞👍(真的很重要!)
- 收藏一下⭐(下次要用的时候好找)
- 有问题评论区留言💬(我会认真回复)
写教程真的挺费时间的,这一篇我前前后后写了一周。但看到有人说有帮助,就觉得值了。
下一篇讲组合式 API,我先去整理了,争取这周内发出来。
咱们下篇见!👋
#Vue3 #Vite #前端开发 #项目搭建
所有评论(0)