Vue 生态工具链
2026/3/20大约 6 分钟
Vue 生态工具链:构建现代化开发工作流
一个高效的开发工作流离不开优秀的工具链。Vue 生态提供了从项目创建、开发调试到构建部署的完整工具支持。本文将介绍 Vue 开发中常用的工具和配置技巧。
构建工具对比
Vue CLI vs Vite
| 特性 | Vue CLI | Vite |
|---|---|---|
| 构建工具 | Webpack | Rollup + esbuild |
| 冷启动速度 | 较慢(需打包) | 极快(按需编译) |
| 热更新 | 较慢 | 极快 |
| 配置复杂度 | 较高 | 较低 |
| 生态成熟度 | 成熟 | 快速发展 |
| 推荐场景 | Vue2 项目 | Vue3 新项目 |
Vue CLI
安装与创建项目
# 全局安装
npm install -g @vue/cli@4.5.15
# 创建项目
vue create my-project
# 使用图形界面
vue ui
vue.config.js 配置
// vue.config.js
const path = require("path");
module.exports = {
// 部署路径
publicPath: process.env.NODE_ENV === "production" ? "/my-app/" : "/",
// 输出目录
outputDir: "dist",
// 静态资源目录
assetsDir: "static",
// 是否使用 eslint-loader
lintOnSave: process.env.NODE_ENV !== "production",
// 生产环境 source map
productionSourceMap: false,
// 开发服务器配置
devServer: {
port: 8080,
open: true,
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
},
},
},
// webpack 配置
configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"@components": path.resolve(__dirname, "src/components"),
},
},
// 外部依赖(CDN)
externals:
process.env.NODE_ENV === "production"
? {
vue: "Vue",
"vue-router": "VueRouter",
vuex: "Vuex",
}
: {},
},
// 链式 webpack 配置
chainWebpack: (config) => {
// 修改 loader
config.module
.rule("svg")
.exclude.add(path.resolve(__dirname, "src/icons"))
.end();
config.module
.rule("icons")
.test(/\.svg$/)
.include.add(path.resolve(__dirname, "src/icons"))
.end()
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]",
});
// 分包
config.optimization.splitChunks({
chunks: "all",
cacheGroups: {
vendor: {
name: "chunk-vendor",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial",
},
elementUI: {
name: "chunk-elementUI",
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 20,
},
commons: {
name: "chunk-commons",
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
},
});
},
// CSS 相关
css: {
// 是否提取 CSS
extract: process.env.NODE_ENV === "production",
// CSS source map
sourceMap: false,
// CSS 预处理器配置
loaderOptions: {
sass: {
additionalData: `@import "@/styles/variables.scss";`,
},
less: {
lessOptions: {
modifyVars: {
"primary-color": "#1DA57A",
},
javascriptEnabled: true,
},
},
},
},
// 多页面配置
pages: {
index: {
entry: "src/main.js",
template: "public/index.html",
filename: "index.html",
title: "首页",
},
admin: {
entry: "src/admin/main.js",
template: "public/admin.html",
filename: "admin.html",
title: "管理后台",
},
},
};
Vite
创建项目
# npm
npm create vite@latest my-vue-app -- --template vue
# 使用 TypeScript
npm create vite@latest my-vue-app -- --template vue-ts
# pnpm
pnpm create vite my-vue-app --template vue
vite.config.js 配置
// vite.config.js
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// 自动导入
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// SVG 图标
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
// 压缩
import viteCompression from "vite-plugin-compression";
export default defineConfig(({ command, mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd());
return {
// 基础路径
base: mode === "production" ? "/my-app/" : "/",
// 插件
plugins: [
vue(),
// 自动导入 API
AutoImport({
imports: ["vue", "vue-router", "pinia"],
resolvers: [ElementPlusResolver()],
dts: "src/auto-imports.d.ts",
}),
// 自动导入组件
Components({
resolvers: [ElementPlusResolver()],
dts: "src/components.d.ts",
}),
// SVG 图标
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), "src/icons")],
symbolId: "icon-[dir]-[name]",
}),
// Gzip 压缩
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: "gzip",
ext: ".gz",
}),
],
// 路径别名
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"@components": path.resolve(__dirname, "src/components"),
"@utils": path.resolve(__dirname, "src/utils"),
},
},
// CSS 配置
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
},
// 开发服务器
server: {
host: "0.0.0.0",
port: 3000,
open: true,
cors: true,
proxy: {
"/api": {
target: env.VITE_API_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
// 构建配置
build: {
target: "es2015",
outDir: "dist",
assetsDir: "assets",
// 小于此阈值的资源内联为 base64
assetsInlineLimit: 4096,
// CSS 代码拆分
cssCodeSplit: true,
// 生产环境移除 console
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
// 分包策略
rollupOptions: {
output: {
chunkFileNames: "js/[name]-[hash].js",
entryFileNames: "js/[name]-[hash].js",
assetFileNames: "[ext]/[name]-[hash].[ext]",
manualChunks: {
"vue-vendor": ["vue", "vue-router", "pinia"],
"element-plus": ["element-plus"],
utils: ["lodash-es", "dayjs", "axios"],
},
},
},
},
// 优化依赖
optimizeDeps: {
include: ["vue", "vue-router", "pinia", "axios", "lodash-es"],
},
};
});
环境变量
# .env
VITE_APP_TITLE=My App
# .env.development
VITE_API_URL=http://localhost:3000
# .env.production
VITE_API_URL=https://api.example.com
// 使用
console.log(import.meta.env.VITE_APP_TITLE);
console.log(import.meta.env.VITE_API_URL);
console.log(import.meta.env.MODE); // 'development' | 'production'
console.log(import.meta.env.DEV); // true | false
console.log(import.meta.env.PROD); // true | false
ESLint + Prettier
配置文件
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2021: true,
},
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/typescript/recommended",
"prettier",
],
parserOptions: {
ecmaVersion: 2021,
},
rules: {
"vue/multi-word-component-names": "off",
"vue/no-v-html": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
};
// .prettierrc
{
"semi": false,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto"
}
// package.json scripts
{
"scripts": {
"lint": "eslint --ext .js,.vue,.ts src",
"lint:fix": "eslint --ext .js,.vue,.ts src --fix",
"format": "prettier --write \"src/**/*.{js,ts,vue,scss,css}\""
}
}
Git Hooks(husky + lint-staged)
# 安装
npm install -D husky lint-staged
# 初始化 husky
npx husky install
# 添加 pre-commit hook
npx husky add .husky/pre-commit "npx lint-staged"
# 添加 commit-msg hook
npx husky add .husky/commit-msg "npx commitlint --edit $1"
// package.json
{
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss}": [
"prettier --write"
]
}
}
Commitlint
npm install -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.js
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat", // 新功能
"fix", // 修复
"docs", // 文档
"style", // 格式
"refactor", // 重构
"perf", // 性能
"test", // 测试
"chore", // 构建/工具
"revert", // 回滚
],
],
},
};
单元测试(Vitest)
npm install -D vitest @vue/test-utils happy-dom
// vite.config.js
export default defineConfig({
test: {
globals: true,
environment: "happy-dom",
},
});
// src/components/__tests__/Counter.test.js
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import Counter from "../Counter.vue";
describe("Counter", () => {
it("renders properly", () => {
const wrapper = mount(Counter, {
props: { initialCount: 0 },
});
expect(wrapper.text()).toContain("0");
});
it("increments count when button is clicked", async () => {
const wrapper = mount(Counter);
await wrapper.find("button").trigger("click");
expect(wrapper.text()).toContain("1");
});
});
常用开发工具
Vue Devtools
- Chrome/Firefox 扩展
- 组件层级查看
- 状态检查
- 性能分析
- 路由/Vuex/Pinia 调试
VueUse
npm install @vueuse/core
import {
useMouse,
useLocalStorage,
useFetch,
useDebounce,
useClipboard,
useDark,
useFullscreen,
useIntersectionObserver,
} from "@vueuse/core";
// 鼠标位置
const { x, y } = useMouse();
// 本地存储
const state = useLocalStorage("my-store", { name: "default" });
// 防抖
const input = ref("");
const debounced = useDebounce(input, 500);
// 剪贴板
const { copy, copied } = useClipboard();
// 暗黑模式
const isDark = useDark();
// 全屏
const { isFullscreen, toggle } = useFullscreen();
unplugin-vue-components
自动导入组件,无需手动 import:
// vite.config.js
import Components from "unplugin-vue-components/vite";
import {
ElementPlusResolver,
AntDesignVueResolver,
} from "unplugin-vue-components/resolvers";
export default defineConfig({
plugins: [
Components({
resolvers: [ElementPlusResolver(), AntDesignVueResolver()],
}),
],
});
<template>
<!-- 无需导入,直接使用 -->
<el-button>按钮</el-button>
<el-input v-model="text" />
</template>
unplugin-auto-import
自动导入 API:
// vite.config.js
import AutoImport from "unplugin-auto-import/vite";
export default defineConfig({
plugins: [
AutoImport({
imports: ["vue", "vue-router", "pinia"],
dts: "src/auto-imports.d.ts",
}),
],
});
<script setup>
// 无需导入,直接使用
const count = ref(0);
const router = useRouter();
const route = useRoute();
</script>
部署优化
静态资源优化
// vite.config.js
export default defineConfig({
build: {
// 图片压缩
assetsInlineLimit: 4096,
// 分包
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
if (id.includes("element-plus")) {
return "element-plus";
}
if (id.includes("echarts")) {
return "echarts";
}
return "vendor";
}
},
},
},
},
});
CDN 加速
<!-- index.html -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@4.1.6/dist/vue-router.global.prod.js"></script>
// vite.config.js
import { viteExternalsPlugin } from "vite-plugin-externals";
export default defineConfig({
plugins: [
viteExternalsPlugin({
vue: "Vue",
"vue-router": "VueRouter",
}),
],
});
Nginx 配置
server {
listen 80;
server_name example.com;
root /var/www/my-vue-app;
index index.html;
# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
# 缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 不缓存
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# API 代理
location /api/ {
proxy_pass http://backend:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
总结
Vue 生态提供了完整的工具链支持:
- 构建工具:Vue CLI(Webpack)和 Vite
- 代码规范:ESLint + Prettier
- Git 规范:husky + lint-staged + commitlint
- 单元测试:Vitest + Vue Test Utils
- 开发工具:Vue Devtools、VueUse
- 自动导入:unplugin-vue-components、unplugin-auto-import
- 部署优化:分包、CDN、缓存策略
掌握这些工具,你就能构建高效的 Vue 开发工作流。