Vite2 + Vue3 + TypeScript + Pinia搭建一套企业级的开发脚手架工具

1,079 阅读8分钟

介绍

一个使用Vite2 + Vue3 + pinia + TypeScript + ElementPlus 完整技术路线开发的项目。

特性

  • ✨脚手架工具:高效、快速的 Vite

  • 🔥前端框架:眼下最时髦的 Vue3

  • 🍍状态管理器:vue3新秀 Pinia,犹如 react zustand般的体验,友好的api和异步处理

  • 🏆开发语言:政治正确 TypeScript

  • 🎉UI组件:ElementUI开发者无障碍过渡使用 ElementPlus,熟悉的配方熟悉的味道

  • 🎨css样式:scsspostcss

  • 📖代码规范:EslintCommitlint

  • 🔒权限管理:页面级、菜单级、按钮级、接口级

  • ✊依赖按需加载:unplugin-auto-import,可自动导入使用到的vuevue-router等依赖

  • 💪组件按需导入:unplugin-vue-components,无论是第三方UI组件还是自定义组件都可实现自动按需导入以及TS语法提示

使用Vite快速创建脚手架

yarn create vite vue-data-report --template vue-ts

约束代码风格

vscode中settings.json中配置

// .vscode/settings.json 
{ 
    // 保存时 prettier 自动格式化 
    "editor.formatOnSave": true, 
    // 保存时自动启用 eslint --fix 自动修复 
    "editor.codeActionsOnSave": { 
        "source.fixAll": true 
     } 
}

安装Eslint依赖

yarn add eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser -D

安装prettier

yarn add eslint-config-prettier eslint-plugin-prettier prettier prettier-eslint -D

创建.prettierrc.js

module.exports = {
  semi: false,
  singleQuote: true,
  trailingComma: 'none',
  endOfLine: 'lf'
}

项目创建.eslintrc.js

npx eslint --init

