Vue3 现已成为新的默认版本,这个开箱即用的Vue3模板它不香吗?

31,085

前言

image.png

2月7日,Vue3 正式成为了新的默认版本, 可以预见,接下来 Vue3 相关的生态会迅速的发展,大伙们也可以开始学习 Vue3 准备新一轮的卷了。而项目的开始,少了一个开箱即用的模板怎么行呢?我结合者 Vue3 新的生态,提前踩了踩坑撸了一个开箱即用的模板分享出来。

  • 这篇文章上半文为该模板开箱即用的功能概述

  • 下半文则为一些想自己动手配置的小伙伴讲解一下配置的过程

本文篇幅较长,难免有遗漏或者写错的地方,如有找到,欢迎在评论区提出讨论或者在 Github 上提出 issue ,有想补充贡献的,也欢迎提出PR

已配置好的开箱即用功能:

  • 编程语言TypeScript 4.x 、JavaScript

  • 前端框架Vue 3.x

  • 构建工具Vite 2.x

  • UI 框架Element Plus

  • 图标工具icones

  • CSS预编译Sass

  • CSS框架Windi CSS

  • HTTP工具Axios

  • 路由管理Vue Router 4.x

  • 状态管理Pinia

  • 代码规范EditorConifg、Prettier、ESLint、Airbnb JavaScript Style Guide

  • 提交规范husky、Commitlint 、lint-staged

还有一些其余的各种功能插件:

  • 实现自动按需加载墙裂推荐):unplugin-auto-import、unplugin-vue-components、unplugin-icons

  • 实现 SVG图标 的组件化vite-svg-loader

  • 让各种 API 支持响应式VueUse

  • 让加载页面时有所反馈NProgress

  • 支持 markdownvite-plugin-md

上述所有的功能都已经过配置和验证,使用模板之后就可以愉快的撸码了!

这个模板没有配置 GitHub Actions、变更文档、单元测试 等的内容,如果有需要可以查看我的另一篇文档自行配置 # 从零开始搭建规范的 TypeScript SDK 项目工程环境

Github

github.com/nekobc19989…

使用方法

食用模板前,建议大家先 fork本仓库 ,以获得最新的变更和补充,如果本模板对你有帮助,顺手点一个 star 呀

下载

image.png

  • 点击 Use this template 按钮,就可以跳转到创建项目的地址
  • 或者直接使用 git clone
git clone https://github.com/nekobc1998923/vitecamp.git

安装

npm install

运行

npm run dev

打包

npm run build

vetur -> volar

额外提一句,对于 vue3 的支持,vetur 很明显的不如 volar,这边建议小伙伴们禁用vuetur而使用volor

如果同时还维护着 vue2 的项目,可以使用 vscode 的工作区功能进行单独使用

image.png

配置流程:

下面是整个模板的一个配置流程,如果有需要 自己动手配置一个 的可以往下接着阅读~

代码目录结构

image.png

整个模板代码目录的结构如图所示:

  • .husky:用来放husky 钩子的配置文件夹
  • .vscode:用来放项目中的 vscode 配置
  • presets:用来放 vite 插件的 plugin 配置
  • public:用来放一些诸如 页头icon 之类的公共文件,会被打包到dist根目录下
  • src:用来放项目代码文件
  • api:用来放http的一些接口配置
  • assets:用来放一些 CSS 之类的静态资源
  • components:用来放 Vue 组件
  • layout:用来放项目的布局
  • router:用来放项目的路由配置
  • store:用来放状态管理Pinia的配置
  • utils:用来放项目中的工具方法类
  • views:用来放项目的.vue视图

Vite

vite 初始化

vite 创建一个 vue3 + typescript 基本项目,只需要一条短短的指令,这里的 my-vue-app 需要改成你的项目名:

npm init vite@latest my-vue-app --template vue-ts
使用 alias 别名

别忘了先安装 @types/node 哦~

我们配置 vite.config.ts

resolve: {
  alias: {
    '@': resolve(__dirname, './src'), // 把 @ 指向到 src 目录去
  },
},

配置完后别忘了 tsconfig.json 也需要配置哦

配置 vite 服务设置

我们配置 vite.config.ts ,加入以下配置

