我是靠谱客的博主 可靠戒指,这篇文章主要介绍vite + vue3 + setup + pinia + ts 项目实战,现在分享给大家,希望可以做个参考。

介绍

一个使用 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

emoji

特性

  • 脚手架工具:高效、快速的 Vite
  • ??前端框架:眼下最时髦的 Vue3
  • ??状态管理器:vue3新秀 Pinia,犹如 react zustand般的体验,友好的api和异步处理
  • ??开发语言:政治正确 TypeScript
  • ??UI组件:antd开发者无障碍过渡使用 ant-design-vue,熟悉的配方熟悉的味道
  • ??css样式:lesspostcss
  • ??代码规范:EslintPrettierCommitlint
  • ??权限管理:页面级、菜单级、按钮级、接口级
  • 依赖按需加载:unplugin-auto-import,可自动导入使用到的vuevue-router等依赖
  • ??组件按需导入:unplugin-vue-components,无论是第三方UI组件还是自定义组件都可实现自动按需导入以及TS语法提示

项目目录

复制代码
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
44
45
46
47
48
├── .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 配置使用

复制代码
1
2
3
4
5
6
7
8
# 提升一些依赖包至 node_modules # 解决部分包模块not found的问题 # 用于配合 pnpm shamefully-hoist = true # node-sass 下载问题 # sass_binary_site="https://npm.taobao.org/mirrors/node-sass/"

代码规范

工具:huskyeslintprettier

具体使用方式,网上很多,我在之前另一篇文章也有说过,这里不再赘述~

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,安装错误可能会无效哦~

复制代码
1
2
3
4
5
# 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语法,使用起来非常方便、灵活~

一些必须的插件

复制代码
1
2
3
4
5
6
{ // "@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;
    }

    // 创建 store
    export const useUserStore = defineStore({
    id: ‘app-user’, // 唯一 ID,可以配合 Vue devtools 使用
    state: (): UserState => ({
    // userInfo
    userInfo: null,
    }),
    getters: {
    getUserInfo(): Nullable {
    return this.userInfo || null;
    },
    },
    actions: {
    setUserInfo(info: Nullable) {
    this.userInfo = info ?? null;
    },
    resetState() {
    this.userInfo = null;
    },

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /** * @description: fetchUserInfo */ async fetchUserInfo(params: ReqParams) { const res = await fetchApi.userInfo(params); if (res) { this.setUserInfo(res); } },

    },
    })

组件中使用

复制代码
1
2
3
4
5
6
7
8
9
10
11
// 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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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` }, } ] }) ]

方案二:unplugin-vue-components

推荐使用 unplugin-vue-components 插件

该插件只需在 vite plugin中添加对应 AntDesignVueResolver 即可,也支持自定义的 components 自动注册,很方便

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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加载器,也没关系,也支持自定义配置

复制代码
1
2
3
4
5
6
7
8
9
10
11
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语法提示,开发效率杠杠的~

配置如下:

复制代码
1
2
3
4
5
6
7
8
9
10
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.tstype类型文件,用于语法提示与类型检测通过

  • 注意

"unplugin-vue-components": "^0.17.2"

当前版本已知问题:issues 174

对于 ant-design-vuenotification / 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相关 defineComponentcomputedwatch等模块依赖根据使用,插件自动导入,你无需关心 import,直接使用即可

该插件默认支持:

  • vue
  • vue-router
  • vue-i18n
  • @vueuse/head
  • @vueuse/core

当然你也可以自定义配置 unplugin-auto-import

用法如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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'

自定义主题

自定义主题设置参考官方文档配置即可,两种常规方式

  1. 按需加载配合 webpack/vite loader属性修改变量
  2. 全量引入,配合 variables.less自定义样式覆盖框架主题样式

这里我们采用第一种方法通过loader配置配合按需加载食用

vite项目下,请手动安装 less,pnpm add less -D

复制代码
1
2
3
4
5
6
7
8
9
css: { preprocessorOptions: { less: { modifyVars: { 'primary-color': 'red' }, javascriptEnabled: true, // 这是必须的 }, }, }

注意:在使用了 unplugin-vue-components进行按需加载配置后,相关 less变量设置需要同步开启 importStyle: 'less',unplugin-vue-components issues 160

复制代码
1
2
AntDesignVueResolver({ importStyle: 'less' }) // 这里很重要

mock数据

  • vite-plugin-mock 插件

vite plugin配置

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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文件

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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文件

如:

复制代码
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
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代理

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 如下

复制代码
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
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

复制代码
1
2
3
4
5
6
7
8
9
import visualizer from 'rollup-plugin-visualizer'; visualizer({ filename: './node_modules/.cache/visualizer/stats.html', open: true, gzipSize: true, brotliSize: true, })

代码压缩

插件:vite-plugin-compression

复制代码
1
2
3
4
5
6
7
import compressPlugin from 'vite-plugin-compression'; compressPlugin({ ext: '.gz', deleteOriginFile: false, })

Chunk 拆包

如果想把类似 ant-design-vue这样的包依赖单独拆分出来,也可以手动配置 manualChunks属性

复制代码
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
// 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浏览器

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
// Native ESM legacy({ targets: ['defaults', 'not IE 11'] }) // IE11 // 需要 regenerator-runtime legacy({ targets: ['ie >= 11'], additionalLegacyPolyfills: ['regenerator-runtime/runtime'] })

效果图

首页

vite-vue3-4


包依赖分析可视化,部分截图

vite-vue3-2


开启压缩、开启兼容后生产打包的产物

vite-vue3-1

路由和布局

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
// 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>

用法如下:

复制代码
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
44
45
46
47
48
49
50
51
52
53
// 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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 没有获取,请求数据 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指令控制按钮级别的权限

复制代码
1
2
3
4
5
6
7
8
9
10
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 添加

复制代码
1
2
3
4
5
6
7
8
9
// 接口权限拦截 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使用学习记录~

使用过之后会发现 vue3vue2有着完全不同的开发体验,现在的 vue3TS有着极好的支持,开发效率和质量上上升了一个层次啊,而且也支持 JSX语法,类似 React的形式开发也是可行的,当然,配合 vue模板使用时,也有着极大的灵活性,可自行根据场景定制自己的代码,在结合目前的 script setup开发,直接爽到起飞呀~

在使用 vue3composition api开发模式时,一定要摒弃之前的 options api的开发逻辑,配和 hooks可以自由组合拆分代码,灵活性极高,方便维护管理,不会再出现 vue2时代的整个代码都拧在一起的情况

一句话:vite + vue3 + setup + ts + vscode volar 插件,谁用谁知道,爽的一批~

仓库地址:https://github.com/JS-banana/vite-vue3-ts

参考

  • vue3
  • Pinia
  • Vue Router
  • vue-vben-admin
  • 一个简单的Vue按钮级权限方案
  • 手摸手,带你用vue撸后台 系列二(登录权限篇)
  • 前后端分离下前端权限处理

最后

以上就是可靠戒指最近收集整理的关于vite + vue3 + setup + pinia + ts 项目实战的全部内容,更多相关vite内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(110)

评论列表共有 0 条评论

立即
投稿
返回
顶部