修改.eslintrc.js配置

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true,
  },
  parser: 'vue-eslint-parser',
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended'
  ],
  parserOptions: {
    ecmaVersion: 12,
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },
  // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    '@typescript-eslint/ban-ts-ignore': 'off',
    '@typescript-eslint/no-unused-vars': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    'no-var': 'error',
    'prettier/prettier': 'error',
    // 禁止出现console
    'no-console': 'warn',
    // 禁用debugger
    'no-debugger': 'warn',
    // 禁止出现重复的 case 标签
    'no-duplicate-case': 'warn',
    // 禁止出现空语句块
    'no-empty': 'warn',
    // 禁止不必要的括号
    'no-extra-parens': 'off',
    // 禁止对 function 声明重新赋值
    'no-func-assign': 'warn',
    // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
    'no-unreachable': 'warn',
    // 强制所有控制语句使用一致的括号风格
    curly: 'warn',
    // 要求 switch 语句中有 default 分支
    'default-case': 'warn',
    // 强制尽可能地使用点号
    'dot-notation': 'warn',
    // 要求使用 === 和 !==
    eqeqeq: 'warn',
    // 禁止 if 语句中 return 语句之后有 else 块
    'no-else-return': 'warn',
    // 禁止出现空函数
    'no-empty-function': 'warn',
    // 禁用不必要的嵌套块
    'no-lone-blocks': 'warn',
    // 禁止使用多个空格
    'no-multi-spaces': 'warn',
    // 禁止多次声明同一变量
    'no-redeclare': 'warn',
    // 禁止在 return 语句中使用赋值语句
    'no-return-assign': 'warn',
    // 禁用不必要的 return await
    'no-return-await': 'warn',
    // 禁止自我赋值
    'no-self-assign': 'warn',
    // 禁止自身比较
    'no-self-compare': 'warn',
    // 禁止不必要的 catch 子句
    'no-useless-catch': 'warn',
    // 禁止多余的 return 语句
    'no-useless-return': 'warn',
    // 禁止变量声明与外层作用域的变量同名
    'no-shadow': 'off',
    // 允许delete变量
    'no-delete-var': 'off',
    // 强制数组方括号中使用一致的空格
    'array-bracket-spacing': 'warn',
    // 强制在代码块中使用一致的大括号风格
    'brace-style': 'warn',
    // 强制使用骆驼拼写法命名约定
    camelcase: 'warn',
    // 强制使用一致的缩进
    indent: 'off',
    // 强制在 JSX 属性中一致地使用双引号或单引号
    // 'jsx-quotes': 'warn',
    // 强制可嵌套的块的最大深度4
    'max-depth': 'warn',
    // 强制最大行数 300
    // "max-lines": ["warn", { "max": 1200 }],
    // 强制函数最大代码行数 50
    // 'max-lines-per-function': ['warn', { max: 70 }],
    // 强制函数块最多允许的的语句数量20
    'max-statements': ['warn', 100],
    // 强制回调函数最大嵌套深度
    'max-nested-callbacks': ['warn', 3],
    // 强制函数定义中最多允许的参数数量
    'max-params': ['warn', 3],
    // 强制每一行中所允许的最大语句数量
    'max-statements-per-line': ['warn', { max: 1 }],
    // 要求方法链中每个调用都有一个换行符
    'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }],
    // 禁止 if 作为唯一的语句出现在 else 语句中
    'no-lonely-if': 'warn',
    // 禁止空格和 tab 的混合缩进
    'no-mixed-spaces-and-tabs': 'warn',
    // 禁止出现多行空行
    'no-multiple-empty-lines': 'warn',
    // 禁止出现;
    semi: ['warn', 'never'],
    // 强制在块之前使用一致的空格
    'space-before-blocks': 'warn',
    // 强制在 function的左括号之前使用一致的空格
    // 'space-before-function-paren': ['warn', 'never'],
    // 强制在圆括号内使用一致的空格
    'space-in-parens': 'warn',
    // 要求操作符周围有空格
    'space-infix-ops': 'warn',
    // 强制在一元操作符前后使用一致的空格
    'space-unary-ops': 'warn',
    // 强制在注释中 // 或 /* 使用一致的空格
    // "spaced-comment": "warn",
    // 强制在 switch 的冒号左右有空格
    'switch-colon-spacing': 'warn',
    // 强制箭头函数的箭头前后使用一致的空格
    'arrow-spacing': 'warn',
    'prefer-const': 'warn',
    'prefer-rest-params': 'warn',
    'no-useless-escape': 'warn',
    'no-irregular-whitespace': 'warn',
    'no-prototype-builtins': 'warn',
    'no-fallthrough': 'warn',
    'no-extra-boolean-cast': 'warn',
    'no-case-declarations': 'warn',
    'no-async-promise-executor': 'warn',
  },
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly',
  },
}

项目新建.eslintignore

# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist

有的同学可能在项目中同时使用了eslintpretttier,结果这两个工具相互冲突。

冲突原因: vscodeeslintprettier插件,都开启了自动格式化和自动修复代码的功能。

解决格式化冲突的办法

解决Eslint 和 Prettier 之间的冲突

配置 husky + lint-staged

使用husky + lint-staged助力团队编码规范, husky&lint-staged 安装推荐使用 mrm, 它将根据 package.json 依赖项中的代码质量工具来安装和配置 husky 和 lint-staged,因此请确保在此之前安装并配置所有代码质量工具,如 Prettier 和 ESlint

安装husky + lint-staged

yarn add lint-staged husky -D

创建.husky目录并指定该目录为git hooks所在目录

package.json中添加prepare脚本

{ 
    "scripts": { 
        "prepare": "husky install",
        "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx"
     } 
}

通过yarn生成.husky目录

yarn 

注意:如果没有生成.husky目录,并报如下错误: fatal: not a git repository (or any of the parent directories): .git Done in 0.50s.那是因为没有生成.git文件,可以通过git init生成.git文件

添加git hooks

创建一条pre-commit hook

npx husky add .husky/pre-commit "npm run lint"

执行该命令后,会看到.husky目录下下新增了一个名为pre-commitshell脚本。 这样,在之后执行git commit命令时会先触发pre-commit这个脚本。 pre-commit脚本内容如下:

#!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run lint

注意:npm run lint命令根据你自己项目中script脚本而定,eslint --ext .js,.vue src在lint脚本中

