從今天開(kāi)始,我們開(kāi)啟《手把手搭建Vue3中后臺(tái)框架》系列文章,這是一個(gè)我們已經(jīng)在生產(chǎn)中實(shí)際使用的項(xiàng)目,技術(shù)棧是:
- 前端:Vue3 + TypeScript + Vite + Pinia + NAIveUI
我會(huì)在工作之余抽時(shí)間把整體框架的開(kāi)發(fā)過(guò)程一點(diǎn)一點(diǎn)寫(xiě)出來(lái),另外UI組件庫(kù)我將更換為NaiveUI 有技術(shù)不到位的地方,希望大家能指正出來(lái)。今天我們就從項(xiàng)目創(chuàng)建開(kāi)始。
使用 Vite 創(chuàng)建項(xiàng)目
pnpm create vite
√ Project name: ... OpenDataV
√ Select a framework: » vue
√ Select a variant: » vue-ts
啟動(dòng)項(xiàng)目
cd OpenDataV
pnpm install
pnpm run dev
配置基礎(chǔ)開(kāi)發(fā)功能
創(chuàng)建完項(xiàng)目后,我們需要配置一些基礎(chǔ)功能來(lái)滿足開(kāi)發(fā)要求,主要有以下幾點(diǎn):
- 目錄別名配置
- 打包配置
- 開(kāi)發(fā)服務(wù)配置
- 環(huán)境變量的使用配置
目錄別名配置
在項(xiàng)目開(kāi)發(fā)的過(guò)程中,我們需要使用導(dǎo)入文件,可以采用相對(duì)導(dǎo)入和絕對(duì)導(dǎo)入的方式,相對(duì)導(dǎo)入在有些地方需要使用多個(gè)..來(lái)進(jìn)行目錄定位,所以通常我們都使用絕對(duì)定位,為了方便導(dǎo)入,可以給指定目錄設(shè)置別名,引入是就不需要寫(xiě)長(zhǎng)長(zhǎng)的目錄了。
首先在在vite.config.ts中增加別名配置:
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: [
{
find: /@//,
replacement: resolve(__dirname, 'src') + '/'
}
],
extensions: ['.ts', '.js', '.jsx', '.tsx'],
}
})
在使用path這個(gè)包之前,需要安裝@types/node這個(gè)包,因?yàn)槲覀兪褂玫氖莟s,所以在項(xiàng)目當(dāng)中就會(huì)有ts類型檢查。因?yàn)橛泻芏鄮?kù)并沒(méi)有遷移至ts,所以ts提供了一種中間的處理方式,就是.d.ts文件,用來(lái)給特定的庫(kù)添加類型說(shuō)明。node本身并沒(méi)有遷移至ts,所以我們?cè)谑褂靡恍﹏ode庫(kù)的時(shí)候就會(huì)報(bào)錯(cuò),因此就需要安裝類型說(shuō)明:@types/node,只要安裝在開(kāi)發(fā)依賴下即可。
extensions的配置是使用別名時(shí)要忽略的文件后綴,官方不建議忽略.vue后綴,因此我們只配置了.ts、.js、.tsx、.jsx
配置完vite以后,我們還要配置tsconfig.json文件,讓ts也認(rèn)識(shí)這個(gè)別名。
{
"compilerOptions": {
"baseUrl": "./", // 基礎(chǔ)根目錄
"paths": {
"@/*": ["src/*"]
}
}
配置好別名以后,就可以在引入模塊時(shí)使用了。
打包配置
主要是配置打包的目標(biāo)版本和分塊大小,target表示在打包時(shí)無(wú)論我們使用的是哪個(gè)版本的JAVAScript標(biāo)準(zhǔn),都處理成es2015兼容版本。
build: {
target: 'es2015',
chunkSizeWarningLimit: 1500
}
開(kāi)發(fā)服務(wù)配置
當(dāng)我們創(chuàng)建好項(xiàng)目啟動(dòng)的時(shí)候,默認(rèn)啟動(dòng)項(xiàng)目的時(shí)候只能通過(guò)localhost訪問(wèn),而且端口并不是我們指定的,所以我們首先要配置訪問(wèn)限制,方便其他人也能查看我們的開(kāi)發(fā)頁(yè)面,并要指定端口。
server: {
https: false,
host: true,
port: 3000
}
開(kāi)發(fā)過(guò)程中一般不需要啟用https,host可以配置為指定IP,也可以配置為布爾型,如果我們希望所有IP都能訪問(wèn),可以將其配置為0.0.0.0或者true。
除了以上配置外,還有一個(gè)配置項(xiàng)proxy,這是一個(gè)代理配置,可以理解為網(wǎng)絡(luò)代理,就是把我們的請(qǐng)求代理到其他的地址。要搞清楚proxy的用法,首先我們要了解前后端分離以后前端是怎么獨(dú)立運(yùn)行的。
在傳統(tǒng)的web開(kāi)發(fā)過(guò)程中,一般是通過(guò)后端來(lái)渲染前端頁(yè)面數(shù)據(jù),例如我們使用的Django Jinjia模板等。這個(gè)時(shí)候在開(kāi)發(fā)的過(guò)程中就需要把整個(gè)服務(wù)跑起來(lái)。但是前后端分離的開(kāi)發(fā)模式將前端和后端徹底分開(kāi)了,僅通過(guò)API連接起來(lái),這就使得前端開(kāi)發(fā)的時(shí)候并不需要后端服務(wù)器,這種方式是怎么做到的呢?其實(shí)就是前端自己?jiǎn)?dòng)一個(gè)服務(wù)器來(lái)渲染前端頁(yè)面數(shù)據(jù),而不是通過(guò)后端渲染,只通過(guò)API來(lái)獲取后端數(shù)據(jù)。
前后端完全分離的開(kāi)發(fā)模式使得前端與后端的鏈接僅僅是中間的數(shù)據(jù)。那么如果在功能開(kāi)發(fā)前就定義好接口和返回的數(shù)據(jù)格式,前端就可以通過(guò)假數(shù)據(jù)來(lái)測(cè)試已開(kāi)發(fā)的功能,不需要等后端開(kāi)發(fā)完后才能測(cè)試。對(duì)于假數(shù)據(jù)我們有多種方式去實(shí)現(xiàn)它,最常見(jiàn)的就是mock,因?yàn)榍岸艘呀?jīng)有了自己的服務(wù)器,我們只要把假數(shù)據(jù)寫(xiě)到j(luò)son文件中,通過(guò)前端服務(wù)器發(fā)給前端頁(yè)面就可以了。實(shí)際上依然是前端從服務(wù)器拿到數(shù)據(jù)再渲染頁(yè)面,只不過(guò)這個(gè)服務(wù)器是前端的自己?jiǎn)?dòng)的,而數(shù)據(jù)是固定的假數(shù)據(jù)而已。
理解上這個(gè)過(guò)程以后,我們?cè)賮?lái)看看proxy這個(gè)配置的意義。因?yàn)槲覀兒秃蠖思s定好了接口和返回?cái)?shù)據(jù)的格式,因此在編寫(xiě)前端頁(yè)面的時(shí)候我們就可以寫(xiě)好這些請(qǐng)求和處理,但是我們的前端可以要從多個(gè)后端獲取數(shù)據(jù)或者我們開(kāi)發(fā)的時(shí)候是從本地服務(wù)器獲取數(shù)據(jù),但是聯(lián)調(diào)的時(shí)候又要從真實(shí)后端獲取數(shù)據(jù),這個(gè)時(shí)候proxy就派上用場(chǎng)了。我們先來(lái)看一下proxy的配置示例:
proxy: {
// 字符串簡(jiǎn)寫(xiě)寫(xiě)法
'/foo': 'http://localhost:4567',
// 選項(xiàng)寫(xiě)法
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, '')
}
}
proxy是通過(guò){key: options}的方式去配置的,其中key是請(qǐng)求的前綴,而options則是我們需要轉(zhuǎn)發(fā)的實(shí)際地址,例如以上示例:
- /foo開(kāi)頭的請(qǐng)求轉(zhuǎn)發(fā)到地址:http://localhost:4567
也就是說(shuō)當(dāng)我們?cè)谇岸税l(fā)送了/foo/xxx這個(gè)請(qǐng)求后,實(shí)際的請(qǐng)求會(huì)發(fā)送到http://localhost:4567這個(gè)服務(wù)去,而得到的返回值也是從這里發(fā)回來(lái)的。
- /api開(kāi)頭的請(qǐng)求轉(zhuǎn)發(fā)到地址:http://jsonplaceholder.typicode.com,并將地址中的api去掉
也就是說(shuō)/api/user/info這個(gè)請(qǐng)求最終會(huì)變成http://jsonplaceholder.typicode.com/user/info請(qǐng)求。
通過(guò)代理的這種方式我們就可以很方便的切換數(shù)據(jù)來(lái)源,但是要注意了,這只是在開(kāi)發(fā)階段使用,如果部署到生產(chǎn)環(huán)境后就失效了,因?yàn)樵谏a(chǎn)環(huán)境我們前端是不會(huì)啟動(dòng)自有服務(wù)器的,通常都是通過(guò)Nginx服務(wù)進(jìn)行靜態(tài)資源的轉(zhuǎn)發(fā)。所以要記住了:以上只在開(kāi)發(fā)階段有用。
明確了以上知識(shí)點(diǎn),那么proxy的配置就根據(jù)實(shí)際情況了使用了。在這里我們先不配置。
環(huán)境變量的使用配置
在前端開(kāi)發(fā)過(guò)程中,我們有很多有很多配置,現(xiàn)在通常都使用環(huán)境變量的方式配置,而vite也默認(rèn)支持這種方式,在運(yùn)行 vite 命令時(shí),開(kāi)發(fā)環(huán)境默認(rèn)加載 .env.development,生產(chǎn)環(huán)境默認(rèn)加載 .env.production,因此我們只需要配置對(duì)應(yīng)的文件即可。
一般的環(huán)境變量有三個(gè)文件:
- .env 任何情況下都會(huì)加載
- .env.development 開(kāi)發(fā)模式下加載
- .env.production 生產(chǎn)模式下加載
目前我們需要配置的有以下幾個(gè)變量:
# 網(wǎng)站標(biāo)題
VITE_App_TITLE = '后臺(tái)管理'
# 端口
VITE_APP_PORT = 8800
# 后端地址
VITE_APP_BASE_URL = 'http://localhost:9527'
這里的端口是我們前端服務(wù)器的端口,因此需要在vite.config.ts中引用,我們需要對(duì)這個(gè)文件做一些修改,將默認(rèn)的加載方式改為函數(shù)加載:
import type { UserConfigExport, ConfigEnv } from 'vite';
import { loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default ({ mode }: ConfigEnv): UserConfigExport => {
const { VITE_APP_PORT } = loadEnv(mode, process.cwd());
return {
plugins: [vue()],
resolve: {
alias: [
{
find: /@//,
replacement: resolve(__dirname, 'src') + '/',
},
],
extensions: ['.ts', '.js', '.jsx', '.tsx'],
},
server: {
https: false,
host: true,
port: Number(VITE_APP_PORT),
},
build: {
target: 'es2015',
chunkSizeWarningLimit: 1500,
},
};
};
修改了導(dǎo)入方式,并且通過(guò)vite提供的loadEnv函數(shù)加載對(duì)應(yīng)的環(huán)境變量文件,然后獲取其中的環(huán)境變量,并修改server中配置的port。
配置代碼格式化
一個(gè)好的項(xiàng)目,必須有統(tǒng)一的代碼風(fēng)格,但是在實(shí)際開(kāi)發(fā)當(dāng)中通常都是多人協(xié)作開(kāi)發(fā),因此我們必須想辦法統(tǒng)一大家的風(fēng)格,在前端開(kāi)發(fā)中我們可以使用ESlint和Prettier進(jìn)行代碼風(fēng)格統(tǒng)一配置。
在這之前每次我都把之前項(xiàng)目中的配置挪用到新的項(xiàng)目中,直到我發(fā)現(xiàn)eslint-config-alloy,這個(gè)項(xiàng)目是把最常用的配置幫我們做好了,因此我們可以很方便的使用它。
安裝相關(guān)的包
首先安裝typescript和vue的相關(guān)支持。
pnpm install -D @babel/core @babel/eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser @vue/eslint-config-typescript eslint eslint-config-alloy eslint-plugin-vue vue-eslint-parser
在配置eslint和prettier之前需要說(shuō)明的是必須使用.cjs作為配置文件的后綴,因?yàn)檫@些命令模式采用commonjs,而我們創(chuàng)建的項(xiàng)目package.json中type: module,要么選擇修改package.json中的配置,要么采用.cjs后綴,我推薦使用.cjs后綴。
配置eslint
在項(xiàng)目根目錄增加.eslintrc.cjs文件,配置如下:
module.exports = {
extends: [
'alloy',
'alloy/vue',
'alloy/typescript',
],
parser: 'vue-eslint-parser',
parserOptions: {
parser: {
js: '@babel/eslint-parser',
jsx: '@babel/eslint-parser',
ts: '@typescript-eslint/parser',
tsx: '@typescript-eslint/parser',
},
},
env: {
// 你的環(huán)境變量(包含多個(gè)預(yù)定義的全局變量)
browser: true,
node: true
},
globals: {
// 你的全局變量(設(shè)置為 false 表示它不允許被重新賦值)
//
// myGlobal: false
},
rules: {
// 自定義你的規(guī)則
'@typescript-eslint/prefer-optional-chain': 'off',
},
};
不要問(wèn)我為什么,安裝官方給的配置就可以了。
配置prettier
然后增加.prettierrc.cjs文件,配置如下:
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 2 個(gè)空格縮進(jìn)
tabWidth: 2,
// 不使用縮進(jìn)符,而使用空格
useTabs: false,
// 行尾需要有分號(hào)
semi: true,
// 使用單引號(hào)
singleQuote: true,
// 對(duì)象的 key 僅在必要時(shí)用引號(hào)
quoteProps: 'as-needed',
// jsx 不使用單引號(hào),而使用雙引號(hào)
jsxSingleQuote: false,
// 末尾需要有逗號(hào)
trailingComma: 'none',
// 大括號(hào)內(nèi)的首尾需要空格
bracketSpacing: true,
// jsx 標(biāo)簽的反尖括號(hào)需要換行
bracketSameLine: false,
// 箭頭函數(shù),只有一個(gè)參數(shù)的時(shí)候,也需要括號(hào)
arrowParens: 'always',
// 每個(gè)文件格式化的范圍是文件的全部?jī)?nèi)容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要寫(xiě)文件開(kāi)頭的 @prettier
requirePragma: false,
// 不需要自動(dòng)在文件開(kāi)頭插入 @prettier
insertPragma: false,
// 使用默認(rèn)的折行標(biāo)準(zhǔn)
proseWrap: 'preserve',
// 根據(jù)顯示樣式?jīng)Q定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 內(nèi)不用縮進(jìn)
vueIndentScriptAndStyle: false,
// 換行符使用 lf
endOfLine: 'lf',
// 格式化嵌入的內(nèi)容
embeddedLanguageFormatting: 'auto',
// html, vue, jsx 中每個(gè)屬性占一行
singleAttributePerLine: false
};
這個(gè)配置里詳細(xì)說(shuō)明了每一項(xiàng)的意思,可以根據(jù)自己的需求修改。除了這些配置外,我們還要配置vscode,以保證所有人編輯項(xiàng)目是都采用同樣的設(shè)置。
配置.vscode/settings.json
配置.vscode/settings.json文件
{
// 使用項(xiàng)目的typescript而不是vscode自帶的
"typescript.tsdk": "./node_modules/typescript/lib",
"eslint.validate": [
"JavaScript",
"javascriptreact",
"vue",
"typescript",
"typescriptreact"
],
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.eol": "n",
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
配置完以后,有可能你會(huì)發(fā)現(xiàn)不生效,這時(shí)重啟一下vscode,嘗試一下是否會(huì)格式化,如果格式化異常可以查看一下問(wèn)題:
- 默認(rèn)格式化工具是否為prettier
在隨便一個(gè)文件內(nèi)容中右鍵,選擇“使用...格式化文檔”,會(huì)在編輯器上方彈出格式化工具
默認(rèn)工具一定要是Prettier,如果不是就選擇下面的“配置默認(rèn)格式化程序”,修改為Prettier
- Prettier工具的運(yùn)行狀態(tài)
查看編輯器右下角的Prettier前面是否為對(duì)號(hào),如果不是就點(diǎn)擊這里,會(huì)在看到輸出日志,日志中的報(bào)錯(cuò)信息會(huì)告訴我們?nèi)绾涡迯?fù)。我在上面使用的.cjs就是日志中提示的方式。
一般完成了這些問(wèn)題,基本就可以正常使用了。
最后我們?cè)趐ackage.json中的scripts里添加eslint格式化命令:
"lint": "eslint --ext .js,.ts,.vue src/ --fix"
配置husky
配置好代碼格式化以后,我們也無(wú)法保證開(kāi)發(fā)人員會(huì)按照要求去寫(xiě)代碼,因此需要做強(qiáng)制的檢測(cè),在提交代碼前,執(zhí)行檢測(cè),所以我們需要增加Git Hook鉤子,在執(zhí)行g(shù)it commit命令時(shí)執(zhí)行指定的檢測(cè)腳本或者命令。
要注意:項(xiàng)目必須是已經(jīng)上傳到Git倉(cāng)庫(kù)才能使用此工具。
首先安裝好對(duì)應(yīng)的工具
pnpm install -D husky
使用一下命令在項(xiàng)目根目錄下增加husky相關(guān)配置
# 增加配置文件,在項(xiàng)目根目錄下執(zhí)行此命令,會(huì)生成.husky目錄
npx husky install
# 增加鉤子攔截配置
npx husky add .husky/pre-commit "pnpm lint"
到這里項(xiàng)目初始化就完成了,我們還有一些其他的配置需要做,這個(gè)就留在下一節(jié)吧。