Vue生态与工程化

目录


Vue CLI

Vue CLI简介

Vue CLI是Vue官方提供的脚手架工具,用于快速创建Vue项目。它提供了交互式的项目初始化、内置的开发服务器、配置加载等功能。

1
2
3
4
5
6
7
8
9
10
11
12
# 安装
npm install -g @vue/cli

# 创建项目
vue create my-project

# 启动开发服务器
cd my-project
npm run serve

# 构建生产版本
npm run build

Vue CLI项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
my-project/
├── public/
│ ├── favicon.ico
│ └── index.html
├── src/
│ ├── assets/
│ │ └── logo.png
│ ├── components/
│ │ └── HelloWorld.vue
│ ├── router/
│ │ └── index.js
│ ├── store/
│ │ └── index.js
│ ├── views/
│ │ ├── About.vue
│ │ └── Home.vue
│ ├── App.vue
│ └── main.js
├── .browserslistrc
├── .eslintrc.js
├── babel.config.js
├── package.json
└── vue.config.js

vue.config.js配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/my-app/'
: '/',

outputDir: 'dist',

assetsDir: 'static',

lintOnSave: process.env.NODE_ENV !== 'production',

productionSourceMap: false,

devServer: {
port: 8080,
open: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},

configureWebpack: {
resolve: {
alias: {
'@': resolve('src')
}
}
},

chainWebpack: config => {
config.plugin('html').tap(args => {
args[0].title = 'My App'
return args
})
}
}

环境变量

1
2
3
4
5
6
7
8
9
10
11
12
# .env - 所有环境加载
VUE_APP_BASE_API=/api
VUE_APP_VERSION=1.0.0

# .env.local - 所有环境加载,git忽略
VUE_APP_CUSTOM_CONFIG=xxx

# .env.development - 开发环境
VUE_APP_ENV=development

# .env.production - 生产环境
VUE_APP_ENV=production
1
2
// 使用环境变量
console.log(process.env.VUE_APP_BASE_API)

Vite

Vite简介

Vite是新一代前端构建工具,由Vue作者尤雨溪主导开发。它利用浏览器原生ES模块实现极速的开发体验。

1
2
3
4
5
6
7
8
9
10
11
12
# 创建项目
npm create vite@latest my-vue3-app -- --template vue

# 安装依赖
cd my-vue3-app
npm install

# 启动开发服务器
npm run dev

# 构建
npm run build

Vite核心特性

特性 说明
极速冷启动 基于ESM,无需打包
热模块更新 基于ESM的HMR
按需编译 只编译当前使用的模块
开箱即用 内置TypeScript、JSX、CSS支持

Vite配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
plugins: [vue()],

resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},

server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
},

build: {
target: 'es2015',
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
'element-plus': ['element-plus']
}
}
}
}
})

Vite环境变量

1
2
3
4
5
6
7
8
# .env - 所有环境
VITE_APP_BASE_URL=/api

# .env.development
VITE_APP_ENV=development

# .env.production
VITE_APP_ENV=production
1
2
// 使用环境变量
console.log(import.meta.env.VITE_APP_BASE_URL)

构建工具对比

Vite vs Webpack vs Vue CLI

特性 Vite Webpack Vue CLI
开发启动 极快(ESM) 慢(打包) 慢(打包)
HMR 极速 中等 中等
生产构建 Rollup Webpack Webpack
配置复杂度
生态 发展中 成熟 成熟
Vue3支持 最佳 支持 支持
冷启动 即时

Vite工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────┐
│ Vite 开发模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 浏览器请求 │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Vite Dev │ │
│ │ Server │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 模块处理 │ │
│ │ │ │
│ │ 1. HTML -> 返回HTML │ │
│ │ 2. Vue SFC -> 编译成JS/CSS │ │
│ │ 3. CSS -> 返回CSS │ │
│ │ 4. ES Module -> 返回原模块 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 返回浏览器 │
│ │
└─────────────────────────────────────────────────────────────┘