commitlint安装与配置

yarn add @commitlint/cli @commitlint/config-conventional -D

规范commit message信息

类似的,我们也可以添加commit-msg钩子,来规范我们的commit message信息。

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

commitlint.config.js

module.exports = { 
    extends: [ '@commitlint/config-conventional' ], 
    rules: { 
        'type-enum': [2, 'always', [ 'build', 'ci', 'perf', 'feat', 'fix', 'refactor', 'docs', 'chore', 'style', 'revert', 'test' ]], 
        'type-case': [0], 'type-empty': [0], 'scope-empty': [0], 'scope-case': [0], 'subject-full-stop': [0], 'subject-case': [0, 'never'], 'header-max-length': [0, 'always', 72] 
     }
}

配置文件引用别名alias

直接修改vite.config.ts文件配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from 'path'

export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            '@': resolve(__dirname, 'src')
        }
    }
})

注意: 如果项目报找不到模块“vite”或其相应的类型声明。ts(2307),我也不知道怎么解决,哪位大佬指点一下呢?

Vue能力的支持

模板语法配合JSX语法,使用起来非常方便、灵活

一些必须的插件

{ 
    "@vitejs/plugin-legacy": "^1.6.2", // 低版本浏览器兼容 
    "@vitejs/plugin-vue": "^1.9.3", // vue 支持 
    "@vitejs/plugin-vue-jsx": "^1.2.0", // jsx 支持 
}

引入jsx

yarn add @vitejs/plugin-vue-jsx -D
// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vueJsx({
      // options are passed on to @vue/babel-plugin-jsx
    })
  ]
})

如果我们想在.vue文件中写jsx语法,我们可以通过修改script中的lang属性为jsx或者tsx

<script lang="tsx">
import {defineComponent} from 'vue'
export default defineComponent({
    render() {
        return <div>Hello</div>
    }
})
</script>

<style lang="scss" scoped>
// 书写样式
</style>

为打包后的文件提供传统浏览器兼容性支持

yarn add @vitejs/plugin-legacy -D
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default {
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
}

引入ElementPlus

安装依赖

yarn add element-plus

首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件。

yarn add unplugin-vue-components unplugin-auto-import -D

修改配置

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
}

引入tailwindcss

yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest @fullhuman/postcss-purgecss

配置postcss.config.js

const purgecss = require('@fullhuman/postcss-purgecss')({
  content: ['./src/**/*.html', './src/**/*.vue', './src/**/*.jsx'],

  defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || []
})

module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    ...(process.env.NODE_ENV === 'production' ? [purgecss] : [])
  ]
}

配置tailwind.config.js

module.exports = {
  purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  content: [],
  theme: {
    extend: {}
  },
  plugins: []
}

引入tailwindcss

新建一个tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;

main.ts中引入:

import './style/tailwind.css'

引入scss

yarn add sass -D

如何在Vue组件中使用tailwindcss

<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
</script>

<template>
  <div class="app">
    <div class="h-12 w-screen bg-black"></div>
    <div class="h-12 w-screen bg-white flex items-center px-4">
      <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/' }"
          >统一登录平台</el-breadcrumb-item
        >
        <el-breadcrumb-item><a href="/">商户管理平台</a></el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <!-- 项目情况, 数据指标 -->
    <div class="container mx-auto mt-2.5 flex">
      <div class="h-48 w-5/12 bg-white"></div>
      <div class="h-48 w-7/12 ml-2.5 bg-white"></div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.app {
 // 通过apply引入tailwindcss
  @apply bg-gray-100;
  @apply h-screen;
}
</style>

引入SvgIcon

安装依赖vite-plugin-svg-icons

yarn add vite-plugin-svg-icons -D

配置插件

import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'

export default () => {
  return {
    plugins: [
      createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹
        iconDirs: [path.resolve(process.cwd(), 'src/icons')],
        // 指定symbolId格式
        symbolId: 'icon-[dir]-[name]',

        /**
         * 自定义插入位置
         * @default: body-last
         'body-last' | 'body-first'
         */
        inject: 'body-last',

        /**
         * custom dom id
         * @default: __svg__icons__dom__
         */
        customDomId: '__svg__icons__dom__',
      }),
    ],
  }
}