值得注意的是:我们只有配置了 host 之后,我们本地才可以通过 ip 进行访问项目

    server: {
      host: true, // host设置为true才可以使用network的形式,以ip访问项目
      port: 8080, // 端口号
      open: true, // 自动打开浏览器
      cors: true, // 跨域设置允许
      strictPort: true, // 如果端口已占用直接退出
      // 接口代理
      proxy: {
        '/api': {
          // 本地 8000 前端代码的接口 代理到 8888 的服务端口
          target: 'http://localhost:8888/',
          changeOrigin: true, // 允许跨域
          rewrite: (path) => path.replace('/api/', '/'),
        },
      },
    },
配置 build 配置

我们可以设置打包时移除代码中的 console ,以及配置打包后的静态资源到 dist 下不同的目录

    build: {
      brotliSize: false,
      // 消除打包大小超过500kb警告
      chunkSizeWarningLimit: 2000,
      // 在生产环境移除console.log
      terserOptions: {
        compress: {
          drop_console: false,
          pure_funcs: ['console.log', 'console.info'],
          drop_debugger: true,
        },
      },
      assetsDir: 'static/assets',
      // 静态资源打包到dist下的不同目录
      rollupOptions: {
        output: {
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
        },
      },
    },
配置 vite 环境变量

经常,我们需要去区分生产环境和开发环境,我们在根目录下新建三个文件用来放环境变量:

  • .env
  • .env.development
  • .env.production

其中 .env 配置如下:

VITE_API_BASEURL = /api

VITE_BASE = /base-vue3/

VITE_APP_TITLE  = base-vue3

VITE_API_BASEURL 是项目的api基础前缀

VITE_BASE 是项目的基础路径前缀

VITE_APP_TITLE 是项目的网页标题

自动按需加载 api & 组件 & 样式

  • 你是否厌烦了每次使用 vue 时,需要不断的 import vue 的 api
  • 你是否厌烦了每次使用 组件库 时,需要不断的 按需引入 组件
  • 你是否厌烦了有时使用 第三方组件库 时,需要额外引入 css 样式

现在有两个插件可以帮我们解决这些问题,我们在调用时可以不需要import而直接使用,且最终打包时,只有实际使用过的api和组件才会被build进最终产物,它们就是 unplugin-auto-importunplugin-vue-components

unplugin-auto-import:自动按需引入 vue\vue-router\pinia 等的api

unplugin-vue-components:自动按需引入 第三方的组件库组件我们自定义的组件

更加详细的内容 推荐阅读juejin.cn/post/706264…

安装配置

我们先安装依赖:

npm install -D unplugin-vue-components unplugin-auto-import

然后我们配置 vite.config.ts 加入以下内容;

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

export default {
  plugins: [
    // ...
    AutoImport({
      dts: './src/auto-imports.d.ts',
      imports: ['vue', 'pinia', 'vue-router', '@vueuse/core'],
      // Generate corresponding .eslintrc-auto-import.json file.
      // eslint globals Docs - https://eslint.org/docs/user-guide/configuring/language-options#specifying-globals
      eslintrc: {
        enabled: true, // Default `false`
        filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
        globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
      },
    }),
    Components({
      dts: './src/components.d.ts',
      // imports 指定组件所在位置,默认为 src/components
      dirs: ['src/components/'],
    }),
  ],
}

因为我们使用了 ESLint 我们还需要去 .eslintrc.js 中配置:

// .eslintrc.js

module.exports = { 
  /* ... */
  extends: [
    // ...
    './.eslintrc-auto-import.json',
  ],
}

Element Plus

image.png

Element UI 不必我多说了大家都认识把,在使用 Vue2 搭建项目时使用的人数就已经非常多了,而 Element Plus 就是他们基于 Vue3 的组件库;Element Plus 于 2月7日,同 Vue3 一起成为正式版本,我试用了一下,正式版本还是比较好用的,值得推荐

安装配置

我们先安装 element-plus

npm install element-plus --save

然后我们在 vite.config.ts 中配置上文配置了的 AutoImportComponents中的 resolvers

// 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()],
    })
  ],
}

这样配置完之后,它就会去自动按需引入我们在代码中使用到的组件和对应的样式,是不是很便利呢

设置中文主题