工程化最佳实践

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
src/
├── api/ # API接口
│ ├── user.js
│ ├── product.js
│ └── index.js
├── assets/ # 静态资源
│ ├── images/
│ └── styles/
├── components/ # 公共组件
│ ├── BaseButton.vue
│ ├── BaseInput.vue
│ └── BaseModal.vue
├── composables/ # 组合式函数 (Vue3)
│ ├── useMouse.js
│ └── useFetch.js
├── config/ # 配置文件
│ └── index.js
├── constants/ # 常量定义
│ └── index.js
├── directives/ # 自定义指令
│ └── index.js
├── hooks/ # 生命周期钩子 (Vue2)
│ └── index.js
├── layout/ # 布局组件
│ ├── MainLayout.vue
│ └── BlankLayout.vue
├── router/ # 路由配置
│ ├── index.js
│ └── routes/
├── store/ # 状态管理
│ ├── modules/
│ └── index.js
├── utils/ # 工具函数
│ ├── storage.js
│ └── format.js
├── views/ # 页面组件
│ ├── home/
│ │ └── index.vue
│ └── user/
│ ├── index.vue
│ └── detail.vue
├── App.vue
└── main.js

API封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// api/request.js
import axios from 'axios'
import { useAuthStore } from '@/store/auth'

const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})