src/main.ts内引入注册脚本。

import 'virtual:svg-icons-register'

创建SvgIcon组件

<template>
  <svg aria-hidden="true" :width="size" :height="size">
    <use :xlink:href="symbolId" :fill="color" />
  </svg>
</template>

<script>
import { defineComponent, computed } from 'vue'

export default defineComponent({
  name: 'SvgIcon',
  props: {
    prefix: {
      type: String,
      default: 'icon',
    },
    name: {
      type: String,
      required: true,
    },
    color: {
      type: String,
      default: '#333',
    },
    size: {
      type: [String, Number],
      default: 16
    }
  },
  setup(props) {
    const symbolId = computed(() => `#${props.prefix}-${props.name}`)
    return { symbolId }
  },
})
</script>

TypeScript支持

// tsconfig.json
{
  "compilerOptions": {
    "types": ["vite-plugin-svg-icons/client"]
  }
}

配置css预处理器scss

安装

yarn add sass -D

配置全局scss样式文件

src下新增style文件夹,用于存放全局样式文件。在style中新增一个variable.scss文件,设置一个用于测试的颜色变量:

$test-color: red;

如何将这个全局样式文件全局注入到项目中呢?配置 Vite 即可:

css:{
    preprocessorOptions:{
      scss:{
        additionalData:'@import "@/assets/style/variable.scss";'
      }
    }
  },

组件中使用

不需要任何引入可以直接使用全局scss定义的变量

.test{
  color: $test-color;
}

路由

安装

# 安装路由
yarn add vue-router@4

src文件下新增router文件夹 => router.ts文件,内容如下:

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Login',
    component: () => import('@/pages/login/Login.vue'), // 注意这里要带上 文件后缀.vue
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

export default router


统一请求封装

安装依赖

# 安装 axios
yarn add axios
# 安装 nprogress 用于请求 loading
# 也可以根据项目需求自定义其它 loading
yarn add nprogress
# 类型声明,或者添加一个包含 `declare module 'nprogress'
yarn add @types/nprogress --dev

新增utils文件夹,在utils下新增request.ts文件用于axios封装。

// 统一请求封装
import axios, { AxiosRequestConfig, AxiosInstance } from 'axios'
import NProgress from 'nprogress'

// 获取axios请求实例
const instance: AxiosInstance = axios.create({
  baseURL: '/api',
  timeout: 10000
})

// 请求拦截器
instance.interceptors.request.use(
  (config): AxiosRequestConfig<any> => {
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    NProgress.done()
    const { data } = response
    return data
  },
  (error) => {
    NProgress.done()
    return Promise.reject(error)
  }
)

const request = Object.create(instance)

// 对get, post, put, delete等常用方法作了封装
request.get = function (url: string, data = {}, config = {}) {
  NProgress.start()
  return instance.get(url, { params: data, ...config })
}

request.post = function (url: string, data = {}, config = {}) {
  NProgress.start()
  return instance.post(url, data, config)
}

request.put = function (url: string, data = {}, config = {}) {
  NProgress.start()
  return instance.put(url, data, config)
}

request.delete = function (url: string, data = {}, config = {}) {
  NProgress.start()
  return instance.delete(url, { params: data, ...config })
}

export default request

状态管理pinia

安装pinia

# 安装
yarn add pinia@next

main.ts中增加

// 引入
import { createPinia } from "pinia"
// 创建根存储库并将其传递给应用程序
app.use(createPinia())

创建store/main.ts

import { defineStore } from 'pinia'

export const useMainStore = defineStore({
  id: 'main',
  state: () =>({
    name: '超级管理员'
  })
})

组件中获取store

<template>
  <div>{{mainStore.name}}</div>
</template>

<script setup lang="ts">
import { useMainStore } from "@/store/main"

const mainStore = useMainStore()

</script>

getters用法

Pinia中的getter与Vuex中的getter,组件中的计算属性具有相同的功能

import { defineStore } from 'pinia'