Element Plus 默认的是英文,如果我们要使用中文主题,就可以在 APP.vue 中设置

<script setup lang="ts">
import zhCn from 'element-plus/lib/locale/lang/zh-cn';
const locale = zhCn;
</script>

<template>
  <el-config-provider :locale="locale">
    <router-view></router-view>
  </el-config-provider>
</template>

其余组件库

如果你想使用其余诸如 ant-design-vue 或者 Naive UI 等的组件库,也可以自行参考配置,值得一提的是,它们都支持配置成自动按需引入的形式

Ant Design of Vue

Naive UI

自动按需引入的,可以自行在这里参考配置:github.com/antfu/unplu…

@vitejs/plugin-vue-jsx

这个插件可以让我们支持 jsx 写法

npm i @vitejs/plugin-vue-jsx -D

然后配置 vite.config.ts

import vueJsx from '@vitejs/plugin-vue-jsx';

export default defineConfig({
  plugins: [...,vueJsx()]
})

@vitejs/plugin-legacy

npm i @vitejs/plugin-legacy -D

然后配置 vite.config.ts

import legacy from '@vitejs/plugin-legacy';

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

vite-svg-loader

这个插件可以让我们直接引入 svg 文件来使用,就像使用 Vue组件 一样

安装流程
npm i vite-svg-loader -D

然后配置 vite.config.ts

import svgLoader from 'vite-svg-loader'

export default defineConfig({
  plugins: [...,svgLoader()]
})
使用示例
<script setup lang="ts">
import MyIcon from '@/assets/example.svg?component';
</script>

<template>
  <MyIcon />
</template>

vite-plugin-md

这个插件可以帮助我们将 markdown 引入为 Vue组件 进行使用

值得注意的是,因为上面我们配置了 自动按需引入组件 ,安装完后我们只需要把 .md 文件放在 components 目录下就可以直接使用啦

安装配置
npm i vite-plugin-md -D

然后配置 vite.config.ts

// vite.config.ts
import Markdown from 'vite-plugin-md';

export default {
  plugins: [
    Markdown(),
  ]
}

TypeScript 配置

这里直接贴上完整的 tsconfig.json 配置

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    // baseUrl来告诉编译器到哪里去查找模块,所有非相对模块导入都会被当做相对于 baseUrl。
    "baseUrl": ".",
    // 非相对模块导入的路径映射配置
    "paths": {
      "@/*": ["src/*"],
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  // 编译器默认排除的编译文件
  "exclude": ["node_modules"]
}

代码风格规范

代码风格规范这一块的配置,我在我的另一篇文章中有很详细的配置流程,小伙伴们可以看过去: 从零开始搭建规范的 TypeScript SDK 项目工程环境

我们只需要额外安装一个依赖即可,在 eslint 初始化时选择 Vue 环境的话,它会帮我们自动装上

npm i -D eslint-plugin-vue

然后最后配置成的 .eslintrc.js 会有一些不一样的配置,我这里贴出完整配置:

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  globals: {
    defineEmits: true,
    document: true,
    localStorage: true,
    GLOBAL_VAR: true,
    window: true,
    defineProps: true,
    defineExpose: true,
  },
  extends: [
    './.eslintrc-auto-import.json',
    'airbnb-base',
    'plugin:@typescript-eslint/recommended',
    'plugin:vue/vue3-recommended',
    'plugin:prettier/recommended', // 添加 prettier 插件
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
  },
  plugins: ['vue', '@typescript-eslint', 'import'],
  rules: {
    'no-console': 'off',
    'import/no-unresolved': 'off',
    'import/extensions': 'off',
    'import/no-extraneous-dependencies': 'off',
  },
};

Vue Router 4.x

安装配置

我们需要安装 vue-router4.x 的版本

npm install vue-router@4 --save

我们在 src/router 目录下新建 index.ts 文件存放我们的 router 配置

我们再把配置好的 router 挂载到 vue 上:修改入口 main.ts 文件

unplugin-icons

我们经常都要为找各种各样的图标而发愁,而现在有一个极好的插件和一个极好的库,可以直接解决我们找图标、用图标的后顾之忧

icones:是一个非常优秀的图标库,里面集成了很多的图标

unplugin-icons:可以自动按需引入我们所要使用的图标,而不用手动 import