request.interceptors.request.use(
config => {
const authStore = useAuthStore()
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`
}
return config
},
error => Promise.reject(error)
)

request.interceptors.response.use(
response => {
const { data } = response
if (data.code !== 0) {
Message.error(data.message)
return Promise.reject(data)
}
return data.data
},
error => {
if (error.response) {
const { status } = error.response
if (status === 401) {
const authStore = useAuthStore()
authStore.logout()
router.push('/login')
}
}
return Promise.reject(error)
}
)

export default request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// api/user.js
import request from './request'

export const getUserList = (params) => {
return request.get('/users', { params })
}

export const getUserById = (id) => {
return request.get(`/users/${id}`)
}

export const createUser = (data) => {
return request.post('/users', data)
}

export const updateUser = (id, data) => {
return request.put(`/users/${id}`, data)
}

export const deleteUser = (id) => {
return request.delete(`/users/${id}`)
}

工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// utils/storage.js
const STORAGE_PREFIX = 'APP_'

export const storage = {
set(key, value) {
const data = JSON.stringify(value)
localStorage.setItem(STORAGE_PREFIX + key, data)
},

get(key, defaultValue = null) {
const data = localStorage.getItem(STORAGE_PREFIX + key)
if (data === null) return defaultValue
try {
return JSON.parse(data)
} catch {
return data
}
},

remove(key) {
localStorage.removeItem(STORAGE_PREFIX + key)
},

clear() {
Object.keys(localStorage).forEach(key => {
if (key.startsWith(STORAGE_PREFIX)) {
localStorage.removeItem(key)
}
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// utils/format.js
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hour = String(d.getHours()).padStart(2, '0')
const minute = String(d.getMinutes()).padStart(2, '0')
const second = String(d.getSeconds()).padStart(2, '0')

return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hour)
.replace('mm', minute)
.replace('ss', second)
}

export function formatFileSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

Element UI

简介

Element UI是一套为开发者、设计师和产品经理准备的基于Vue 2.0的桌面端组件库。

1
2
3
4
5
# Vue2
npm install element-ui -S

# Vue3
npm install element-plus -S

基本使用

1
2
3
4
5
6
7
8
9
10
11
// Vue2 - main.js
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'

Vue.use(ElementUI)

new Vue({
render: h => h(App)
}).$mount('#app')
1
2
3
4
5
6
7
8
9
// Vue3 - main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

常用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>

<el-button type="primary" @click="handleClick">按钮</el-button>

<el-input v-model="input" placeholder="请输入"></el-input>

<el-table :data="tableData" stripe>
<el-table-column prop="date" label="日期"></el-table-column>
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
</el-table>

<el-form :model="form" label-width="80px">
<el-form-item label="名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-form>

<el-dialog title="提示" :visible.sync="dialogVisible">
<span>这是一段信息</span>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">确定</el-button>
</template>
</el-dialog>
</template>

Ant Design Vue

简介

Ant Design Vue是Ant Design的Vue实现,专注于企业级产品的UI设计。

1
2
3
4
5
# Vue3
npm install ant-design-vue@next

# Vue2
npm install ant-design-vue

基本使用

1
2
3
4
5
6
7
8
9
// Vue3 - main.js
import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import App from './App.vue'

const app = createApp(App)
app.use(Antd)
app.mount('#app')

常用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<a-button type="primary" @click="handleClick">按钮</a-button>

<a-input v-model:value="input" placeholder="请输入" />

<a-table :dataSource="dataSource" :columns="columns" />

<a-modal v-model:open="visible" title="Modal" @ok="handleOk">
<p>Some contents...</p>
</a-modal>

<a-form :model="form" layout="vertical">
<a-form-item label="名称">
<a-input v-model:value="form.name" />
</a-form-item>
</a-form>
</template>

项目结构

小型项目结构

1
2
3
4
5
6
7
8
9
10
src/
├── api/
├── assets/
├── components/
├── router/
├── store/
├── utils/
├── views/
├── App.vue
└── main.js

中型项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
src/
├── api/
├── components/ # 公共组件
│ ├── common/ # 通用组件
│ └── business/ # 业务组件
├── composables/ # 组合式函数
├── constants/ # 常量
├── directives/ # 指令
├── hooks/ # 钩子函数
├── layouts/ # 布局组件
├── router/ # 路由
│ └── modules/ # 路由模块
├── store/ # 状态管理
│ └── modules/ # Store模块
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面
│ ├── modules/ # 按模块划分
│ └── ...
└── App.vue

大型项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
src/
├── apis/ # 接口定义
│ ├── modules/ # 按模块划分
│ └── index.ts
├── assets/ # 静态资源
│ ├── icons/
│ ├── images/
│ └── styles/
├── components/ # 组件
│ ├── base/ # 基础组件
│ ├── business/ # 业务组件
│ └── common/ # 通用组件
├── composables/ # 组合式函数
├── configs/ # 配置文件
├── constants/ # 常量定义
├── directives/ # 指令
├── enums/ # 枚举
├── hooks/ # 钩子函数
├── layouts/ # 布局
├── mocks/ # Mock数据
├── router/ # 路由
│ ├── guards/ # 路由守卫
│ └── routes/ # 路由配置
├── services/ # 服务层
├── stores/ # 状态管理
│ └── modules/ # Store模块
├── types/ # TypeScript类型
├── utils/ # 工具函数
├── views/ # 页面
├── App.vue
└── main.ts

代码规范

ESLint配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2021: true
},
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended'
],
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'vue/multi-word-component-names': 'off'
}
}

Prettier配置

1
2
3
4
5
6
7
8
9
10
11
// .prettierrc.js
module.exports = {
semi: false,
singleQuote: true,
trailingComma: 'none',
printWidth: 100,
tabWidth: 2,
useTabs: false,
arrowParens: 'avoid',
endOfLine: 'auto'
}

Git Hooks

1
2
3
4
5
6
7
// .huskyrc
{
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
1
2
3
4
5
// lint-staged.config.js
module.exports = {
'*.{js,vue}': ['eslint --fix', 'git add'],
'*.{css,scss}': ['prettier --write', 'git add']
}

总结

构建工具选择

场景 推荐
新项目Vue3 Vite
Vue2项目 Vue CLI
大型企业项目 Webpack
快速原型 Vite
追求构建速度 Vite

UI框架选择

框架 特点
Element Plus 简洁、文档完善
Ant Design Vue 企业级、组件丰富
Vuetify Material Design
Naive UI TypeScript优先

相关链接