在开始一个Vue后台管理系统项目时,选择合适的构建工具是首要决策。目前主流选择是Vue CLI和Vite,两者各有特点,适用于不同场景。
Vue CLI基于Webpack打包机制,采用"预打包+热更新"的工作模式。Webpack会先扫描整个项目的依赖关系,构建完整的依赖图,然后进行打包处理。这种方式的优势是兼容性好,可以处理各种复杂的模块依赖关系,但缺点是随着项目规模增大,启动和热更新速度会明显下降。
Vite则采用了完全不同的思路,它基于现代浏览器原生支持的ES Modules(ESM)。开发环境下,Vite不会预先打包整个应用,而是按需编译和提供源代码。当浏览器请求某个模块时,Vite才会实时编译该模块。这种设计使得Vite在开发环境下的启动速度极快,几乎是即时的。
提示:如果你的项目需要支持较旧的浏览器(如IE11),Vite可能不是最佳选择,因为它依赖于现代浏览器的ESM支持。
在实际项目中,两者的性能差异非常明显:
冷启动时间:
热模块替换(HMR):
生产构建:
Vue CLI作为Vue官方长期维护的工具,拥有丰富的插件生态:
Vite的生态正在快速发展中:
根据项目特点选择:
| 项目特征 | 推荐工具 | 理由 |
|---|---|---|
| Vue 2项目 | Vue CLI | 原生支持,生态完善 |
| 大型传统后台 | Vue CLI | 稳定,兼容性好 |
| Vue 3新项目 | Vite | 开发体验极佳 |
| 需要快速迭代 | Vite | 即时热更新 |
| 旧浏览器支持 | Vue CLI | 更好的polyfill支持 |
在实际项目中,我通常会考虑以下因素:
Vue CLI需要Node.js环境,建议使用长期支持版(LTS):
bash复制# 检查Node版本
node -v
# 应该显示v14.x.x或更高
# 检查npm版本
npm -v
# 应该显示6.x.x或更高
如果版本过低,可以通过以下方式升级:
bash复制nvm install --lts
nvm use --lts
推荐使用yarn进行安装,因为它比npm更快且生成更稳定的依赖树:
bash复制# 全局安装Vue CLI
yarn global add @vue/cli
# 验证安装
vue --version
# 应该显示4.x.x或更高
如果遇到权限问题,可以尝试:
bash复制# 对于npm用户
npm install -g @vue/cli --unsafe-perm
创建项目时,手动选择功能可以更好地适应后台管理系统需求:
bash复制vue create my-admin
在交互式界面中,选择"Manually select features",然后根据项目需求选择:
必选功能:
可选功能:
配置选择建议:
创建完成后,目录结构应如下:
code复制my-admin/
├── public/
│ ├── favicon.ico
│ └── index.html
├── src/
│ ├── assets/
│ ├── components/
│ ├── router/
│ ├── store/
│ ├── views/
│ ├── App.vue
│ └── main.js
├── .eslintrc.js
├── babel.config.js
├── package.json
└── vue.config.js
运行项目:
bash复制cd my-admin
yarn serve
项目应该能在http://localhost:8080正常启动。
vue.config.js是Vue CLI的核心配置文件,用于自定义webpack行为:
javascript复制const path = require('path')
module.exports = {
// 基本路径
publicPath: process.env.NODE_ENV === 'production'
? '/admin/'
: '/',
// 输出目录
outputDir: 'dist',
// 静态资源目录
assetsDir: 'static',
// 是否生成source map
productionSourceMap: false,
// webpack配置
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components')
}
}
},
// 开发服务器配置
devServer: {
port: 8081,
open: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},
// CSS相关配置
css: {
extract: true, // 生产环境提取CSS
sourceMap: false,
loaderOptions: {
scss: {
additionalData: `
@import "~@/assets/styles/variables.scss";
@import "~@/assets/styles/mixins.scss";
`
}
}
}
}
javascript复制// vue.config.js
const env = process.env.NODE_ENV
module.exports = {
publicPath: env === 'development'
? '/'
: env === 'test'
? '/test-admin/'
: '/prod-admin/'
}
javascript复制const webpack = require('webpack')
module.exports = {
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
'__VERSION__': JSON.stringify(require('./package.json').version)
})
]
}
}
javascript复制module.exports = {
chainWebpack: config => {
// 移除prefetch插件
config.plugins.delete('prefetch')
// 分割代码
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20,
test: /[\\/]node_modules[\\/]_?element-ui(.*)/
}
}
})
}
}
Vite需要较新的Node.js版本:
bash复制node -v
# 应该显示v16.0.0或更高
如果版本不足,可以通过以下方式升级:
bash复制nvm install 16
nvm use 16
bash复制n 16
Vite提供了更简单的项目创建方式:
bash复制# 使用npm
npm create vite@latest my-admin -- --template vue-ts
# 使用yarn
yarn create vite my-admin --template vue-ts
# 使用pnpm(推荐)
pnpm create vite my-admin --template vue-ts
模板选项说明:
创建完成后,安装依赖并启动:
bash复制cd my-admin
# 使用pnpm安装(最快)
pnpm install
# 启动开发服务器
pnpm run dev
Vite的启动速度非常快,即使是大型项目也能在1秒内完成启动。
typescript复制import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd())
return {
// 基础路径
base: env.VITE_BASE_URL || '/',
// 插件配置
plugins: [vue()],
// 解析配置
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components')
}
},
// 开发服务器配置
server: {
port: 5173,
open: true,
proxy: {
'/api': {
target: env.VITE_API_BASE_URL,
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'static',
sourcemap: false,
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
},
// CSS配置
css: {
preprocessorOptions: {
scss: {
additionalData: `
@import "@/assets/styles/variables.scss";
@import "@/assets/styles/mixins.scss";
`
}
}
}
}
})
typescript复制import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: [
'vue',
'vue-router',
'pinia'
],
dts: 'src/auto-imports.d.ts'
})
]
})
typescript复制import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
Components({
dts: 'src/components.d.ts',
dirs: ['src/components']
})
]
})
typescript复制import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
viteCompression({
algorithm: 'gzip',
ext: '.gz'
})
]
})
无论是Vue CLI还是Vite创建的项目,基础目录结构大致相同:
code复制my-admin/
├── public/ # 静态资源(直接复制到dist)
│ ├── favicon.ico # 网站图标
│ └── index.html # 主HTML文件
├── src/
│ ├── assets/ # 编译处理的静态资源
│ │ ├── images/ # 图片资源
│ │ └── styles/ # 全局样式
│ ├── components/ # 公共组件
│ │ ├── common/ # 通用基础组件
│ │ └── business/ # 业务公共组件
│ ├── router/ # 路由配置
│ │ └── index.ts # 路由定义
│ ├── store/ # 状态管理
│ │ ├── index.ts # 主store
│ │ └── modules/ # 模块化store
│ ├── views/ # 页面组件
│ │ ├── Login/ # 登录页
│ │ └── Home/ # 首页
│ ├── App.vue # 根组件
│ └── main.ts # 应用入口
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .eslintrc.js # ESLint配置
├── .prettierrc # Prettier配置
├── package.json # 项目配置
├── tsconfig.json # TypeScript配置
└── vite.config.ts/vue.config.js # 构建配置
后台系统通常需要扩展以下目录结构:
code复制src/
├── api/ # API请求管理
│ ├── index.ts # axios封装
│ ├── user.ts # 用户相关API
│ └── system.ts # 系统管理API
├── composables/ # 组合式函数
│ ├── useAuth.ts # 认证逻辑
│ └── useTable.ts # 表格逻辑
├── directives/ # 自定义指令
│ ├── index.ts # 指令注册
│ └── auth.ts # 权限指令
├── hooks/ # 自定义钩子
│ └── useRequest.ts # 请求封装
├── layout/ # 布局组件
│ ├── index.vue # 主布局
│ ├── Sidebar.vue # 侧边栏
│ └── Header.vue # 顶部导航
├── utils/ # 工具函数
│ ├── index.ts # 工具集合
│ ├── format.ts # 格式化
│ └── storage.ts # 存储封装
└── types/ # 类型定义
├── api.ts # API类型
├── user.ts # 用户类型
└── global.d.ts # 全局类型
api/index.ts通常包含axios的全局配置:
typescript复制import axios from 'axios'
import { useUserStore } from '@/store/user'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(
config => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
// 处理业务错误
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
error => {
// 处理HTTP错误
return Promise.reject(error)
}
)
export default service
directives/auth.ts实现权限控制:
typescript复制import type { Directive } from 'vue'
import { useUserStore } from '@/store/user'
const authDirective: Directive = {
mounted(el, binding) {
const { value } = binding
const userStore = useUserStore()
if (value && Array.isArray(value)) {
const hasPermission = userStore.permissions.some(permission => {
return value.includes(permission)
})
if (!hasPermission) {
el.parentNode?.removeChild(el)
}
} else {
throw new Error('v-auth需要传入权限数组,例如v-auth="[\'user.add\']"')
}
}
}
export default authDirective
后台系统通常需要区分开发、测试和生产环境:
.env.development:
ini复制NODE_ENV=development
VITE_BASE_URL=/
VITE_API_BASE_URL=http://localhost:3000
.env.production:
ini复制NODE_ENV=production
VITE_BASE_URL=/admin/
VITE_API_BASE_URL=https://api.example.com
在src/env.d.ts中添加类型声明:
typescript复制interface ImportMetaEnv {
readonly VITE_BASE_URL: string
readonly VITE_API_BASE_URL: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
.eslintrc.js):javascript复制module.exports = {
root: true,
env: {
node: true,
browser: true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@vue/typescript/recommended',
'@vue/prettier'
],
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': [
'error',
{
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: true,
arrowParens: 'avoid'
}
]
}
}
.prettierrc):json复制{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid"
}
typescript复制// router/index.ts
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard/index.vue')
}
]
typescript复制// 使用defineAsyncComponent
const AsyncComponent = defineAsyncComponent(() =>
import('@/components/AsyncComponent.vue')
)
typescript复制// vite.config.ts
import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [
splitVendorChunkPlugin()
],
build: {
rollupOptions: {
external: ['vue', 'vue-router', 'axios'],
output: {
globals: {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios'
}
}
}
}
})
nginx复制server {
listen 80;
server_name admin.example.com;
location / {
root /var/www/admin/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 开启gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
}
dockerfile复制# Dockerfile
FROM node:16 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
在实际项目中,我通常会根据团队的技术栈和项目需求,选择合适的工具链和优化策略。对于新项目,Vite+Vue3的组合能提供更好的开发体验;而对于需要维护的旧项目,Vue CLI的稳定性可能更为重要。