安装配置
npm i -D unplugin-icons @iconify/json

安装好后,我们配置 vite.config.ts 加入以下内容

// vite.config.ts
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import Components from 'unplugin-vue-components/vite'

export default {
  plugins: [
    Components({
      resolvers: IconsResolver(),
    }),
    Icons({
      compiler: 'vue3',
      autoInstall: true,
    }),
  ],
}
使用示例

我们先打开网址:icones.netlify.app/ 随便选择一个图标

image.png

然后点击复制

image.png

回到我们的代码中,只需要短短的一句就可以使用了:

<template>
  <i-mdi-account-reactivate style="font-size: 2em; color: red" />
</template>

Pinia

image.png

Pinia.js 是新一代的状态管理工具,可以认为它是下一代的 Vuex,即 Vuex5.x;它相比于 Vuex 有几个比较明显的变化:

  • 更好的 typescript 支持
  • 移除了 mutations 只留下 actions; actions 支持同步以及异步
  • 无需手动添加 store
安装配置
npm install pinia --save

在src/store 文件夹下新建index.ts,配置如下:

import { createPinia } from 'pinia';

const store = createPinia();

export default store;

然后我们在main.ts下use导出的store

pinia使用方式

我们可以在store目录下新建theme.ts文件,编写如下:

import { defineStore } from 'pinia';

const theme = defineStore({
  // 这里的id必须为唯一ID
  id: 'theme',
  state: () => {
    return {
      themeType: '亮蓝色',
      themeColor: '#2080F0FF',
    };
  },
  // 等同于vuex的getter
  getters: {
    getThemeType: (state) => state.themeType,
    getThemeColor: (state) => state.themeColor,
  },
  // pinia 放弃了 mutations 只使用 actions
  actions: {
    // actions可以用async做成异步形式
    setThemeType(type: string) {
      this.themeType = type;
    },
  },
});

export default theme;

调用方式如下:

<script setup lang="ts">
import theme from '@/store/theme'
const myTheme = theme()
myTheme.setThemeType('暗淡色')
</script>

<template>
  {{myTheme.themeColor}}
</template>

SCSS

这个不用我过多介绍,不会还有人没有用过 sass 或者 less 吧?我们直接进入配置流程:

安装配置
npm i sass -D

我们在 src/assets 目录下新建 variables.scss 文件,用来存放我们全局的 css 变量,我们先只配置一个主题色:

同时我们也在 src/assets 目录下新建 index.scss,在它里面引入我们未来将要新建的 scss 文件;并在 main.ts 中引入它

$theme-color: #2080F0FF

然后打开 vite.config.ts 文件,加上 css 配置的引入

css: {
  preprocessorOptions: {
    scss: {
      additionalData: `
      @import "@/assets/styles/variables.scss";
    `,
      javascriptEnabled: true,
    },
  },
},
组件中的使用方式

按上述配置完后,我们在组件中不需要任何的引入,就可以直接进行使用我们在 variables.scss 中定义的变量:

<style lang="scss">
.myclass {
  color: $theme-color;
}
</style>

Windi CSS

Windi CSS 可以视为 Tailwind CSS 的上位替代品,它提供更快的加载时间、与 Tailwind v2.0 的完全兼容性以及一系列额外的酷功能。

我们可以进行 原子化 的css编程,并且框架自动帮我们做好了按需引入的工作;

安装配置
npm i -D vite-plugin-windicss windicss

安装完依赖后,我们在 vite.config.ts 中安装此插件

import WindiCSS from 'vite-plugin-windicss'

export default {
  plugins: [
    WindiCSS(),
  ],
}

然后,我们在 main.ts 中引入它

import 'virtual:windi.css'

Axios

安装配置
npm i axios -S
基本封装

安装完后我们再在 src/api 目录下新建 http.ts 配置文件,用来封装 axios 方法,简单配置如下:

// src/api/http.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { ElMessage } from 'element-plus';
import showCodeMessage from '@/api/code';

const BASE_PREFIX = '/api';

