共计 12483 个字符,预计需要花费 32 分钟才能阅读完成。
介绍
一个应用 vite
+ vue3
+ pinia
+ ant-design-vue
+ typescript
残缺技术路线开发的我的项目,秒级开发更新启动、新的 vue3 composition api
联合 setup
纵享丝滑般的开发体验、全新的 pinia
状态管理器和优良的设计体验(1k
的 size)、antd
无障碍过渡应用 UI 组件库 ant-design-vue
、平安高效的 typescript
类型反对、代码标准验证、多级别的权限治理~
前言
前两天接到了一个需要,就是把原来的一个我的项目的次要功能模块和用户模块权限零碎抽出来做一个新后盾我的项目,并迭代新增一些新性能,看起来如同也没啥货色
拿到源码看了下我的项目,好家伙,原我的项目是个微利用我的项目,主利用用户模块是 react
技术栈,子利用模块是 vue2
技术栈,这间接 CV 大法看样子是不行了👀,我这要做的毕竟是个单页面利用,确定一个技术路线即可,具体看下代码逻辑并跑起来看看
跑起来试了下,两个我的项目根本都是 1 分钟左右启动,看代码 vue 我的项目整个业务逻辑代码都拧在一块写了
想到之前问老大要源码的时候,说那个是老我的项目了,从新搭一个写应该会快点
这话没故障啊,话不多说,间接开整,这次间接上 vite
+ vue3
个性
- ✨脚手架工具:高效、疾速的 Vite
- 🔥前端框架:眼下最时尚的 Vue3
- 🍍状态管理器:
vue3
新秀 Pinia,犹如react zustand
般的体验,敌对的 api 和异步解决 - 🏆开发语言:政治正确 TypeScript
- 🎉UI 组件:
antd
开发者无障碍过渡应用 ant-design-vue,相熟的配方相熟的滋味 - 🎨css 款式:less、
postcss
- 📖代码标准:Eslint、Prettier、Commitlint
- 🔒权限治理:页面级、菜单级、按钮级、接口级
- ✊依赖按需加载:unplugin-auto-import,可主动导入应用到的
vue
、vue-router
等依赖 - 💪组件按需导入:unplugin-vue-components,无论是第三方 UI 组件还是自定义组件都可实现主动按需导入以及
TS
语法提醒
我的项目目录
├── .husky // husky git hooks 配置目录
├── _ // husky 脚本生成的目录文件
├── commit-msg // commit-msg 钩子,用于验证 message 格局
├── pre-commit // pre-commit 钩子,次要是和 eslint 配合
├── config // 全局配置文件
├── vite // vite 相干配置
├── constant.ts // 我的项目配置
├── themeConfig.ts // 主题配置
├── dist // 默认的 build 输入目录
├── mock // 前端数据 mock
├── public // vite 我的项目下的动态目录
└── src // 源码目录
├── api // 接口相干
├── assets // 公共的文件(如 image、css、font 等)├── components // 我的项目组件
├── directives // 自定义 指令
├── enums // 自定义 常量(枚举写法)├── hooks // 自定义 hooks
├── layout // 全局布局
├── router // 路由
├── store // 状态管理器
├── utils // 工具库
├── views // 页面模块目录
├── login // login 页面模块
├── ...
├── App.vue // vue 顶层文件
├── auto-imports.d.ts // unplugin-auto-import 插件生成
├── components.d.d.ts // unplugin-vue-components 插件生成
├── main.ts // 我的项目入口文件
├── shimes-vue.d.ts // vite 默认 ts 类型文件
├── types // 我的项目 type 类型定义文件夹
├── .editorconfig // IDE 格局标准
├── .env // 环境变量
├── .eslintignore // eslint 疏忽
├── .eslintrc // eslint 配置文件
├── .gitignore // git 疏忽
├── .npmrc // npm 配置文件
├── .prettierignore // prettierc 疏忽
├── .prettierrc // prettierc 配置文件
├── index.html // 入口文件
├── LICENSE.md // LICENSE
├── package.json // package
├── pnpm-lock.yaml // pnpm-lock
├── postcss.config.js // postcss
├── README.md // README
├── tsconfig.json // typescript 配置文件
└── vite.config.ts // vite
开发
我的项目初始化
如果应用 vscode 编辑器开发 vue3,请务必装置 Volar 插件与 vue3 配合应用更佳(与本来的 Vetur 不兼容)
应用 vite cli 疾速创立我的项目
yarn create vite project-name --template vue-ts
装置相干依赖
举荐应用新一代 pnpm
包管理工具,性能和速度以及 node_modules
依赖治理都很优良
倡议配合 .npmrc
配置应用
# 晋升一些依赖包至 node_modules
# 解决局部包模块 not found 的问题
# 用于配合 pnpm
shamefully-hoist = true
# node-sass 下载问题
# sass_binary_site="https://npm.taobao.org/mirrors/node-sass/"
代码标准
工具:husky
、eslint
、prettier
具体应用形式,网上很多,我在之前另一篇文章也有说过,这里不再赘述~
a Vite2 + Typescript + React + Antd + Less + Eslint + Prettier + Precommit template
次要就是自动化的概念,在一个适合的机会实现规定的事
- 联合 VsCode 编辑器(保留时主动执行格式化:
editor.formatOnSave: true
) - 配合 Git hooks 钩子(commit 前或提交前执行:
pre-commit
=>npm run lint:lint-staged
)
留神:
针对不同零碎 commitlint
装置形式有所不同 commitlint,装置谬误可能会有效哦~
# Install commitlint cli and conventional config
npm install --save-dev @commitlint/{config-conventional,cli}
# For Windows:
npm install --save-dev @commitlint/config-conventional @commitlint/cli
性能
vue 能力反对
模板 语法配合 jsx 语法,应用起来十分不便、灵便~
一些必须的插件
{
// "@vitejs/plugin-legacy": "^1.6.2", // 低版本浏览器兼容
"@vitejs/plugin-vue": "^1.9.3", // vue 反对
"@vitejs/plugin-vue-jsx": "^1.2.0", // jsx 反对
}
状态管理器 Pinia
vue 新一代状态管理器,用过 react zustand
的同学应该会有很相熟的感觉
Pinia 是一个围绕 Vue 3 Composition API 的封装器。因而,你不用把它作为一个插件来初始化,除非你须要 Vue devtools 反对、SSR 反对和 webpack 代码宰割的状况
- 十分轻量化,仅有 1 KB
- 直观的 API 应用,合乎直觉,易于学习
- 模块化设计,便于拆分状态
- 全面的 TS 反对
// ... 引入相干依赖
interface IUserInfoProps{
name: string;
avatar: string;
mobile: number;
auths: string[]}
interface UserState {userInfo: Nullable<IUserInfoProps>;}
// 创立 store
export const useUserStore = defineStore({
id: 'app-user', // 惟一 ID,能够配合 Vue devtools 应用
state: (): UserState => ({
// userInfo
userInfo: null,
}),
getters: {getUserInfo(): Nullable<IUserInfoProps> {return this.userInfo || null;},
},
actions: {setUserInfo(info: Nullable<IUserInfoProps>) {this.userInfo = info ?? null;},
resetState() {this.userInfo = null;},
/**
* @description: fetchUserInfo
*/
async fetchUserInfo(params: ReqParams) {const res = await fetchApi.userInfo(params);
if (res) {this.setUserInfo(res);
}
},
},
})
组件中应用
// TS 类型推断、异步函数应用都很不便
import {useHomeStore} from '/@/store/modules/home';
const store = useHomeStore();
const userInfo = computed(() => store.getUserInfo);
onMounted(async () => {await store.fetchInfo(); // 异步函数
// ...
});
UI 组件按需加载、主动导入
理解基本概念:vite 自带按需加载(针对 js),咱们这里次要针对款式做按需加载解决
计划一:vite-plugin-style-import
import styleImport from 'vite-plugin-style-import'
//
plugins:[
styleImport({
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {return `ant-design-vue/es/${name}/style/index`
},
}
]
})
]
<!– 不过,我之前在写 vite
+ react
时,应用 vite-plugin-style-import
这个插件遇到过热更新款式不同步的一些问题 –>
计划二:unplugin-vue-components
举荐应用 unplugin-vue-components 插件
该插件只需在 vite plugin 中增加对应 AntDesignVueResolver
即可,也反对自定义的 components
主动注册,很不便
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
// vite.config.ts plugins 增加如下配置
export default defineConfig({
plugins: [
Components({
resolvers: [AntDesignVueResolver(), // ant-design-vue
// ElementPlusResolver(), // Element Plus
// VantResolver(), // Vant]
})
]
})
当然这里如果没有你应用的对应的 UI 框架的 Resolver
加载器,也没关系,也反对自定义配置
Components({
resolvers: [
// example of importing Vant
(name) => {
// where `name` is always CapitalCase
if (name.startsWith('Van'))
return {importName: name.slice(3), path: 'vant' }
}
]
})
另一强悍性能:该插件不仅反对 UI 框架组件的按需导入,也反对我的项目组件的主动按需导入
具体表现就是:如咱们应用 ant-design-vue 的 Card 组件或咱们本人定义的 components/Icon 等其余组件时,咱们不必导入,间接用即可,插件会为咱们主动按需导入,联合 TS 语法提醒,开发效率杠杠的~
配置如下:
Components({
// allow auto load markdown components under `./src/components/`
extensions: ['vue'],
// allow auto import and register components
include: [/\.vue$/, /\.vue\?vue/],
dts: 'src/components.d.ts',
})
须要在 src
目录下增加 components.d.ts
文件配合应用,该文件会被插件自动更新
components.d.ts
作用
间接的作用是:在我的项目下生成对应.d.ts
type 类型文件,用于语法提醒与类型检测通过
- 留神
"unplugin-vue-components": "^0.17.2"
以后版本已知问题:issues 174
对于 ant-design-vue
的 notification
/ message
组件,当在 js
中应用时,该插件不会执行主动导入能力(款式不会被导入)
最终成果是:message.success('xx')
能够创立 DOM
元素,然而没有相干款式代码
因为该插件的设计原理是依据 vue template
模板中的组件应用进行解决的,函数式调用时插件查问不到
解决方案:
- 改用
vite-plugin-style-import
插件 - 手动全局引入 message 组件款式,
import 'ant-design-vue/es/message/style'
- 在 vue 组件的 template 中手动增加
<a-message />
供插件索引依赖时应用
依赖按需主动导入
- unplugin-auto-import
vue 相干 defineComponent
、computed
、watch
等模块依赖依据应用,插件主动导入,你无需关怀 import
,间接应用即可
该插件默认反对:
vue
vue-router
vue-i18n
@vueuse/head
@vueuse/core
- …
当然你也能够自定义配置 unplugin-auto-import
用法如下:
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
// ...
plugins: [
AutoImport({
imports: [
'vue',
'vue-router',
'vue-i18n',
'@vueuse/head',
'@vueuse/core',
],
dts: 'src/auto-imports.d.ts',
})
]
})
须要在 src
目录下增加 auto-imports.d.ts
文件配合应用,该文件会被插件自动更新
最终成果为:
如 ref
办法咱们能够间接应用并有相应的 TS 语法提醒,而不须要手动的去 import {ref} from 'vue'
自定义主题
自定义主题设置参考官网文档配置即可,两种惯例形式
- 按需加载配合
webpack/vite
loader 属性批改变量 - 全量引入,配合
variables.less
自定义款式笼罩框架主题款式
这里咱们采纳第一种办法通过 loader 配置配合按需加载食用
vite 我的项目下,请手动装置 less,
pnpm add less -D
css: {
preprocessorOptions: {
less: {modifyVars: { 'primary-color': 'red'},
javascriptEnabled: true, // 这是必须的
},
},
}
留神 :在应用了 unplugin-vue-components
进行按需加载配置后,相干 less 变量设置须要同步开启 importStyle: 'less'
,unplugin-vue-components issues 160
AntDesignVueResolver({importStyle: 'less'}) // 这里很重要
mock 数据
- vite-plugin-mock 插件
vite plugin 配置
viteMockServe({
ignore: /^\_/,
mockPath: 'mock',
localEnabled: true,
prodEnabled: false,
// 开发环境无需关怀
// injectCode 只受 prodEnabled 影响
// https://github.com/anncwb/vite-plugin-mock/issues/9
// 上面这段代码会被注入 main.ts
injectCode: `
import {setupProdMockServer} from '../mock/_createProductionServer';
setupProdMockServer();
`,
})
根目录下创立 _createProductionServer.ts
文件
import {createProdMockServer} from 'vite-plugin-mock/es/createProdMockServer';
// 批量加载
const modules = import.meta.globEager('./**/*.ts');
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {if (key.includes('/_')) {return;}
mockModules.push(...modules[key].default);
});
/**
* Used in a production environment. Need to manually import all modules
*/
export function setupProdMockServer() {createProdMockServer(mockModules);
}
这样 mock 目录下的非 _
结尾文件都会被主动加载成 mock 文件
如:
import Mock from 'mockjs';
const data = Mock.mock({
'items|30': [
{
id: '@id',
title: '@sentence(10, 20)',
account: '@phone',
true_name: '@name',
created_at: '@datetime',
role_name: '@name',
},
],
});
export default [
{
url: '/table/list',
method: 'get',
response: () => {
const items = data.items;
return {
code: 0,
result: {
total: items.length,
list: items,
},
};
},
},
];
配置好代理间接申请 /api/table/list
就能够失去数据了
Proxy 代理
import proxy from './config/vite/proxy';
export default defineConfig({
// server
server: {hmr: { overlay: false}, // 禁用或配置 HMR 连贯 设置 server.hmr.overlay 为 false 能够禁用服务器谬误遮罩层
// 服务配置
port: VITE_PORT, // 类型:number 指定服务器端口;
open: false, // 类型:boolean | string 在服务器启动时主动在浏览器中关上应用程序;cors: false, // 类型:boolean | CorsOptions 为开发服务器配置 CORS。默认启用并容许任何源
host: '0.0.0.0', // 反对从 IP 启动拜访
proxy,
},
})
proxy 如下
import {
API_BASE_URL,
API_TARGET_URL,
} from '../../config/constant';
import {ProxyOptions} from 'vite';
type ProxyTargetList = Record<string, ProxyOptions>;
const ret: ProxyTargetList = {
// test
[API_BASE_URL]: {
target: API_TARGET_URL,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${API_BASE_URL}`), ''),
},
// mock
// [MOCK_API_BASE_URL]: {
// target: MOCK_API_TARGET_URL,
// changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp(`^${MOCK_API_BASE_URL}`), '/api'),
// },
};
export default ret;
环境变量 .env
我这边是把系统配置放到 config/constant.ts
治理了
为了方便管理不同环境的接口和参数配置,能够应用环境变量 .env,如 .env、.env.local、.env.development、.env.production
配合 dotenv
库 应用还是很不便的
包依赖剖析可视化
插件:rollup-plugin-visualizer
import visualizer from 'rollup-plugin-visualizer';
visualizer({
filename: './node_modules/.cache/visualizer/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
})
代码压缩
插件:vite-plugin-compression
import compressPlugin from 'vite-plugin-compression';
compressPlugin({
ext: '.gz',
deleteOriginFile: false,
})
Chunk 拆包
如果想把相似 ant-design-vue
这样的包依赖独自拆分进去,也能够手动配置 manualChunks
属性
// vite.config.ts
build: {
rollupOptions: {
output: {manualChunks: configManualChunk}
}
}
// optimizer.ts
const vendorLibs: {match: string[]; output: string }[] = [
{match: ['ant-design-vue'],
output: 'antdv',
},
{match: ['echarts'],
output: 'echarts',
},
];
export const configManualChunk = (id: string) => {if (/[\\/]node_modules[\\/]/.test(id)) {const matchItem = vendorLibs.find((item) => {const reg = new RegExp(`[\\/]node_modules[\\/]_?(${item.match.join('|')})(.*)`, 'ig');
return reg.test(id);
});
return matchItem ? matchItem.output : null;
}
};
兼容解决
插件:@vitejs/plugin-legacy
兼容不反对 <script type="module">
个性的浏览器,或 IE 浏览器
// Native ESM
legacy({targets: ['defaults', 'not IE 11']
})
// IE11
// 须要 regenerator-runtime
legacy({targets: ['ie >= 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
效果图
首页
包依赖剖析可视化,局部截图
开启压缩、开启兼容后生产打包的产物
路由和布局
// router/index.ts
import {createRouter, createWebHashHistory} from 'vue-router'
import routes from './router.config'
const router = createRouter({history: createWebHashHistory(), //
routes,
})
// main.ts
app.use(router); // 挂载后可全局应用实列,如模板中 <div @click="$router.push('xx')"></div>
用法如下:
// router.config.ts
import BasicLayout from '/@/layouts/BasicLayout/index.vue'; // 根本布局
import BlankLayout from '/@/layouts/BlankLayout.vue'; // 空布局
import type {RouteRecordRaw} from 'vue-router';
const routerMap: RouteRecordRaw[] = [
{
path: '/app',
name: 'index',
component: BasicLayout,
redirect: '/app/home',
meta: {title: '首页'},
children: [
{
path: '/app/home',
component: () => import('/@/views/home/index.vue'),
name: 'home',
meta: {
title: '首页',
icon: 'liulanqi',
auth: ['home'],
},
},
{
path: '/app/others',
name: 'others',
component: BlankLayout,
redirect: '/app/others/about',
meta: {
title: '其余菜单',
icon: 'xitongrizhi',
auth: ['others'],
},
children: [
{
path: '/app/others/about',
name: 'about',
component: () => import('/@/views/others/about/index.vue'),
meta: {title: '对于', keepAlive: true, hiddenWrap: true},
},
{
path: '/app/others/antdv',
name: 'antdv',
component: () => import('/@/views/others/antdv/index.vue'),
meta: {title: '组件', keepAlive: true, breadcrumb: true},
},
],
},
]
}
...
]
权限
- 反对页面和菜单级别的权限治理、路由治理
- 反对按钮级别的权限治理
- 反对接口级别的权限治理
几个关键词:router.addRoutes
动静路由、v-auth
指令、axios
拦挡
应用 router.beforeEach
全局路由钩子
外围逻辑如下,详情见仓库代码 router/permission.ts
// 没有获取,申请数据
await permissioStore.fetchAuths();
// 过滤权限路由
const routes = await permissioStore.buildRoutesAction();
// 404 路由肯定要放在 权限路由前面
routes.forEach((route) => {router.addRoute(route);
});
// hack 办法
// 不应用 next() 是因为,在执行完 router.addRoute 后,// 本来的路由表内还没有增加进去的路由,会 No match
// replace 使路由从新进入一遍,进行匹配即可
next({...to, replace: true});
应用 v-auth
指令管制按钮级别的权限
function isAuth(el: Element, binding: any) {const { hasPermission} = usePermission();
const value = binding.value;
if (!value) return;
if (!hasPermission(value)) {el.parentNode?.removeChild(el);
}
}
axios
拦挡
在 axios
申请拦截器 interceptors.request.use
增加
// 接口权限拦挡
const store = usePermissioStoreWithOut();
const {url = ''} = config;
if (!WhiteList.includes(url) && store.getIsAdmin === 0) {if (!store.getAuths.includes(url)) {return Promise.reject('没有操作权限');
}
}
总结
在开始应用 vite
+ vue3
的时候,也是边踩坑边学习开发的过程,好在当初社区比拟沉闷,很多问题都有对应的解决方案,配合文档和 github issue
一起食用根本 ok,该我的项目也是参考了 vue-vben-admin
的一些实现和代码治理,本文作为 vue3
应用学习记录~
应用过之后会发现 vue3
和 vue2
有着齐全不同的开发体验,当初的 vue3
对 TS
有着极好的反对,开发效率和品质上回升了一个档次啊,而且也反对 JSX 语法,相似 React
的模式开发也是可行的,当然,配合 vue 模板应用时,也有着极大的灵活性,可自行依据场景定制本人的代码,在联合目前的 script setup
开发,间接爽到腾飞呀~
在应用 vue3
的 composition api
开发模式时,肯定要摒弃之前的 options api
的开发逻辑,配和 hooks
能够自由组合拆分代码,灵活性极高,不便保护治理,不会再呈现 vue2
时代的整个代码都拧在一起的状况
一句话:vite
+ vue3
+ setup
+ ts
+ vscode volar
插件,谁用谁晓得,爽的一批~
仓库地址:https://github.com/JS-banana/…
参考
- vue3
- Pinia
- Vue Router
- vue-vben-admin
- 一个简略的 Vue 按钮级权限计划
- 手摸手,带你用 vue 撸后盾 系列二(登录权限篇)
- 前后端拆散下前端权限解决