模板技术信息:
- 构建工具 vite 2.7.2
- 项目框架 vue 3.2.25
- 网络请求 axios 0.24.0
- 路由配置 vue-router 4.0.12
- 状态管理 pinia 2.0.9 & vuex 4.0.2
- 编程语言 TypeScript 4.4.4
- 数据模拟 Mockjs 1.1.0
模版工程化配置:
- CSS 预编译 sass 1.46.0 / less 4.1.2 / stylus 0.56.0
- 代码规范 eslint 8.6.0 + prettier 2.5.1
- Git hooks husky 7.0.4
- 提交规范 commitizen 4.2.4 + commitlint + standard-version 9.3.2
- 单元测试 vue-test-utils + jest
文章系列目录
前言
近期接触了不少 vue 相关项目,而官方提供的模版相对简易,并没有集成过多的工程化配置。在这里就自己搭建一个相对而言比较完备的 vue 模版。由于内容相对还是比较多的,所以作为两篇文章进行叙述,文章目录及技术 如上所示。废话就不多说了,下面开始进入主题。
项目
github地址: vue3-vite-ts-pnpm
项目初始化
这里直接使用 vite 官方 提供的 vue-ts 模版进行基础项目构建。自行配置 ts 环境也可以。【网上有很多类似教程这里就不多赘述了】
PS:本文会尽可能多的配上图文说明,便于更好的理解。构建过程中使用的最近比较🔥热的 pnpm 包管理工具,当然如果你想使用 npm 和 yarn 也是完全 👌 的 。
全局安装 pnpm,如果之前安装过就不用再安装了
npm i pnpm -g
初始化 vue-ts 模版
pnpm init vite@latest
进入项目目录并安装依赖
cd vue3-vite-ts-pnpm && pnpm i
启动项目,浏览器输入
http://localhost:3000/ 即可
pnpm run dev
vite 配置
更新 vite 基本配置,修改配置文件 vite.config.ts 内容如下
import { UserConfigExport, ConfigEnv } from 'vite';
import { viteMockServe } from 'vite-plugin-mock';
import vue from '@vitejs/plugin-vue';
// import eslintPlugin from "vite-plugin-eslint";
import { loadEnv } from 'vite';
import { resolve } from 'path';
export default (): UserConfigExport => {
return {
plugins: [
vue()
],
resolve: {
// 配置 src 目录映射
alias: {
'@': resolve(__dirname, 'src')
}
},
base: './', // 打包路径
server: {
port: 3000, // 端口
open: true, // 启动打开浏览器
cors: true, // 跨域
proxy: { // 代理
"/api": {
target: "http://localhost:8080/api/", // 目标地址
changeOrigin: true, // 修改源
secure: false, // ssl
rewrite: path => path.replace("/api/", "/")
}
}
}
};
};
这里 path 可能会报 ts 模块类型警告,开发环境下安装相应模块类型声明包即可!
pnpm i @types/node -D
路由配置
安装 vue-router
安装 vue3 配套语法的 vue-router@4.x 版本路由
pnpm i vue-router@4
动态路由
动态路由:依据 src/views 视图目录下文件或目录自动生成对应路由信息。如:src/views/404.vue 文件对应路由为:/404;src/views/about/me/index.vue 文件对应路由为:/about/me 等等。
在 src 目录下,新建 router 文件夹并创建 index.ts 文件,如下
安装第三方工具库
lodash-es 及类型声明文件 @types/lodash-es,选择 lodash-es 而不选择 lodash Correct way to import lodash
pnpm i lodash-es -D
pnpm i @types/lodash-es -D
依据已有的文件 全局导入 动态添加路由,在src/router/index.ts 文件添加下面内容
import { createRouter , createWebHistory, RouteRecordRaw } from 'vue-router';
import { camelCase, forEach } from 'lodash-es';
import HelloWorld from '@/components/HelloWorld.vue';
const modules = import.meta.glob('../views/**/index.vue');
const pages = import.meta.glob('../views/*.vue');
/**
* 动态添加路由配置
* @param routers 文件模块对象
* @param routes 路由对象数组
* @param type 动态添加到路由页面类型
*/
const addRoutes = (routers: any, routes: Array<RouteRecordRaw>, type: string = 'pages') => {
forEach(routers, item => {
const routePath = item.name.slice(9, type === 'pages' ? -4 : -10);
routes.push({
path: `/${routePath}`,
name: camelCase(routePath),
// fix: The above dynamic import cannot be analyzed by vite.(warning)
component: routers[item.name],
})
})
}
// 初始化首页
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Index',
component: HelloWorld,
},
];
addRoutes(modules, routes, 'modules');
addRoutes(pages, routes);
// 404
routes.push({
path: '/:pathMath(.*)*',
component: () => import('@/views/404.vue'),
})
const router = createRouter({
history: createWebHistory(),
routes
});
// 路由拦截器, 自定义拦截内容
router.beforeEach((to, from, next) => {
next();
});
export default router;
解决 vite 别名 @ 引入文件 ts 警告及路径提示问题,在 tsconfig.ts 文件中添加下面内容即可:
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
挂载路由
app 下挂载路由,修改 main.ts 文件内容如下
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
const app = createApp(App);
// 挂载路由
app.use(router);
app.mount('#app')
测试路由
添加简单测试页面,同时配置路由页面,修改 App.vue 文件内容,⚠️ 在模板中替换为 <router-view /> 组件。
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<router-view></router-view>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
添加测试路由页面目录如下(只截取了当前视图目录结构)
只是为了测试,页面内容可以随便填写,这里给出一个简易内容模版
<template>
<div>home page</div>
</template>
状态管理(Pinia & Vuex)
vuex 和 pinia,Pinia 是 Vue.js 的轻量级状态管理库,用法与 vuex 基本相同,具体异同可参考 Pinia与Vuex的对比,模版 demo 中以 Pinia 为例;不过下面也给出了 Vuex 简单示例说明。
Pinia
pinia 安装
pnpm i pinia
pinia 状态配置
在 src 目录下,新建 store 文件夹并创建 index.ts 文件;新建 modules 文件夹及 app.js,如下
src/store/index.ts 添加内容如下
import type { App } from 'vue';
import { createPinia } from 'pinia';
const store = createPinia();
// 注入 pinia
export function setupStore(app: App<Element>) {
app.use(store);
}
export { store };
src/store/modules/app.ts 添加内容如下
import { defineStore } from 'pinia';
interface AppState {
count: number;
}
export const useAppStore = defineStore({
// 必填项,store 唯一标识
id: 'app',
// 状态数据
state: (): AppState => ({
count: 0
}),
// 计算操作
getters: {
countPow2(): number {
return this.count ** 2;
}
},
// 方法操作
actions: {
increment() {
this.count++;
},
decrease() {
this.count--;
}
}
})
pinia 状态注入
src/main.ts 添加内容如下
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
import { setupStore } from '@/store';
const app = createApp(App);
// 挂载路由
app.use(router);
// 注入 store
setupStore(app);
app.mount('#app')
pinia 状态管理测试
首页路由配置修改,src/router/index.ts 添加修改内容部分如下
import Home from '@/views/index.vue';
// 初始化首页
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'index',
component: Home,
},
];
src/views/index.vue 添加测试 store 示例内容如下
<template>
<h2>home page</h2>
<div>count: {{appStore.count}}</div>
<div>countPow2: {{appStore.countPow2}}</div>
<button @click="appStore.increment">count + 1</button>
<button @click="appStore.decrease">count - 1</button>
</template>
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();
</script>
Vuex
vuex 安装
pnpm i vuex
vuex 状态配置
在 src 目录下,新建 store 文件夹并创建 index.ts 文件,添加内容如下
import { createStore, Store, useStore as useVuexStore } from 'vuex';
import { InjectionKey } from 'vue';
export interface State {
count: number;
}
export const key: InjectionKey<Store<State>> = Symbol('store-key'); // 页面中使用 state 时会有对应提示说明
// 处理 state 类型
export const useStore = () => {
return useVuexStore(key);
};
const store = createStore<State>({
// 数据源
state: {
count: 0
},
// 计算操作
getters: {},
// 同步操作
mutations: {
increment(state) {
state.count += 1;
}
},
// 异步操作
actions: {
incrementAction: ({ commit }) => {
setTimeout(() => {
commit('increment');
}, 2000);
}
},
// 模块划分
modules: {}
});
export default store;
vuex 状态注入
src/main.ts 添加内容如下
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
import store, { key } from '@/store';
const app = createApp(App);
// 挂载路由
app.use(router);
// 注入 store
app.use(store, key);
app.mount('#app')
vuex 状态管理测试
首页路由配置修改,src/router/index.ts 添加修改内容部分如下
import Home from '@/views/index.vue';
// 初始化首页
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'index',
component: Home,
},
];
src/views/index.vue 添加测试 store 示例内容如下
<template>
<h2>home page</h2>
<button @click="increment">同步 count is: {{ store.state.count }}</button>
<button @click="incrementAction"> 异步 count is: {{ store.state.count }}</button>
</template>
<script setup lang="ts">
import { useStore } from '../store';
const store = useStore();
// 同步操作
const increment = () => {
store.commit('increment');
};
// 异步操作
const incrementAction = () => {
store.dispatch('incrementAction');
};
</script>
网络请求
axios 安装
pnpm i axios
axios 封装
src 目录下新增 utils/http 文件目录及添加文件,具体内容
// 目录文件说明
.
└── http
├── Axios.ts // axios 封装类
├── constant.ts // 静态变量
└── index.ts
axios 测试
在 src/views/index.vue 文件 script 中添加下面测试内容
<script setup lang="ts">
import { defHttp } from '@/utils/http';
(async () => {
// 测试封装的 axios
const res = await defHttp.request({
url: '/api/sug?code=utf-8&q=电脑',
method: 'GET',
});
console.log(res);
})()
</script>
请求跨域 vite 代理配置,修改 proxy 配置内容如下:
// vite.config.ts
proxy: {
"/api": {
target: "https://suggest.taobao.com", // 目标地址
changeOrigin: true, // 修改源
secure: false, // ssl
rewrite: path => path.replace("/api/", "/")
}
}
全局环境变量配置
项目根目录下新建 .env.development 和 .env.production 环境配置文件 【可依据项目需要是否添加全局环境变量配置】
添加下面全局环境变量至
.env.development 和 .env.production 文件
# 定义请求的基础 URL,方便跨域请求时使用
VITE_GLOB_API_URL = /api
# 接口服务地址
VITE_PROXY = https://suggest.taobao.com
使用全局环境变量修改替换 proxy 配置相关信息
// vite.config.ts
...
import { loadEnv } from 'vite';
export default ({ mode }): UserConfigExport => {
const env = loadEnv(mode, process.cwd());
return {
...
server: {
...
proxy: {
// 依据环境获取 .env.development 或 .env.production 文件中的配置
[env.VITE_GLOB_API_URL]: {
// '/api'
target: env.VITE_PROXY, // 在 .env.development 中配置
changeOrigin: true, // 修改源
// ws: true, // if you want to proxy websockets
rewrite: path =>
path.replace(new RegExp(`^${env.VITE_GLOB_API_URL}`), '')
}
}
}
};
};
Mock 数据
PS:在开发环境选择使用 vite-plugin-mock 插件而不使用 mockjs 进行数据接口的模拟,原因是 mockjs 本地开发看不到请求参数和响应结果,所以本地 mock 采用 Node.js 中间件进行参数拦截,具体可参考 Mock 服务
安装
pnpm i mockjs -S
pnpm i vite-plugin-mock -D
PS:
- 在使用
pnpm安装mockjs的时候会出现下图peer内声明的包需要手动进行安装的提示信息,目前pnpm是无法自动安装的,需要自行手动进行安装。具体可查看 automatically install peerDependencies - 创建的
mock目录需和public目录同级,否则使用fetch可以访问(但结果不正确),使用axios访问404
手动安装
peer 内声明的依赖包
pnpm i rollup
vite.config.ts 添加 vite-plugin-mock 插件配置
import { viteMockServe } from 'vite-plugin-mock';
export default ({ command, mode }): UserConfigExport => {
...
return {
plugins: [
...
viteMockServe({
// default
mockPath: 'mock',
localEnabled: command === 'serve',
}),
],
...
}
}
mock 测试
PS:下面创建的 mock 测试目录需要和 public 目录处于同一级别
测试数据、接口方法
src 同级添加 mock 目录并创建 test.ts 文件,内容如下:
import { MockMethod } from 'vite-plugin-mock';
export default [
{
url: '/api/get',
method: 'get',
response: ({ query }) => {
return {
code: 0,
data: {
name: 'test get'
}
};
}
},
{
url: '/api/post',
method: 'post',
timeout: 2000,
response: {
code: 0,
data: {
name: 'test post'
}
}
}
] as MockMethod[];
src 目录下添加 api 目录进行接口调用封装,并添加 api/test/index.ts 测试模块文件,内容如下:
// api/test/index.ts
import { defHttp } from '@/utils/http';
enum Api {
// 测试接口
Test = '/api/sug?code=utf-8&q=电脑',
Get = '/api/get',
Post = '/api/post'
}
/**
* @description: 测试线上接口
*/
export const TestApi = () =>
defHttp.request({
url: Api.Test,
method: 'GET'
});
/**
* @description: 测试 mock get 方法
*/
export const MockGet = () => defHttp.get({ url: Api.Get });
/**
* @description: 测试 mock post 方法
*/
export const MockPost = () => defHttp.post({ url: Api.Post });
接口调用
在 src/views/index.vue 文件 script 调用接口内容如下,运行项目查看控制台信息或网络信息。
...
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';
import { TestApi, MockGet, MockPost } from '@/api/test';
(async () => {
// 测试封装的 axios
const res1 = await TestApi();
console.log('TestApi:', res1);
const res2 = await MockGet();
console.log('MockGet:', res2);
const res3 = await MockPost();
console.log('MockPost:', res3);
})()
const appStore = useAppStore();
</script>
CSS 预编译器
安装项目需要的 css 预编译器 stylus、sass/scss、less
pnpm i stylus -D
pnpm i sass -D
pnpm i less -D
详情示例代码可查看项目 vue3-vite-ts-pnpm 视图文件 views/index.vue 内容信息。
总结
文章知识回顾。我们可以了解到 vue 生态基本配置信息,主要包含以下内容知识:
vue-router动态路由配置pinia和vuex状态管理配置axios请求另类封装mock模拟接口数据css预编译器集成