// 创建实例
const service: AxiosInstance = axios.create({
  // 前缀
  baseURL: BASE_PREFIX,
  // 超时
  timeout: 1000 * 60 * 30,
  // 请求头
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // TODO 在这里可以加上想要在请求发送前处理的逻辑
    // TODO 比如 loading 等
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  },
);

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    if (response.status === 200) {
      return response;
    }
    ElMessage.info(JSON.stringify(response.status));
    return response;
  },
  (error: AxiosError) => {
    const { response } = error;
    if (response) {
      ElMessage.error(showCodeMessage(response.status));
      return Promise.reject(response.data);
    }
    ElMessage.warning('网络连接异常,请稍后再试!');
    return Promise.reject(error);
  },
);

export default service;

我们再新建一个 code.ts 用来存放我们 http 请求后接收到状态码的处理逻辑

// src/api/code.ts
declare interface codeMessageMapTypes {
  400: string;
  401: string;
  403: string;
  404: string;
  405: string;
  500: string;
  [key: string]: string;
}

const codeMessageMap: codeMessageMapTypes = {
  400: '[400]:请求参数错误',
  401: '[401]:账户未登录',
  403: '[403]:拒绝访问',
  404: '[404]:请求路径错误',
  405: '[405]:请求方法错误',
  500: '[500]:服务器错误',
};

const showCodeMessage = (code: number | string): string => {
  return codeMessageMap[JSON.stringify(code)] || '网络连接异常,请稍后再试!';
};

export default showCodeMessage;

VueUse

VueUse 是一个响应式的 Vue 实用程序的合集,使用它,我们可以把各种各样的东西变成响应式而不用我们手动编写 hook

安装配置
npm i npm i @vueuse/core -D

安装完依赖后,我们还需要在 vite.config.ts 中配置 自动按需引入

import { VueUseComponentsResolver } from 'unplugin-vue-components/resolvers';

export default defineConfig({
  plugins: [
    AutoImport({
      imports: ['@vueuse/core'],
    }),
    Components({
      resolvers: [VueUseComponentsResolver()],
    }),
  ],
});

这样配置完之后,我们就不需要再自己去引入方法,可以直接使用啦

NProgress

这个插件相信大部分人都有用过,它可以帮助我们在顶部加上页面加载提示:一个加载进度条和一个转的圈

安装配置
npm i --save nprogress

我们因为用着 typescript 所以还需要安装它的 types

npm i @types/nprogress -D

安装完之后,我门回到 src/router/index.ts 配置我们的路由守卫

import NProgress from 'nprogress';
router.beforeEach((to, from) => {
  if (!NProgress.isStarted()) {
      NProgress.start();
  }
});

router.afterEach((to, from) => {
  NProgress.done();
});

我们再在 src/assets/styles 目录下新建 nprogress.scss 文件配置如下:

/* Make clicks pass-through */
#nprogress {
  pointer-events: none;
}

#nprogress .bar {
  background: $theme-color;

  position: fixed;
  z-index: 1031;
  top: 0;
  left: 0;

  width: 100%;
  height: 2px;
}

/* Fancy blur effect */
#nprogress .peg {
  display: block;
  position: absolute;
  right: 0px;
  width: 100px;
  height: 100%;
  box-shadow: 0 0 10px $theme-color, 0 0 5px $theme-color;
  opacity: 1.0;

  -webkit-transform: rotate(3deg) translate(0px, -4px);
  -ms-transform: rotate(3deg) translate(0px, -4px);
  transform: rotate(3deg) translate(0px, -4px);
}

/* Remove these to get rid of the spinner */
#nprogress .spinner {
  display: block;
  position: fixed;
  z-index: 1031;
  top: 15px;
  right: 15px;
}

#nprogress .spinner-icon {
  width: 18px;
  height: 18px;
  box-sizing: border-box;

  border: solid 2px transparent;
  border-top-color: $theme-color;
  border-left-color: $theme-color;
  border-radius: 50%;

  -webkit-animation: nprogress-spinner 400ms linear infinite;
  animation: nprogress-spinner 400ms linear infinite;
}

.nprogress-custom-parent {
  overflow: hidden;
  position: relative;
}

.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
  position: absolute;
}

@-webkit-keyframes nprogress-spinner {
  0% {
    -webkit-transform: rotate(0deg);
  }

  100% {
    -webkit-transform: rotate(360deg);
  }
}

@keyframes nprogress-spinner {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}