创建项目
# yarn & npm
yarn create @vitejs/app
npm init @vitejs/app
项目结构
vite创建时项目后得到项目目录结构如下,vite.config.ts则为vite项目的配置文件,vite是以插件形式对vue进行支持,脚手架已经配置了vue的支持
使用ts则需要先安装一下类型声明文件
# yarn && npm
yarn add -D @types/node
npm install -D @types/node
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()] // 引入的vue插件
})
TypeScript配置文件修改
ts文件夹别名设置,项目文件夹创建types文件夹用来存放项目声明定义ts声明和接口,并在tsconfig.json添加项目文路径与文件支持
// tsconfig.json
{
...,
'baseUrl': '.', //基础目录
'paths': {
'@/*': ['src/*'], // src/* 别名设置
'#/*': ['types/*'] // types/* 别名设置
}
},
'include': [
...,
'types/**.ts', // 添加 types/**.ts 目录文件依赖
'types/**.d.ts' // 添加 types/**.d.ts 目录文件依赖
]
}
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
...
export default defineConfig({
...,
resolve: {
// 配置别名
alias: {
'@': resolve(__dirname, './src'),
'#': resolve(__dirname, './types'),
},
},
})
Axios支持
安装axios
# yarn && npm
yarn add axios
npm install --save axios
1、项目目录src下新建utils/http文件夹,创建分别创建index.ts 和 axios.ts,用到loadsh-es需要先安装一下
# loadsh-es
yarn add -S loadsh-es && yarn add -D @types/loadsh-es
npm install --save loadsh-es && npm install --save-dev @types/loadsh-es
2、types目录先创建axios请求可以自定义的接口
export interface CustomResqusetOptions {
// 自定义请求设置参数
joinTime?: boolean
}
export interface CustomResponseResult {
// 自定义返回的参数
data?: any[]
}
3、实现index.ts 和 axios.ts
// axios.ts
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import type { CustomResqusetOptions, CustomResponseResult } from '#/axios'
import axios from 'axios'
import { cloneDeep, isFunction } from 'lodash-es'
export interface CreateAxiosConfig extends AxiosRequestConfig {
transform?: AxiosTransform
customResquestOptions?: CustomResqusetOptions
}
// 抽象接口
export abstract class AxiosTransform {
beforeRequestHook?: (config: AxiosRequestConfig, options: CustomResqusetOptions) => AxiosRequestConfig
requestInterceptor?: (config: AxiosRequestConfig, options: CreateAxiosConfig) => AxiosRequestConfig
transformResponseHook?: (res: AxiosResponse<any>, options: CustomResqusetOptions) => AxiosResponse<any> | any
resqonseInterceptors?: (res: AxiosResponse<any>, options: CreateAxiosConfig) => AxiosResponse<any> | Promise<AxiosResponse<any>> | any
}
// 创建一个Axios类,内部实现请求拦截器、响应拦截器执行时机的钩子函数,使用单例模式创建class
export class Aixos {
private axiosInstance: AxiosInstance
private readonly options: CreateAxiosConfig
constructor(options: CreateAxiosConfig) {
this.options = options
this.axiosInstance = axios.create()
this.setup()
}
private setup() {
const transform = this.getTransform()
if (!transform) return
const { requestInterceptor, resqonseInterceptors } = transform
// 请求拦截器
this.axiosInstance.interceptors.request.use((config) => {
// 增加其他优先处理逻辑
// 拦截器
if (requestInterceptor && typeof isFunction(requestInterceptor)) {
config = requestInterceptor(config, this.options)
}
return config
})
// 响应拦截器
this.axiosInstance.interceptors.response.use((res) => {
if (resqonseInterceptors && isFunction(resqonseInterceptors))
res = resqonseInterceptors(res, this.options)
return res
})
}
private getTransform() {
const { transform } = this.options
return transform
}
getAxios() {
return this.axiosInstance
}
configAxios(config: CreateAxiosConfig) {
if (!this.axiosInstance) return
this.axiosInstance = axios.create(config)
}
setHeader(header?: any) {
if (!this.axiosInstance) return
Object.assign(this.axiosInstance.defaults.headers, header)
}
request<T = any>(config: AxiosRequestConfig, options?: CustomResqusetOptions): Promise<T> {
let conf: CreateAxiosConfig = cloneDeep(config)
const { customResquestOptions } = this.options
let opt: CustomResqusetOptions = Object.assign({}, customResquestOptions, options) // 合并默认设置与传入配置
const transform = this.getTransform()
const { beforeRequestHook, transformResponseHook } = transform || {}
// 钩子函数
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(config, opt)
}
conf.customResquestOptions = opt //合并配置
// 这里添加一些公共处理逻辑
return new Promise((reslove, reject) => {
this.axiosInstance
.request<any, AxiosResponse<CustomResponseResult>>(conf)
.then((res: AxiosResponse<CustomResponseResult>) => {
if (transformResponseHook && isFunction(transformResponseHook)) {
try {
const transformResponse = transformResponseHook(res)
reslove(transformResponse)
}
catch (err) {
reject(err)
}
return
}
}).catch((e: Error) => reject(e))
})
}
get<T = any>(config: AxiosRequestConfig, options?: CustomResqusetOptions): Promise<T> {
return this.request({ ...config, method: 'GET' }, options)
}
psot<T = any>(config: AxiosRequestConfig, options?: CustomResqusetOptions): Promise<T> {
return this.request({ ...config, method: 'POST' }, options)
}
put<T = any>(config: AxiosRequestConfig, options?: CustomResqusetOptions): Promise<T> {
return this.request({ ...config, method: 'PUT' }, options)
}
delete<T = any>(config: AxiosRequestConfig, options?: CustomResqusetOptions): Promise<T> {
return this.request({ ...config, method: 'DELETE' }, options)
}
}
// index.ts
import type { CreateAxiosConfig, AxiosTransform } from './aixos'
import type { CustomResqusetOptions } from '#/axios'
import { Aixos } from './aixos'
import { objectDeepMerge } from '@/utils'
const transform: AxiosTransform = {
beforeRequestHook(config, options) {
// 请求之前钩子函数
console.log('1、请求之前钩子函数: transformResponseHook')
return config
},
transformResponseHook(res, options) {
// 响应拦截后钩子函数
console.log('4、响应拦截后钩子函数: transformResponseHook')
return res
},
requestInterceptor(config, options) {
// 请求拦截
console.log('2、请求拦截: requestInterceptor')
return config
},
resqonseInterceptors(res) {
// 响应拦截
console.log('3、响应拦截: resqonseInterceptors')
return res
},
}
export const customDefaultOptins: CustomResqusetOptions | CreateAxiosConfig = {
transform, // 传入 transform
joinTime: true
}
export function createAxios(opt?: CreateAxiosConfig) {
return new Aixos(objectDeepMerge(customDefaultOptins, opt || {})) // 合并配置
}
export const defHttp = createAxios({})
4、utils目录下创建index.ts,编写objectDeepMerge函数
// utils/index.ts
import { isObject } from 'lodash-es'
export function objectDeepMerge<T = any>(src: any = {}, target: any = {}): T {
let key: string
for (key in target) {
src[key] = isObject(src[key]) ? objectDeepMerge(src[key], target[key]) : (src[key] = target[key])
}
return src
}
报错
ENOTSUP: operation not supported on socket, read请求的开发服务器接口返回
500导致
vue-router
1、安装vue-router, vue-router4 还没有转为正式版,所以需要加 next
# yarn & npm
yarn add vue-router@next
npm install --save vue-router@next
2、测试,src目录下新建router文件夹并创建index.ts
// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
const mainRoutes: RouteRecordRaw[] = [
{
name: 'Home',
path: '/home',
component: () => import('@/views/Home.vue'),
meta: {
title: 'home'
}
}
]
export const router = createRouter({
history: createWebHashHistory(),
routes: mainRoutes
})
export function setupRouter(app: App<Element>) {
app.use(router)
}
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { router, setupRouter } from './router'
async function bootstrap() {
const app = createApp(App)
// 添加其他逻辑
setupRouter(app)
await router.isReady()
app.mount('#app', true)
}
void bootstrap()
vuex
1、添加vuex
# yarn & npm
yarn add vuex@next
npm install --save vuex@next
2、测试,src目录下新建store
// store.ts
import type { App } from 'vue'
import { createStore } from 'vuex'
export const store = createStore({
state: {
sidebarOpen: false
},
mutations: {
SET_SIDEBAR(state: any) {
state.sidebarOpen = !state.sidebarOpen
localStorage.setItem('SIDEBAR_OPEN', state.sidebarOpen.toString())
},
GET_SIDEBAR(state: any) {
const rSidebarOpen = localStorage.getItem('SIDEBAR_OPEN')
if (rSidebarOpen) {
state.sidebarOpen = rSidebarOpen === 'true' ? true : false
}
}
},
actions: {
set({ commit }) {
commit('SET_SIDEBAR')
},
get({ commit }) {
commit('GET_SIDEBAR')
}
}
})
export function setupStore(app: App<Element>) {
app.use(store)
}
// Home.vue
<template>
<h2>{{ store.state.sidebarOpen }}</h2>
<button style='width: 30pxheight: 20px' @click='store.dispatch('set')'></button>
</template>
<script setup lang='ts'>
import { useStore } from 'vuex'
const store = useStore()
</script>
less
1、安装less,vite原生已经支持了css预处理,不需要安装less-loader
# yarn & npm
yarn add less -D
npm install --save-dev less
2、测试
// 全局引入
// styles/index.less
@import 'variables.less' // 引入全局变量
h2 {
background-color: red
}
// mian.ts
import '@styles/index.less' // 全局引入
sass
1、安装sass,vite原生已经支持了css预处理,不需要安装sass-loader
# yarn & npm
yarn add sass -D
npm install --save-dev sass
2、测试
// styles/index.scss
@import 'variables.sass' // 引入全局变量
h2 {
background-color: red
}
// mian.ts
import '@styles/index.scss' // 全局引入
stylus
1、安装stylus,vite原生已经支持了css预处理,不需要安装stylus-loader
# yarn & npm
yarn add stylus -D
npm install --save-dev stylus
2、测试
// styles/index.styl
@import 'variables.styl' // 引入全局变量
h2 {
background-color: red
}
// mian.ts
import '@styles/index.styl' // 全局引入
mock
1、安装vite-plugin-mock和mockjs
# yarn && npm
yarn add -D vite-plugin-mock mockjs
npm install --save-dev vite-plugin-mock mockjs
2、vite配置文件vite-config.ts导入并配置插件
// vite-config.ts
import { defineConfig } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
...,
viteMockServe({
ignore: /^\_/, // 忽略 _ 文件
mockPath: 'mock', // mock文件夹
supportTs: true,
})
],
...,
})
3、编写mock
// mock/index.ts
import { MockMethod } from 'vite-plugin-mock'
export default [
{
url: '/',
method: 'get',
response: () => {
return {
code: 20000,
msg: 'success',
'data': ['Hello Mock'],
})
},
},
] as MockMethod[]
4、测试一下
// api/home.ts
import { defHttp } from '@/utils/http'
enum API {
HOME = '/home'
}
export function getHome() {
return defHttp.get({ url: API.HOME })
}
// view/Home.vue
<template>
<h2>{{ msg }}</h2>
</template>
<script setup lang='ts'>
import { getHome } from '@/api/home'
import { ref } from '@vue/reactivity'
const msg = ref(null)
getHome().then(res => {
home.value = res.data.data
})
</script>
// App.vue
<template>
<HomeVue></HomeVue>
</template>
<script setup lang='ts'>
import HomeVue from './views/Home.vue'
</script>
环境变量
1、项目目录下新建环境变量的文件 .env.development 和 .env.production
// .env.development
VITE_APP_ENV = 'development'
// .env.production
VITE_APP_ENV = 'production'
2、修改配置 package.json
//package.json
...,
'scripts': {
'dev': 'cross-env NODE_ENV=development vite',
'build': 'vite build',
'build:dev': 'vite build --mode development',
'build:pro': 'vite build --mode production'
},
...,
整合vant组件库
安装依赖
yarn add vant@next
npm install --save vant@next
vite 版本不需要配置组件的按需加载,因为 Vant 3 内部所有模块都是基于 ESM 编写的,天然具备按需引入的能力,但是样式必须全部引入。
plugins目录下新建 vant.ts
import type { App } from 'vue'
import Vant from 'vant'
import 'vant/lib/index.css'
export function setupVant (app:App<Element>) {
app.use(Vant)
}
修改 main.ts
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { router, setupRouter } from './router'
import { setupStore } from './store'
import { setupVant } from 'plugins/vant'
async function bootstrap() {
const app = createApp(App)
// 挂载stroe
setupStore(app)
// 添加其他逻辑
setupVant(app)
// 挂载router
setupRouter(app)
await router.isReady()
app.mount('#app', true)
}
void bootstrap()
整合插件eslint,prettier 规范代码
安装依赖
yarn add babel-eslint -D
yarn add @vue/eslint-config-prettier -D
yarn add eslint -D
yarn add eslint-define-config -D
yarn add eslint-plugin-prettier -D
yarn add eslint-plugin-vue -D
yarn add prettier -D
根目录 下新建 .eslintrc.js
//.eslintrc.js
module.exports = {
root: true,
env: {
node: true,
},
extends: ['plugin:vue/vue3-essential', 'eslint:recommended'],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
//在此处写规则
'no-unused-vars': 0, // 定义未使用的变量
},
}
根目录 下新建 .prettierrc.json
// .prettierrc.json
{
// 此处填写规则
'singleQuote': true,// 单引号
'seme': true,// 分号
'tabWidth': 2,// 缩进
'TrailingCooma': 'all',// 尾部元素有逗号
'bracketSpacing': true,// 对象中的空格
}
再结合 vscode 的保存自动格式化
// .vscode/settings.json
'editor.formatOnSave': true,// 保存时格式化
'files.autoSave': 'onFocusChange', // 失去焦点时保存
'editor.codeActionsOnSave': {
'source.fixAll.eslint': true
},
'eslint.validate': [
'javascript',
'javascriptreact',
'typescript'
],
配置GZIP压缩
安装依赖
yarn add vite-plugin-compression -D
复制代码
修改 vite.config.js
// vite.config.js
import viteCompression from 'vite-plugin-compression'
plugins:[
...,
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz'
})
]
部署发布
执行对应命令即可
'dev': 'cross-env NODE_ENV=development vite',
'serve': 'vite preview',
'build:dev': 'vite build --mode development',
'build:pro': 'vite build --mode production'