公司项目使用react,但是作为vue2的一名coder,周末花了两天的时间,整理了一波vue3 + tsx + vite + axios 的开发模板,里面涵盖jest、tailwindcss、pinia、element-plus等一些日常工具包,以及加入了eslint、prettier保证日常开发代码质量工具,基本上能够保证大家能够开箱即用,下面附上模板代码地址,关于代码目录结构可以参考代码仓库的说明文档,喜欢的朋友可以转评赞给一个,点个收藏不丢失,下面呢我介绍一下基本构建思路;
要想项目中运行tsx,我们就得考虑到tsx语法糖编译的问题,这里就得用到@vitejs/plugin-vue-jsx
插件,详细用法参考github文档,安装后,在vite的plugin中直接调用即可;
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx()]
})
关于tailwindcss + vite方案,它的官网有了很友好的方案,这块大家按部就班的安装就够了,没有多少复杂度,参考地址,选择tailwindcss主要是它提供了一些快速样式,比如padding、margin、background等,如果我们项目是后台管理系统,tailwindcss会大大降低我们写css样式的工作,大家可以去学习一波在项目中用起来,熟悉了以后就觉得他是在是太方便了。
这里不做用法的介绍,就推荐一个vscode插件Tailwind CSS IntelliSense
,安装后,在项目中我们就可以只能提示,如下所示:
关于代码规范,一般小一点公司不太会做这方面的工程化配置,但是eslint等这些代码规范工具,会让我们团队的代码更规范,风格更统一,团队协作更加方便,我简单说一下配置eslint及prettier的办法
pnpm add eslint -D
pnpm eslint --init
选择对应的项目内容,这里我的项目用到(vue, typescript,browser)这个,当然有这个还不够,我们需要安装如下两个工具包
pnpm add eslint-plugin-import // 主要对于es与typescript import 路径的一个eslint校验
pnpm add eslint-config-airbnb-base // 这个是airbnb出的一套eslint语法规范的工具库,如果自己公司没有对应的代码规范,这个是很实用的一套
项目中我们用到的是eslint-plugin-vue这个vue代码校验规范工具,里面有很多内容及配置项功能,我们这里推荐大家在配置代码规范,可以参考官方的说明文档,链接放在这里;
这个相对来讲比较简单一些,我们直接安装pnpm add eslint-plugin-prettier eslint-config-prettier prettier -D
,这里我们需要注意的是prettier与eslint冲突问题;
上面是配置时候的基本流程,最终结果我将eslintrc文件及package.json文件放到这里,有需要的朋友,可以直接copy一份去配置,毕竟这个配置很臭很长,深入学习感觉又没有太大必要(23333~)
{
"name": "vue-tsx-template",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"fix": "eslint --fix --ext .js,.jsx,.tsx,.vue src && prettier "
},
"dependencies": {
"vue": "^3.2.25"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.23.0",
"@vitejs/plugin-vue": "^2.3.3",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"autoprefixer": "^10.4.7",
"eslint": "^8.15.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.7.1",
"postcss": "^8.4.13",
"prettier": "^2.6.2",
"sass": "^1.51.0",
"tailwindcss": "^3.0.24",
"typescript": "^4.5.4",
"vite": "^2.9.9",
"vue-eslint-parser": "^9.0.1",
"vue-tsc": "^0.34.7"
}
}
下面是.eslintrc.js
文件
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
// 处理 defineProps 报错
'vue/setup-compiler-macros': true,
},
extends: [
'eslint:recommended',
'airbnb-base',
'prettier',
'plugin:prettier/recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['vue', '@typescript-eslint'],
rules: {
// 防止prettier与eslint冲突
'prettier/prettier': 'error',
// eslint-plugin-import es module导入eslint规则配置,旨在规避拼写错误问题
'import/no-unresolved': 0,
'import/extensions': [
'error',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
json: 'always',
},
],
// 使用导出的名称作为默认属性(主要用作导出模块内部有 default, 和直接导出两种并存情况下,会出现default.proptry 这种问题从在的情况)
'import/no-named-as-default-member': 0,
'import/order': ['error', { 'newlines-between': 'always' }],
// 导入确保是否在首位
'import/first': 0,
// 如果文件只有一个导出,是否开启强制默认导出
'import/prefer-default-export': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [],
optionalDependencies: false,
},
],
/**
* 关于typescript语法校验
* 参考文档: https://www.npmjs.com/package/@typescript-eslint/eslint-plugin
*/
'@typescript-eslint/no-extra-semi': 0,
// 是否禁止使用any类型
'@typescript-eslint/no-explicit-any': 0,
// 是否对于null情况做非空断言
'@typescript-eslint/no-non-null-assertion': 0,
// 是否对返回值类型进行定义校验
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/member-delimiter-style': ['error', { multiline: { delimiter: 'none' } }],
// 结合eslint 'no-use-before-define': 'off',不然会有报错,需要关闭eslint这个校验,主要是增加了对于type\interface\enum
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': ['error'],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
ignoreRestSiblings: true,
varsIgnorePattern: '^_',
argsIgnorePattern: '^_',
},
],
'@typescript-eslint/explicit-member-accessibility': ['error', { overrides: { constructors: 'no-public' } }],
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/indent': 0,
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase'],
},
],
// 不允许使用 var
'no-var': 'error',
// 如果没有修改值,有些用const定义
'prefer-const': [
'error',
{
destructuring: 'any',
ignoreReadBeforeAssign: false,
},
],
// 关于vue3 的一些语法糖校验
// 超过 4 个属性换行展示
'vue/max-attributes-per-line': [
'error',
{
singleline: 4,
},
],
// setup 语法糖校验
'vue/script-setup-uses-vars': 'error',
// 关于箭头函数
'vue/arrow-spacing': 'error',
'vue/html-indent': 'off',
},
}
单元测试,根据自己项目体量及重要性而去考虑是否要增加,当然单测可以反推一些组件 or 方法的设计是否合理,同样如果是一个稳定的功能在加上单元测试,这就是一个很nice的体验;
我们单元测试是基于jest来去做的,具体安装单测的办法如下,跟着我的步骤一步步来;
pnpm add @testing-library/vue @testing-library/user-event @testing-library/jest-dom @types/jest jest @vue/test-utils -D
@testing-library/dom @vue/compiler-sfc
我们继续补充pnpm add @babel/core babel-jest @vue/babel-preset-app -D
,最后我们配置babel.config.jsmodule.exports = {
presets: ['@vue/app'],
}
module.exports = {
roots: ['/test' ],
testMatch: [
// 这里我们支持src目录里面增加一些单层,事实上我并不喜欢这样做
'/src/**/__tests__/**/*.{js,jsx,ts,tsx}' ,
'/src/**/*.{spec,test}.{js,jsx,ts,tsx}' ,
// 这里我习惯将单层文件统一放在test单独目录下,不在项目中使用,降低单测文件与业务组件模块混合在一起
'/test/**/*.{spec,test}.{js,jsx,ts,tsx}' ,
],
testEnvironment: 'jsdom',
transform: {
// 此处我们单测没有适用vue-jest方式,项目中我们江永tsx方式来开发,所以我们如果需要加入其它的内容
// '^.+\\.(vue)$': '/node_modules/vue-jest',
'^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': '/node_modules/babel-jest' ,
},
transformIgnorePatterns: [
'/node_modules/' ,
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss|less)$',
],
moduleFileExtensions: ['ts', 'tsx', 'vue', 'js', 'jsx', 'json', 'node'],
resetMocks: true,
}
具体写单元测试的方法,可以参考项目模板中的组件单元测试写法,这里不做过多的说明;
这里呢其实思路有很多种,如果有自己的习惯的封装方式,就按照自己的思路,下面附上我的封装代码,简短的说一下我的封装思路:
get、post、axiosHttp
)import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'
import { ElNotification } from 'element-plus'
import errorHandle from './errorHandle'
// 定义数据返回结构体(此处我简单定义一个比较常见的后端数据返回结构体,实际使用我们需要按照自己所在的项目开发)
interface ResponseData<T = null> {
code: string | number
data: T
success: boolean
message?: string
[key: string]: any
}
const axiosInstance = axios.create()
// 设定响应超时时间
axiosInstance.defaults.timeout = 30000
// 可以后续根据自己http请求头特殊邀请设定请求头
axiosInstance.interceptors.request.use(
(req: AxiosRequestConfig<any>) => {
// 特殊处理,后续如果项目中有全局通传参数,可以在这儿做一些处理
return req
},
error => Promise.reject(error),
)
// 响应拦截
axiosInstance.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
// 数组处理
return res
},
error => Promise.reject(error),
)
// 通用的请求方法体
const axiosHttp = async <T extends Record<string, any> | null>(
config: AxiosRequestConfig,
desc: string,
): Promise<T> => {
try {
const { data } = await axiosInstance.request<ResponseData<T>>(config)
if (data.success) {
return data.data
}
// 如果请求失败统一做提示(此处我没有安装组件库,我简单写个mock例子)
ElNotification({
title: desc,
message: `${data.message || '请求失败,请检查'}`,
})
} catch (e: any) {
// 统一的错误处理
if (e.response && e.response.status) {
errorHandle(e.response.status, desc)
} else {
ElNotification({
title: desc,
message: '接口异常,请检查',
})
}
}
return null as T
}
// get请求方法封装
export const get = async <T = Record<string, any> | null>(url: string, params: Record<string, any>, desc: string) => {
const config: AxiosRequestConfig = {
method: 'get',
url,
params,
}
const data = await axiosHttp<T>(config, desc)
return data
}
// Post请求方法
export const post = async <T = Record<string, any> | null>(url: string, data: Record<string, any>, desc: string) => {
const config: AxiosRequestConfig = {
method: 'post',
url,
data,
}
const info = await axiosHttp<T>(config, desc)
return info
}
请求错误(状态码错误相关提示)
import { ElNotification } from 'element-plus'
function notificat(message: string, title: string) {
ElNotification({
title,
message,
})
}
/**
* @description 获取接口定义
* @param status {number} 错误状态码
* @param desc {string} 接口描述信息
*/
export default function errorHandle(status: number, desc: string) {
switch (status) {
case 401:
notificat('用户登录失败', desc)
break
case 404:
notificat('请求不存在', desc)
break
case 500:
notificat('服务器错误,请检查服务器', desc)
break
default:
notificat(`其他错误${status}`, desc)
break
}
}
这两个相对来讲简单一些,会使用vuex状态管理,上手pinia也是很轻松的事儿,只是更简单化了、更方便了,可以参考模板项目里面的用法example,这里附上router
及pinia
配置方法,路由守卫,大家可以根据项目的要求再添加
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
// 配置路由
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home',
},
{
name: 'home',
path: '/home',
component: () => import('page/Home'),
},
]
const router = createRouter({
routes,
history: createWebHistory(),
})
export default router
针对与pinia,参考如下:
import { createPinia } from 'pinia'
export default createPinia()
在入口文件将router和store注入进去
import { createApp } from 'vue'
import App from './App'
import store from './store/index'
import './style/index.css'
import './style/index.scss'
import 'element-plus/dist/index.css'
import router from './router'
// 注入全局的store
const app = createApp(App).use(store).use(router)
app.mount('#app')
说这些比较枯燥,建议大家去github参考项目说明文档,下载项目,自己过一遍,喜欢的朋友收藏点赞一下,如果喜欢我构建好的项目给个star不丢失,谢谢各位看官的支持。