分享一個(gè)剛碼的Vite-Vue3工程化模板項(xiàng)目
開箱即用的Vite-Vue3工程化模板
前言
畢設(shè)采用技術(shù)棧Vue3,Vite,TypeScript,Node,開發(fā)過程中產(chǎn)出了一些其它的東西,預(yù)計(jì)會(huì)出一系列的文章進(jìn)行介紹
體驗(yàn)?zāi)0?/span>
模板倉庫地址 (TS版):https://github.com/ATQQ/vite-vue3-template (JS版):https://github.com/ATQQ/vite-vue3-js-template 兩步到位
本地引入
#?方法一
npx?degit?atqq/vite-vue3-template#main?my-project
cd?my-project
#?方法二
git?clone?https://github.com/ATQQ/vite-vue3-template.git
cd?vite-vue3-template
啟動(dòng)
#?安裝依賴
yarn?install
#?運(yùn)行
yarn?dev
模板介紹
已包含特性
[x] vite [x] vue3 [x] @vue/compiler-sfc [x] TypeScript [x] Vuex4.x [x] Vue-Router4.x [x] Axios [x] Provide/inject [x] polyfill.io [x] Element UI Plus [x] Sass [x] Eslint [x] Jest [x] Tencent CloudBase static page [x] Tencent CloudBase Github Action
內(nèi)置了常見的工程化項(xiàng)目所用的內(nèi)容,后文只對(duì)其中的一些特性做簡(jiǎn)單介紹,展示部分示例代碼
目錄介紹
.
├──?__tests__
├──?dist????#?構(gòu)建結(jié)果
├──?public??#?公共靜態(tài)資源
├──?src?????#?源碼目錄
│???├──?apis
│???├──?assets
│???├──?components
│???├──?pages
│???├──?router
│???├──?store
│???├──?@types
│???├──?utils
│???├──?shims-vue.d.ts
│???├──?env.d.ts
│???├──?main.ts
│???└──?App.vue
├──?README.md
├──?index.html????#?應(yīng)用入口
├──?jest.config.ts
├──?LICENSE
├──?package.json
├──?tsconfig.json
├──?cloudbaserc.json??#?騰訊云CloudBase相關(guān)配置文件
├──?vite.config.ts??#?vite配置文件
└──?yarn.lock
Vite
Vite有多牛牪犇,我就不贅述了
一個(gè)簡(jiǎn)單的vite.config.ts配置文件
import?{?defineConfig?}?from?'vite'
import?vue?from?'@vitejs/plugin-vue'
import?path?from?'path'
//?https://vitejs.dev/config/
export?default?defineConfig({
??plugins:?[
????vue(),
??],
??build:?{
????target:?'modules',?//?默認(rèn)值
????//?sourcemap:?true,
??},
??server:?{
????port:?8080,
????proxy:?{
??????'/api/':?{
????????target:?'http://localhost:3000',
????????changeOrigin:?true,
????????rewrite:?(p)?=>?p.replace(/^\/api/,?''),
??????},
??????'/api-prod/':?{
????????target:?'http://localhost:3001',
????????changeOrigin:?true,
????????rewrite:?(p)?=>?p.replace(/^\/api-prod/,?''),
??????},
????},
??},
??resolve:?{
????alias:?{
??????'@':?path.resolve(__dirname,?'./src'),
??????'@components':?path.resolve(__dirname,?'./src/components'),
????},
??},
})
@vue/compiler-sfc
這個(gè)就是前段時(shí)間比較爭(zhēng)議的一個(gè)提案,不過真香,進(jìn)一步了解
Vuex
采用分業(yè)務(wù)模塊的方案
目錄結(jié)構(gòu)
src/store/
├──?index.ts
└──?modules
????└──?module1.ts
module1.ts
import?{?Module?}?from?'vuex'
interface?State?{
??count:?number
}
const?store:?Module?=?{
??namespaced:?true,
??state()?{
????return?{
??????count:?0,
????}
??},
??getters:?{
????isEven(state)?{
??????return?state.count?%?2?===?0
????},
??},
??//?只能同步
??mutations:?{
????increase(state,?num?=?1)?{
??????state.count?+=?num
????},
????decrease(state)?{
??????state.count?-=?1
????},
??},
??//?支持異步,可以考慮引入API
??actions:?{
????increase(context,?payload)?{
??????context.commit('increase',?payload)
??????setTimeout(()?=>?{
????????context.commit('decrease')
??????},?1000)
????},
??},
}
export?default?store
index.ts
import?{?createStore?}?from?'vuex'
import?module1?from?'./modules/module1'
//?Create?a?new?store?instance.
const?store?=?createStore({
??modules:?{
????m1:?module1,
??},
})
export?default?store
main.ts中引入
import?store?from?'./store'
app.use(store)
視圖中調(diào)用
import?{?computed?}?from?'vue'
import?{?useStore?}?from?'vuex'
const?store?=?useStore()
//?state
const?count?=?computed(()?=>?store.state.m1.count)
//?getters
const?isEven?=?computed(()?=>?store.getters['m1/isEven'])
//?mutations
const?add?=?()?=>?store.commit('m1/increase')
//?actions
const?asyncAdd?=?()?=>?store.dispatch('m1/increase')
Vue-Router
目錄結(jié)構(gòu)
src/router/
├──?index.ts
├──?Interceptor
│???└──?index.ts
└──?routes
????└──?index.ts
攔截器與頁面路由相分離
Interceptor/index.ts
import?{?Router?}?from?'vue-router'
declare?module?'vue-router'?{
????interface?RouteMeta?{
????????//?是可選的
????????isAdmin?:?boolean
????????//?是否需要登錄
????????requireLogin?:?boolean
????}
}
function?registerRouteGuard(router:?Router)?{
??/**
?????*?全局前置守衛(wèi)
?????*/
??router.beforeEach((to,?from)?=>?{
????if?(to.meta.requireLogin)?{
??????if?(from.path?===?'/')?{
????????return?from
??????}
??????return?false
????}
????return?true
??})
??/**
?????*?全局解析守衛(wèi)
?????*/
??router.beforeResolve(async?(to)?=>?{
????if?(to.meta.isAdmin)?{
??????try?{
????????console.log(to)
??????}?catch?(error)?{
????????//?if?(error?instanceof?NotAllowedError)?{
????????//?????//?...?處理錯(cuò)誤,然后取消導(dǎo)航
????????//?????return?false
????????//?}?else?{
????????//?????//?意料之外的錯(cuò)誤,取消導(dǎo)航并把錯(cuò)誤傳給全局處理器
????????//?????throw?error
????????//?}
????????console.error(error)
??????}
????}
??})
??/**
?????*?全局后置守衛(wèi)
?????*/
??router.afterEach((to,?from,?failure)?=>?{
????//?改標(biāo)題,監(jiān)控上報(bào)一些基礎(chǔ)信息
????//?sendToAnalytics(to.fullPath)
????if?(failure)?{
??????console.error(failure)
????}
??})
}
export?default?registerRouteGuard
routes/index.ts
import?{?RouteRecordRaw?}?from?'vue-router'
import?Home?from?'../../pages/home/index.vue'
import?About?from?'../../pages/about/index.vue'
import?Dynamic?from?'../../pages/dynamic/index.vue'
const?NotFind?=?()?=>?import('../../pages/404/index.vue')
const?Index?=?()?=>?import('../../pages/index/index.vue')
const?Axios?=?()?=>?import('../../pages/axios/index.vue')
const?Element?=?()?=>?import('../../pages/element/index.vue')
const?routes:?RouteRecordRaw[]?=?[
??{?path:?'/:pathMatch(.*)*',?name:?'NotFound',?component:?NotFind?},
??{
????path:?'/',
????name:?'index',
????component:?Index,
????children:?[
??????{?path:?'home',?component:?Home,?name:?'home'?},
??????{?path:?'about',?component:?About,?name:?'about'?},
??????{?path:?'axios',?component:?Axios,?name:?'axios'?},
??????{?path:?'element',?component:?Element,?name:?'element'?},
??????{
????????path:?'dynamic/:id',
????????component:?Dynamic,
????????meta:?{
??????????requireLogin:?false,
??????????isAdmin:?true,
????????},
????????name:?'dynamic',
??????},
????],
??},
]
export?default?routes
router/index.ts
import?{?createRouter,?createWebHistory?}?from?'vue-router'
import?registerRouteGuard?from?'./Interceptor'
import?routes?from?'./routes'
const?router?=?createRouter({
??history:?createWebHistory(import.meta.env.VITE_ROUTER_BASE?as?string),
??routes,
})
//?注冊(cè)路由守衛(wèi)
registerRouteGuard(router)
export?default?router
main.ts中引入
import?router?from?'./router'
app.use(router)
Axios
對(duì)axios的簡(jiǎn)單包裝
ajax.ts
import?axios?from?'axios'
const?instance?=?axios.create({
??baseURL:?import.meta.env.VITE_APP_AXIOS_BASE_URL,
})
/**
?*?請(qǐng)求攔截
?*/
instance.interceptors.request.use((config)?=>?{
??const?{?method,?params?}?=?config
??//?附帶鑒權(quán)的token
??const?headers:?any?=?{
????token:?localStorage.getItem('token'),
??}
??//?不緩存get請(qǐng)求
??if?(method?===?'get')?{
????headers['Cache-Control']?=?'no-cache'
??}
??//?delete請(qǐng)求參數(shù)放入body中
??if?(method?===?'delete')?{
????headers['Content-type']?=?'application/json;'
????Object.assign(config,?{
??????data:?params,
??????params:?{},
????})
??}
??return?({
????...config,
????headers,
??})
})
/**
?*?響應(yīng)攔截
?*/
instance.interceptors.response.use((v)?=>?{
??if?(v.data?.code?===?401)?{
????localStorage.removeItem('token')
????// alert('即將跳轉(zhuǎn)登錄頁。。。', '登錄過期')
????//?setTimeout(redirectHome,?1500)
????return?v.data
??}
??if?(v.status?===?200)?{
????return?v.data
??}
??//?alert(v.statusText,?'網(wǎng)絡(luò)錯(cuò)誤')
??return?Promise.reject(v)
})
export?default?instance
api目錄結(jié)構(gòu)
src/apis/
├──?ajax.ts
├──?index.ts
└──?modules
????└──?public.ts
分業(yè)務(wù)模塊編寫接口調(diào)用方法,通過apis/index.ts對(duì)外統(tǒng)一導(dǎo)出
export?{?default?as?publicApi?}?from?'./modules/public'
注入全局的Axios實(shí)例,Vue2中通常是往原型(prototype)上掛載相關(guān)方法,在Vue3中由于使用CreateApp創(chuàng)建實(shí)例,所以推薦使用provide/inject 來傳遞一些全局的實(shí)例或者方法
main.ts
import?Axios?from?'./apis/ajax'
const?app?=?createApp(App)
app.provide('$http',?Axios)
視圖中使用
import?{?inject?}?from?'vue'
const?$http?=?inject('$http')
polyfill.io
部分瀏覽器可能對(duì)ES的新語法支持程度不一致,存在一定的兼容問題,此時(shí)就需要使用polyfill(墊片)
polyfill.io是一個(gè)墊片服務(wù),直接通過cdn按需引入墊片,不影響包體積
工作原理是通過解析客戶端的UA信息,然后根據(jù)查詢參數(shù),判斷是否需要墊片,不需要?jiǎng)t不下發(fā)
簡(jiǎn)單使用
html>
<html?lang="en">
<head>
??<meta?charset="UTF-8"?/>
??<link?rel="icon"?href="/favicon.ico"?/>
??<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
??<title>Vite?Apptitle>
??<script
????src="https://polyfill.alicdn.com/polyfill.min.js?features=es2019%2Ces2018%2Ces2017%2Ces5%2Ces6%2Ces7%2Cdefault">script>
head>
<body>
??<div?id="app">div>
??<script?type="module"?src="/src/main.ts">script>
body>
html>
查詢參數(shù)在線生成->url-builder
由于官方服務(wù)是部署在非大陸,所以延遲較高,由于polyfill-service是開源的,所以可以自己進(jìn)行搭建
國內(nèi)大廠也有一些鏡像:
https://polyfill.alicdn.com/polyfill.min.js https://polyfill.meituan.com/polyfill.min.js
element UI Plus
Vue3版本的Element UI 組件庫,雖然有些坑,但勉強(qiáng)能用 O(∩_∩)O哈哈~
按需引入在使用過程中發(fā)現(xiàn)Dev和Prod環(huán)境下的樣式表現(xiàn)有差異,固采用全量引入的方式
utils/elementUI.ts
import?{?App?}?from?'@vue/runtime-core'
//?全量引入
import?ElementPlus?from?'element-plus'
import?'element-plus/lib/theme-chalk/index.css'
import?'dayjs/locale/zh-cn'
import?locale?from?'element-plus/lib/locale/lang/zh-cn'
export?default?function?mountElementUI(app:?App )?{
??app.use(ElementPlus,?{?locale?})
}
main.ts
import?mountElementUI?from?'./utils/elementUI'
mountElementUI(app)
