介绍

一个应用 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款式:lesspostcss
  • 代码标准:EslintPrettierCommitlint
  • 权限治理:页面级、菜单级、按钮级、接口级
  • ✊依赖按需加载:unplugin-auto-import,可主动导入应用到的vuevue-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的问题# 用于配合 pnpmshamefully-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,装置谬误可能会有效哦~

# Install commitlint cli and conventional confignpm 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>;}// 创立 storeexport 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.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

用法如下:

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
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.tsbuild: {  rollupOptions: {    output: {      manualChunks: configManualChunk    }  }}
// optimizer.tsconst 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 ESMlegacy({  targets: ['defaults', 'not IE 11']})// IE11// 须要 regenerator-runtimelegacy({  targets: ['ie >= 11'],  additionalLegacyPolyfills: ['regenerator-runtime/runtime']})

效果图

首页


包依赖剖析可视化,局部截图


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

路由和布局

// router/index.tsimport { createRouter, createWebHashHistory } from 'vue-router'import routes from './router.config'const router = createRouter({  history: createWebHashHistory(), //  routes,})// main.tsapp.use(router); // 挂载后可全局应用实列,如模板中 <div @click="$router.push('xx')"></div>

用法如下:

// router.config.tsimport 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应用学习记录~

应用过之后会发现 vue3vue2有着齐全不同的开发体验,当初的 vue3TS有着极好的反对,开发效率和品质上回升了一个档次啊,而且也反对 JSX语法,相似 React的模式开发也是可行的,当然,配合 vue模板应用时,也有着极大的灵活性,可自行依据场景定制本人的代码,在联合目前的 script setup开发,间接爽到腾飞呀~

在应用 vue3composition 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撸后盾 系列二(登录权限篇)
  • 前后端拆散下前端权限解决