export const useMainStore = defineStore({
  id: 'mian',
  state: () => ({
    name: '超级管理员',
  }),
  // getters
  getters: {
    nameLength: (state) => state.name.length,
  }
})

在组件中的的使用:

<template>
  <div>用户名:{{ mainStore.name }}<br />长度:{{ mainStore.nameLength }}</div>
  <hr/>
  <button @click="updateName">修改store中的name</button>
</template>

<script setup lang="ts">
import { useMainStore } from '@/store/mian'

const mainStore = useMainStore()

const updateName = ()=>{
  // $patch 修改 store 中的数据
  mainStore.$patch({
    name: '名称被修改了,nameLength也随之改变了'
  })
}
</script>

actions

这里与Vuex有极大的不同,Pinia仅提供了一种方法来定义如何更改状态的规则,放弃mutations只依靠Actions,这是一项重大的改变。

PiniaActions 更加的灵活:

  • 可以通过组件或其他 action 调用
  • 可以从其他 storeaction 中调用
  • 直接在 store 实例上调用
  • 支持同步异步
  • 有任意数量的参数
  • 可以包含有关如何更改状态的逻辑(也就是 vuex 的 mutations 的作用)
  • 可以 $patch 方法直接更改状态属性
import { defineStore } from 'pinia'

export const useMainStore = defineStore({
  id: 'mian',
  state: () => ({
    name: '超级管理员',
  }),
  getters: {
    nameLength: (state) => state.name.length,
  },
  actions: {
    async insertPost(data:string){
      // 可以做异步
      // await doAjaxRequest(data);
      this.name = data;
    }
  },
})


环境变量配置

Vite提供了两种模式:具有开发服务器的开发模式和生产模式

项目目录新建:.env.development

NODE_ENV=development

VITE_APP_WEB_URL= 'YOUR WEB URL'

项目根目录新建:.env.production :

NODE_ENV=production

VITE_APP_WEB_URL= 'YOUR WEB URL'

组件中使用环境变量:

console.log(import.meta.env.VITE_APP_WEB_URL)

配置package.json:

"build:dev": "vite build --mode development",
"build:pro": "vite build --mode production",

Vite常用基础配置

代理服务

server: {
    host: '0.0.0.0',
    port: 3000,
    open: true,
    https: false,
    proxy: {}
},

生产环境去除console debuger

build:{
  ...
  terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
  }
}

生产环境开启gzip压缩

开启 gzip 可以极大的压缩静态资源,对页面加载的速度起到了显著的作用。 使用 vite-plugin-compression 可以 gzipbrotli 的方式来压缩资源,这一步需要服务器端的配合,vite 只能帮你打包出 .gz 文件。此插件使用简单,你甚至无需配置参数,引入即可。

# 安装
yarn add --dev vite-plugin-compression
复制代码

plugins 中添加:

// vite.config.ts
import viteCompression from 'vite-plugin-compression'

// gzip压缩 生产环境生成 .gz 文件
viteCompression({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz',
    }),

最终vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import viteCompression from 'vite-plugin-compression'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  base: './', //打包路径
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    }),
    vue(),
    vueJsx({
      // options are passed on to @vue/babel-plugin-jsx
    }),
    legacy({
      targets: ['defaults', 'not IE 11']
    }),
    createSvgIconsPlugin({
      // 指定需要缓存的图标文件夹
      iconDirs: [resolve(process.cwd(), 'src/icons')],
      // 指定symbolId格式
      symbolId: 'icon-[dir]-[name]',

      /**
       * 自定义插入位置
       * @default: body-last
       * 'body-last' | 'body-first'
       */
      inject: 'body-last',

      /**
       * custom dom id
       * @default: __svg__icons__dom__
       */
      customDomId: '__svg__icons__dom__'
    }),
    // gzip压缩 生产环境生成 .gz 文件
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz'
    })
  ],
  // 配置别名
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  // 配置全局scss样式文件
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@import "@/style/variable.scss";'
      }
    }
  },
  //启动服务配置
  server: {
    host: '0.0.0.0',
    port: 8000,
    open: true,
    https: false,
    proxy: {}
  },
  // 生产环境打包配置
  //去除 console debugger
  build: {
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  }
})