前言
截止發(fā)文時(shí)間,vite正式版已經(jīng)發(fā)布快2年時(shí)間了,vue3也發(fā)布到3.2版本了,它的周邊設(shè)施基本上已經(jīng)齊活了。也是時(shí)候再次重構(gòu)下我那個(gè)vue3.0的開(kāi)源項(xiàng)目了。
本篇文章就記錄下我的重構(gòu)過(guò)程,歡迎各位感興趣的開(kāi)發(fā)者閱讀本文。
環(huán)境搭建
1年多前,我用Vue Cli 4.5構(gòu)建的此項(xiàng)目,有關(guān)此項(xiàng)目的更多細(xì)節(jié)請(qǐng)移步我的另一篇文章使用Vue3重構(gòu)Vue2項(xiàng)目。同樣的,從CLI遷移到Vite仍然是在package.json中添加vite的依賴項(xiàng),在項(xiàng)目中添加它的配置文件。
此次項(xiàng)目構(gòu)建還加入了volta的相關(guān)配置,對(duì)此感興趣的開(kāi)發(fā)者請(qǐng)移步:強(qiáng)大的JavaScript工具管理器Volta
新增vite相關(guān)依賴項(xiàng)
我們打開(kāi)package.json,找到devDependencies字段,移除CLI相關(guān)的依賴,添加vite相關(guān)的依賴,如下所示:
- +綠色標(biāo)識(shí)代表新增
- -紅色標(biāo)識(shí)代表移除
{ “dependencies”: {- “compression-webpack-plugin”: “^5.0.1”, }, “devDependencies”: {+ “@vitejs/plugin-vue”: “^3.0.0”,+ “vite”: “^3.0.0”,+ “vue-tsc”: “^0.38.4”,+ “@types/node”: “^18.6.3”,- “sass-loader”: “^8.0.2”,- “@vue/cli-plugin-babel”: “~4.5.0”,- “@vue/cli-plugin-eslint”: “~4.5.0”,- “@vue/cli-plugin-router”: “~4.5.0”,- “@vue/cli-plugin-typescript”: “~4.5.0”,- “@vue/cli-plugin-vuex”: “~4.5.0”,- “@vue/cli-service”: “~4.5.0”,- “@vue/compiler-sfc”: “^3.0.0-0”- }}
隨后,我們找到scripts字段,修改項(xiàng)目的運(yùn)行與構(gòu)建命令。
{ “scripts”: { “serve”: “vite –open”, “build”: “vue-tsc –noEmit && vite build”, “preview”: “vite preview” }}
vite3.x版本要求node版本必須大于14.18.0,因此我們需要在engines字段中做一下提示,如下所示:
{ “engines”: { “npm”: “please-use-yarn”, “yarn”: “>= 1.0.0”, “node”: “>= 14.18.0” }}
除了上述配置外,我們還需要在項(xiàng)目的根目錄創(chuàng)建.npmrc文件,寫(xiě)入下述內(nèi)容:
engine-strict = true
配置完成后,我們執(zhí)行在終端執(zhí)行yarn install安裝依賴即可。
在上述配置中,我們還強(qiáng)制設(shè)置了yarn作為項(xiàng)目的包管理工具,如果項(xiàng)目開(kāi)發(fā)成員使用了npm install則不會(huì)開(kāi)始安裝依賴并提示其使用yarn來(lái)安裝依賴。
添加vite配置文件
在vite中,index.html已經(jīng)從public文件夾遷移到項(xiàng)目的根目錄下了,官方文檔對(duì)此的解釋為:在開(kāi)發(fā)期間 Vite 是一個(gè)服務(wù)器,而 index.html 是該 Vite 項(xiàng)目的入口文件。
有關(guān)此變更的詳細(xì)解釋請(qǐng)移步:index.html 與項(xiàng)目根目錄
接下來(lái),我們?cè)陧?xiàng)目的根目錄創(chuàng)建index.html文件(將public目錄下的文件刪除)
- 引入靜態(tài)文件時(shí)不需要使用%PUBLIC_URL%作為占位符,可以直接寫(xiě)/來(lái)訪問(wèn),vite會(huì)將其解析到public根目錄下
- 通過(guò)
注意:如果你的項(xiàng)目比較復(fù)雜,有多個(gè)入口,那么就將index.html文件放到對(duì)應(yīng)入口的根目錄下。
最后,我們創(chuàng)建vite.config.ts文件,配置代碼如下所示:
- 設(shè)置開(kāi)發(fā)環(huán)境的端口號(hào)
- 設(shè)置路徑別名
- 設(shè)置打包后base地址以及打包輸出目錄
import { defineConfig } from “vite”;import { resolve } from “path”;import vue from “@vitejs/plugin-vue”;const IS_PRODUCTION = process.env.NODE_ENV === “production”;export default defineConfig({ plugins: [vue()], server: { host: true, port: 8020, proxy: {} }, resolve: { // 設(shè)置路徑別名 alias: { “@”: resolve(__dirname, “./src”), “*”: resolve(“”) } }, base: IS_PRODUCTION ? “/chat-system” : “./”, define: { “process.env”: {} }, build: { outDir: resolve(__dirname, “dist”) }});
注意:我的項(xiàng)目配置比較簡(jiǎn)單,它只有一個(gè)入口,打包后只會(huì)部署到生產(chǎn)環(huán)境。如果你的項(xiàng)目較為復(fù)雜,也不必太過(guò)擔(dān)心,你的應(yīng)用場(chǎng)景vite也是支持的,按照文檔進(jìn)行相關(guān)的配置就好,如下所示:
- 自定義構(gòu)建
- 多頁(yè)面應(yīng)用模式
- 環(huán)境變量和模式
當(dāng)你的項(xiàng)目有多個(gè)入口時(shí),期望通過(guò)不同命令來(lái)啟動(dòng)不同項(xiàng)目時(shí),你可以使用yarn的–cwd指令來(lái)指定其運(yùn)行時(shí)的工作目錄。
例如:你有兩個(gè)入口,那么就在src目錄下創(chuàng)建兩個(gè)文件夾:**A、B **。A和B中分別有自己的index.html、main.ts以及package.json文件(配置start、build命令,傳入不同的參數(shù)來(lái)啟動(dòng)/構(gòu)建不同入口的項(xiàng)目)
根目錄的package.json中你就可以配置啟動(dòng)/構(gòu)建命令為:
{ “scripts”: { “dev:A”: “yarn –cwd ./src/A run start”, “dev:B”: “yarn –cwd ./src/B run start”, “build:A”: “yarn –cwd ./src/A run build”, “build:B”: “yarn –cwd ./src/B run build”, “build”: “vue-tsc –noEmit && vite build”, “preview”: “vite preview” },}
最后,我們以A入口為例,列舉下package.json文件中的配置:
{ “name”: “A”, “version”: “1.0.0”, “main”: “index.js”, “license”: “MIT”, “scripts”: { “start”: “vite serve –config ../../vite.config-A.ts –mode development”, “build”: “vue-tsc –noEmit && vite build –config ../../vite.config-A.ts –mode production” }}
升級(jí)Vue周邊依賴項(xiàng)
vue3.2的單文件組件引入了setup規(guī)范,它可以讓代碼變得更簡(jiǎn)潔,可以使用純 TypeScript 聲明 props 和拋出事件,有著更好的運(yùn)行時(shí)性能。這些優(yōu)點(diǎn)讓我有了升級(jí)vue版本的動(dòng)力,之前的3.0版本寫(xiě)起來(lái)很臃腫,需要return一大堆東西,甚是麻煩。
打開(kāi)package.json文件作出下述變動(dòng):
- 更新了vue、router、vuex的版本號(hào)
- 新增了vueuse包,這是一個(gè)基于 Composition API 的實(shí)用函數(shù)集合,封裝了一些常用的功能(實(shí)時(shí)獲取鼠標(biāo)位置、防抖、節(jié)流、獲取客戶端系統(tǒng)主題等),可以避免一些重復(fù)性的工作內(nèi)容,大大提升開(kāi)發(fā)效率。
{ “dependencies”: { – “vue”: “^3.0.0-0”,- “vue-class-component”: “^8.0.0-0”- “vue-router”: “^4.0.0-0”,- “vuex”: “^4.0.0-0”,+ “vue”: “^3.2.37”,+ “vue-router”: “^4.1.3”,+ “vuex”: “^4.0.2”,+ “@vueuse/components”: “^8.9.2”,+ “@vueuse/core”: “^8.9.2” }}
最后執(zhí)行yarn install即可完成整個(gè)環(huán)境的搭建,本章節(jié)重構(gòu)完成后的完整文件請(qǐng)移步:
- .npmrc
- index.html
- package.json
- vite.config.ts
經(jīng)驗(yàn)分享
本章節(jié)就跟大家分享下,我切到新環(huán)境后做的一些優(yōu)化點(diǎn)以及遇到的問(wèn)題和解決方案。
本章節(jié)修改到的文件,完整文件代碼如下:
- package.json
- tsconfig.json
require不存在
一切準(zhǔn)備就緒后,按下了項(xiàng)目啟動(dòng)按鈕,很快啊,651ms項(xiàng)目就啟動(dòng)了,不愧是vite速度就是快,嘴角瘋狂上揚(yáng)。
瀏覽器加載完項(xiàng)目后,我傻眼了,我的登陸界面呢:new_moon_with_face:?順勢(shì)打開(kāi)控制臺(tái),發(fā)現(xiàn)報(bào)錯(cuò)require is not defined。
解決方案
打開(kāi)Login.vue文件后,發(fā)現(xiàn)我用require導(dǎo)入了一些圖片文件,在VueCLI環(huán)境下的require會(huì)交給webpack處理。在vite中是不存在的,那么我們就需要查看vite是怎么處理靜態(tài)文件了。
翻了下文檔后,在靜態(tài)資源處理章節(jié)發(fā)現(xiàn)他有兩種處理方法:
- 通過(guò)import語(yǔ)句直接導(dǎo)入圖片
- 通過(guò)new URL來(lái)導(dǎo)入圖片
我打算將所有組件都重構(gòu)為setup形式,因此直接使用import方式來(lái)導(dǎo)入圖片可以保持組件的一致性,可以大大提升可讀性。
我們寫(xiě)個(gè)簡(jiǎn)單的demo來(lái)嘗試下,如下所示:
已經(jīng)可以正確解析出圖片的路徑了。
注意:本文不會(huì)過(guò)多講解setup的語(yǔ)法,對(duì)此不了解的開(kāi)發(fā)者請(qǐng)移步:?jiǎn)挝募M件 – script setup
new URL方式可以用來(lái)引入一個(gè)動(dòng)態(tài)資源,例如:你有一份json配置文件,里面描述了圖片的文件名,這些圖片是放在項(xiàng)目中的,他們的訪問(wèn)前綴都一樣,此時(shí)你就可以通過(guò)遍歷json文件通過(guò)此方式來(lái)引入這些圖片。
vue相關(guān)模塊不存在
我試圖從vue的包中導(dǎo)入shallowRef時(shí),編輯器報(bào)錯(cuò): TS2305: Module ‘xxx’ has no exported member ‘shallowRef’. 。
解決方案
經(jīng)過(guò)一番排查后,是因?yàn)轫?xiàng)目typescript版本是3.x,跟3.2版本的vue不兼容,需要將其升級(jí)至4.x版本。
打開(kāi)package.json文件,作出如下所示的修改,重新執(zhí)行yarn install命令即可。
{ “devDependencies”: {- “typescript”: “~3.9.3”,+ “typescript”: “~4.7.4”, }}
setup中的變量警告未被使用
當(dāng)我在setup中聲明了一個(gè)函數(shù)或者導(dǎo)入了一個(gè)文件,在template中已經(jīng)使用了,但是他卻報(bào)錯(cuò)ESLint: ‘xx’ is assigned a value but never used.(@typescript-eslint/no-unused-vars)
解決方案
在 eslint-plugin-vue 插件的Issues中看到有人遇到了跟我同樣的問(wèn)題,在v9.0.0: regression in unused variables in script setup中我找到了解決方案。
我們需要升級(jí)下@vue/eslint-config-typescript和eslint-plugin-vue的版本號(hào),如下所示:
{ “devDependencies”: { “@vue/eslint-config-typescript”: “^11.0.0”, “eslint-plugin-vue”: “^9.0.0” }}
隨后在eslint的配置文件中,添加parser屬性,重新執(zhí)行yarn install命令即可。
module.exports = {+ parser: ‘vue-eslint-parser’}
模塊隔離
Vite 使用 esbuild 來(lái)轉(zhuǎn)譯 TypeScript,并受限于單文件轉(zhuǎn)譯的限制,因此需要在ts的配置文件中將isolatedModules屬性設(shè)置為true。
{ “compilerOptions”: { “isolatedModules”: true }}
process不存在
在路由配置文件中,我們需要從process中獲取BASE_URL,此時(shí)編輯器報(bào)錯(cuò): TS2591: Cannot find name ‘process’. Do you need to install type definitions for node? Try npm i –save-dev @types/node and then add ‘node’ to the types field in your tsconfig.
解決方案
由于vite中已經(jīng)沒(méi)有process了,需要用import.meta來(lái)代替,那么上述的路由配置文件就應(yīng)該改為:
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), // 地址欄不帶# routes});
無(wú)法導(dǎo)入json文件
在表情面板模塊,我將每個(gè)表情都放入了json文件中。在vite中引入文件需要使用import,改了寫(xiě)法后,發(fā)現(xiàn)它報(bào)錯(cuò):Cannot find module ‘xx.json’. Consider using ‘–resolveJsonModule’ to import module with ‘.json’ extension.
解決方案
我們需要在ts的配置文件中添加resolveJsonModule屬性,如下所示:
{ “compilerOptions”: {+ “resolveJsonModule”: true }}
使用vite提供的對(duì)象
當(dāng)我想使用vite所提供的glob屬性時(shí),發(fā)現(xiàn)編輯器報(bào)錯(cuò): TS2339: Property ‘glob’ does not exist on type ‘ImportMeta’.
解決方案也很簡(jiǎn)單,我們只需要在ts的配置文件中添加vite/client即可,如下所示:
{ “compilerOptions”: { “types”: [+ “vite/client” ] }}
獲取全局屬性
當(dāng)我們使用一些第三方庫(kù)的時(shí)候它會(huì)在globalProperties掛載一些方法,當(dāng)在ts+setup環(huán)境下使用時(shí),會(huì)出現(xiàn)類型無(wú)法推導(dǎo)問(wèn)題,如下所示:
第三方庫(kù)提供了一個(gè)$connect方法
我們通過(guò)proxy來(lái)訪問(wèn)
他會(huì)出現(xiàn)報(bào)錯(cuò): TS2339: Property ‘xx’ does not exist on type ‘ComponentPublicInstance{}, {}, {}, {}, {}, {}, {}, {}, false, ComponentOptionsBase >’.
解決方案
我們可以在type目錄下新建一個(gè)global文件夾,在這里存放一些我們擴(kuò)展出來(lái)的全局方法。
如下所示,我們:
- 創(chuàng)建了一個(gè)useCurrentInstance方法
- 將globalProperties屬性暴露出去
import { ComponentInternalInstance, getCurrentInstance } from “vue”;export default function useCurrentInstance() { const { appContext } = getCurrentInstance() as ComponentInternalInstance; const proxy = appContext.config.globalProperties; return { proxy };}
我們?cè)诮M件中使用暴露出來(lái)的proxy即可,如下所示:
無(wú)法識(shí)別NodeJS類型
我們?cè)诮osetinterval和setTimeout指定類型時(shí),會(huì)用到NodeJS模塊,會(huì)出現(xiàn)報(bào)錯(cuò):ESLint: ‘NodeJS’ is not defined.(no-undef)。
這個(gè)問(wèn)題的解決方案是:打開(kāi)eslint的配置文件在globals對(duì)象中添加NodeJS選項(xiàng),如下所示:
{ globals: { NodeJS: true }}
除了將類型聲明為NodeJS.Timeout外,我們還可以將其聲明為number類型,但是需要攜帶window前綴(window.setinterval/window.setTimeout)
管理靜態(tài)資源
當(dāng)我們?cè)诮M件中使用import導(dǎo)入很多靜態(tài)資源時(shí),組件看起來(lái)會(huì)很雜亂。此時(shí)我們可以將其按照功能類型進(jìn)行拆分。我的做法如下:
- 在src下創(chuàng)建resource文件夾
- 根據(jù)功能類型創(chuàng)建ts文件,將其導(dǎo)出
import defaultAvatar from “@/assets/img/login/LoginWindow_BigDefaultHeadImage@2x.png”;import defaultLoginBtnIcon from “@/assets/img/login/icon-enter-undo@2x.png”;import //imgq8.q578.com/ef/0816/df977c06ae2a4ebf.jpg from “@/assets/img/login/icon-enter-undo@2x.png”;import loginBtnHover from “@/assets/img/login/icon-enter-hover@2x.png”;import loginBtnDown from “@/assets/img/login/icon-enter-down@2x.png”;export { defaultAvatar, defaultLoginBtnIcon, //imgq8.q578.com/ef/0816/df977c06ae2a4ebf.jpg, loginBtnHover, loginBtnDown};
分離模版與邏輯代碼
我的項(xiàng)目中有一個(gè)很復(fù)雜的組件,有上千行代碼,去年我用CompositionAPI優(yōu)化了一版,將組件中所有的方法都拆分成了一個(gè)個(gè)獨(dú)立的ts文件,做到了邏輯代碼與模版代碼分離,模版需要什么方法我就通過(guò)import導(dǎo)入進(jìn)來(lái),最后return給模版。
在拆分出來(lái)的文件中,是沒(méi)有辦法訪問(wèn)vue提供的一些內(nèi)置屬性的,比如:defineProps、defineEmits、getCurrentInstance。因此我想了一個(gè)奇妙的方法:將這些無(wú)法訪問(wèn)的屬性都存起來(lái)。具體的做法請(qǐng)移步我另一篇文章:使用Vue3的CompositionAPI來(lái)優(yōu)化代碼量-創(chuàng)建InitData.ts文件
適配方案
vue3.2的setup語(yǔ)法糖支持import進(jìn)來(lái)的方法都能在模版中直接使用,那我們的組件又可以精簡(jiǎn)下了,我花了億點(diǎn)點(diǎn)時(shí)間對(duì)其進(jìn)行了適配。
之前我們想獲取組件的emit需要從context中拿,props聲明并從setup函數(shù)的參數(shù)中獲取,如下所示:
現(xiàn)在我們就不用這么麻煩了,直接通過(guò)defineProps、defineEmits獲取即可,如下所示:
鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。