逐步搭建出團隊的 vue3 前端架構

作者:codexu

https://juejin.cn/post/7025524870842679310

前言

由於 vue3.2 版本的發佈,<script setup> 的實驗性標誌已經去掉,這說明這個語法提案已經正式開始使用,並且我個人對這個方案表示非常喜歡,其他的更新 [1] 請自行了解。到目前爲止,我認爲 vue3 已經完全可以用於生產環境。在此將我的開發體驗,總結至此,分享給大家。

我認爲前端架構核心工作是定製一套適合當前業務需求的解決方案,從而降低需求的增加而帶來的技術實現的複雜度。下面我將從 16 個方向,逐漸帶領大家搭建一套屬於你自己的腳手架,制定一套合理的解決方案,爲項目打下良好的基礎,與同伴形成合適的開發習慣。

由於篇幅問題,以講解實現思路爲主,希望大家友善發言,共同進步!

目錄

  1. 搭建腳手架

使用 vue-cli 或 vite ,通過一系列的配置,初始化一個開發模板,無需從零開始搭建開發環境,可以有效的提升開發效率,相信也是大多數開發者接手一個新項目所使用的一種方式。儘管官方提供的腳手架已經足夠優秀,但未必是真正符合我們自己團隊的使用習慣,所以從官方的基礎上,開發一款屬於我們自己的腳手架,能更多的提升開發效率。

1.1 前端腳手架應具備哪些功能?

1.2 如何開發一款自己的腳手架?

提到構建前端工程化中腳手架,相信大家已經看過不少文章,幾年前我也曾經寫過一篇關於腳手架構建的文章 [69],隨便搜一下關鍵詞可以看到很多相關的文章,在這裏不做太多的介紹,主要講一些這些文章中很少提到的如何根據選項生成文件。

1.3 如何根據選項生成文件?

說實話我也不知道大佬們是怎麼根據各種配置編譯成相應的文件,這塊希望大家踊躍發言,尋求一種更佳高效簡潔的方式。在這裏跟大家分享一下我的方案:

交互方面,搭建過腳手架的同學一定知道 inquirer[70],這個庫可以很方便的通過交互式操作獲取到我們選擇的一些自定義配置參數。那麼問題來了,如何通過這些配置相應的創建對應的文件呢?

這裏我推薦使用 EJS[71] + Prettier[72] 生成代碼,通過 fs-extra[73] 寫入最終的文件。

EJS 是一款 JavaScript 模板引擎,我們可以通過傳入參數,生成對應的代碼串,例如創建一個 package.ejs 用來生成 package.json 中,如果我們選擇使用了 scss 作爲 CSS 預處理器,然後將 sass 和 stylelint-scss 作爲項目的安裝依賴:

<% if (precss === 'scss') { -%>
  "sass": "1.26.5",
  "stylelint-scss": "^3.20.1",
<% } -%>
複製代碼

模板引擎可以幫你通過參數生成代碼,它並不會限制你生成任何類型的代碼文件,因爲我們生成的是純代碼,最後通過讀取 .ejs 文件對應生成相應的類型文件即可。

Prettier 是一款代碼格式化工具,相信大家對它並不陌生。使用 EJS 生成的目的還是給開發人員閱讀和編輯,所以生成的代碼應該符合最終的格式要求,因爲後續我們會爲腳手架添加 ESLint 和 StyleLint 等工具,剛剛創建的項目裏面一堆紅線報錯可是十分不友好的。

import prettier = require("prettier");
prettier.format(code, { parser: 'json' }))
複製代碼

parser 是 prettier 的解析器,常見的 typescript、css、less、json 等文件都可以進行格式化。

  1. 基於 vite 的搭建基礎模板

最早搭建 vue3 腳手架的時候,我選擇的用 vue/cli 搭建,因爲生態不健全,有些基於 webpack 的功能無法使用,但現在 vite 生態已經比較完善了,所以重構腳手架,由 webpack 轉向 vite,這一步極大的提升了開發體驗。

2.1 創建基本模板項目

npm init vite@latest
yarn create vite
pnpm create vite
複製代碼

然後按照提示操作即可,vite 提供的選項很少,只有 vue 或 vue + ts,不像 vue/cli 提供那麼多的配置方式,所以剩下的東西需要我們手動配置。

當然 vite 也提供了很多模板,但是我認爲做加法比做減法更加容易,在衆多的模板中很難找到適合我們自己的。

2.2 常用插件推薦

這裏先簡單瞭解幾個好用的 vite 插件:

爲什麼只推薦這麼幾個插件?因爲 vite 對許多 webpack 需要安裝的 loader 或 plugin 都有着天生的支持,比如 less、sass、typescript,後續會在相應的章節說明用法。

  1. 使用 Typescript

vue2.x 版本對 TypeScript 的支持是硬傷,而 TypeScript 對大型項目的保障能力是被普遍認可的。這一點在 vue3.x 版本中得到了非常友好的支持。

Vite 天然支持引入 .ts 文件。

這裏對 tsconfig.json 做了一些修改:

{
  "compilerOptions"{
    "types"["vite/client"],
    "baseUrl""src",
    "paths"{
      "@/*"["./*"]
    }
  },
  "exclude"["node_modules"]
}
複製代碼

在初期使用 typeScript 的時候,很多人都很喜歡使用 any 類型,把 typeScript 寫成了 anyScript ,雖然使用起來很方便,但是這就失去了 typeScript 的類型檢查意義了,當然寫類型的習慣是需要慢慢去養成的,不用急於一時。

  1. 配置環境變量

vite 提供了兩種模式:具有開發服務器的開發模式(development)和生產模式(production)。

這裏我們可以建立 4 個 .env 文件,一個通用配置和三種環境:開發、測試、生產。

4.1 配置模式

NODE_ENV=development # 開發模式
NODE_ENV=production # 生產模式
複製代碼

package.json 內 script 需要增加 staging 命令

"script"{
  "build""vue-tsc --noEmit && vite build",
  "staging""vue-tsc --noEmit && vite build --mode staging",
  "serve""vite preview --host"
}
複製代碼

4.2 常用的環境變量

推薦使用以下常見的三個變量:

接口請求地址。

通常後端會區分三種環境,部署在不同的地址下。

靜態資源地址。

靜態資源我是不建議你直接放在項目中,這會導致項目倉庫變得巨大。

本地開發和測試環境我會選在使用本地搭建的靜態資源服務器,你可以找後端運維的同學幫你搭建,或者你使用 http-server 在本地啓動一個服務器也可以。生產環境建議上傳至 OSS。

構建資源公共路徑。

這個與 vue/cli 中的 publicPath 同理,有的時候你構建的項目並不是存放在跟路徑下,例如 http://ip:port/{項目名}

4.3 封裝靜態資源文件

如果你配置了 VITE_APP_STATIC_URL 靜態資源環境變量,那麼你需要封裝以下兩個東西:

baseStaticUrl.ts

// 處理靜態資源鏈接
export default function baseStaticUrl(src = '') {
  const { VITE_APP_STATIC_URL } = import.meta.env;
  if (src) {
    return `${VITE_APP_STATIC_URL}${src}`;
  }
  return VITE_APP_STATIC_URL as string;
}
複製代碼

靜態資源組件

靜態資源主要有圖片、音頻和視頻三種常見的形式。

<script lang="ts" setup>
import { computed, ref, Ref, withDefaults, onMounted, watch } from 'vue';
import { baseStaticUrl } from '@/libs/utils';
import useDevice from '@/hooks/useDevice';

interface Props {
  src?: string;
  type?: string;
  autoplay?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
  src: '',
  type: 'img',
  autoplay: true,
});

const envSrc = computed(() => baseStaticUrl(props.src));
// 處理視頻自動播放(解決 chrome 無法自動播放的問題)
const { deviceType } = useDevice();
const poster = computed(() =>
  deviceType.value === 'desktop' ? '' : baseStaticUrl(props.src),
);
const videoRef: Ref<HTMLVideoElement | null> = ref(null);

// 解決移動端視頻無法自動播放的問題
function videoAutoPlay() {
  if (props.type === 'video' && videoRef.value !== null) {
    videoRef.value.src = baseStaticUrl(props.src);
  }
  if (props.autoplay && videoRef.value) {
    videoRef.value.oncanplay = () => {
      if (videoRef.value) videoRef.value.play();
    };
  }
}

// 自動播放視頻
onMounted(() => { videoAutoPlay();});
// 監聽視頻 src,如果存在則自動播放

watch(envSrc, () => { if (videoRef.value) videoRef.value.play(); });
</script>

<script lang="ts">
export default { name: 'StaticFile' };
</script>

<template>
  <img v-if="type === 'img'" :src="envSrc" />
  <video ref="videoRef" v-else-if="type === 'video'" muted :poster="poster" />
  <audio v-else :src="envSrc" />
</template>
複製代碼

4.4 封裝 SVG 的圖標組件

svg 圖標比較小,而且都是可讀的 xml 文本,所以我們把它直接放在項目中即可,通過 vite-plugin-svg-icons 插件,實現自動引入 svg 圖標。

配置 vite.config.ts:

plugins: [
  viteSvgIcons({
    iconDirs: [resolve(process.cwd()'src/assets/icons')],
    symbolId: 'icon-[dir]-[name]',
  }),
]
複製代碼

封裝一個 vue 組件:

<script setup lang="ts">
import { computed, withDefaults } from 'vue';

interface Props {
  prefix?: string;
  name?: string;
  color?: string;
}

const props = withDefaults(defineProps<Props>(), {
  prefix: 'icon',
  name: '',
  color: '#000',
});

const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>

<template>
  <svg aria-hidden="true">
    <use :xlink:href="symbolId" :fill="color" />
  </svg>
</template>
複製代碼

首先將下載的 .svg 圖標放入 @/assets/icons 文件夾下

<svg-icon  />
複製代碼
  1. 按需自動引入組件

unplugin-vue-components[77] 是一款非常強大的插件(極力推薦),核心功能就是幫助你自動按需引入組件,Tree-shakable,只註冊你使用的組件。這裏說一下他的兩個核心使用方式和配置方式。

此插件不僅支持 vue3,同時也支持 vue2,並且支持 Vite、Webpack、Vue CLI、Rollup。

5.1 安裝與配置

安裝:

npm i unplugin-vue-components -D
複製代碼

配置:

// vite.config.ts
import Components from 'unplugin-vue-components/vite'

export default defineConfig({
  plugins: [
    Components({ /* options */ }),
  ],
})
複製代碼

這裏的 options 可以配置一些選項,後面提到的組件庫註冊會使用到。

5.2 改變全局組件註冊方式

我們通常將全局的組件封裝在 @/src/components 中,然後通過 app.component() 註冊全局組件。使用此插件後,無需手寫註冊,直接在模板中使用組件即可:

這裏引入官方的示例:

<template>
  <div>
    <HelloWorld msg="Hello Vue 3.0 + Vite" />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
複製代碼

自動編譯爲:

<template>
  <div>
    <HelloWorld msg="Hello Vue 3.0 + Vite" />
  </div>
</template>

<script>
import HelloWorld from './src/components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>
複製代碼

5.3 自動引入組件庫

在使用組件庫時,常規組件我們也會註冊到全局,如果使用局部註冊由於頁面中會使用到多個組件,會非常麻煩,所以這個功能絕佳,例如我們使用 ant-design-vue 組件庫。

直接在模板中使用即可,無需手動註冊或局部引用:

<template>
  <a-button>按鈕</a-button>
</template>
複製代碼

當然,你還需要在 vite 中引入它的解析器:

import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    Components({
      resolvers: [
        AntDesignVueResolver(),
      ]
    })
  ],
})
複製代碼

目前支持的解析器,根據你的喜好去選擇:

  1. 樣式

項目中最好使用通用樣式,可以創建 src/styles 目錄存放,這裏推薦一些分類:

styles
  ├── antd # 組件庫樣式覆蓋,命名自取,這裏以 ant design 爲例
  ├── color.less # 顏色
  ├── index.less # 入口
  ├── global.less # 公共類
  ├── transition.less # 動畫相關
  └── variable.less # 變量
複製代碼

6.1 預設基礎樣式

相信用過 normalize[92] 的同學不在少數,它可以重置 css 樣式,使各瀏覽器效果保持一致。後面的章節會提到 tailwind.css,它內置了預設樣式重置的功能,與 normalize 還是有一定的區別,有興趣的同學可以瞭解一下 [93]。

6.2 CSS 預處理器

雖然 vite 原生支持 less/sass/scss/stylus,但是你必須手動安裝他們的預處理器依賴,例如:

npm install -D less
複製代碼

如何選擇預處理器?

推薦使用你是所使用的組件庫的樣式語言,因爲 css 預處理器學會一種後,入手其他幾乎沒有學習成本。

6.3 開啓 scoped

沒有加 scoped 屬性,會編譯成全局樣式,造成全局污染。

<style scoped></style>
複製代碼

6.4 深度選擇器

有時我們可能想明確地制定一個針對子組件的規則。

如果你希望 scoped 樣式中的一個選擇器能夠作用得 “更深”,例如影響子組件,你可以使用 >>> 操作符。有些像 Sass 之類的預處理器無法正確解析 >>>。這種情況下你可以使用 /deep/ 或 ::v-deep 操作符取而代之——兩者都是 >>> 的別名,同樣可以正常工作。

  1. 佈局

頁面整體佈局是一個產品最外層的框架結構,往往會包含導航、頁腳、側邊欄等。在頁面之中,也有很多區塊的佈局結構。在真實項目中,頁面佈局通常統領整個應用的界面,有非常重要的作用,所以單獨拆分出來也是非常有必要的。

在腳手架中,所有的通用佈局組件都應該放在 src/layouts 中,這種封裝比較簡單,這裏就不貼代碼了,大家按照自己實際情況自行發揮,在此僅提供一下封裝思路。

7.1 常規的佈局

BasicLayout

基礎頁面佈局,包含了頭部導航,側邊欄等。

BlankLayout

空白的佈局。

7.2 特殊的佈局

RouteLayout

如果你的項目在路由切換中需要對某些二級頁面進行緩存,那麼推薦你創建一個 RouteLayout,通過路由 meta 中的配置,返回 router-view 或者使用 keep-alive 包裹的 router-view

UserLayout

用於用戶登錄註冊等頁面抽離出來。

PageLayout

基礎佈局,包含了麪包屑等信息,內含 slot。

  1. 集成 Tailwind.css

Tailwind.css[94] 在我第一次看到它的時候,內心是比較反感的,但實際上手之後又覺得真香。從 vue2 項目中,我已經引入了 tailwind,整體的開發結果就是,基本很少再使用 <style> 標籤去轉本定義一些 class 和樣式,畢竟起名字這種事,一個是涉及到規範,一個是涉及到英語。如果你選擇 tailwind,CSS 預處理器的作用就會顯得微乎其微,因爲你無需再自定定義各種變量和 mixins。

總體來說,學習成本並不高,花上兩個小時足夠上手,記住不用死記硬背那些類名。

8.1 效率提升

很多人總是說樣式要與 HTML 分離,現在爲什麼又要提倡 tailwind 這種與 HTML 緊密結合的工具?這是因爲現在使用 vue 這類框架已經高度組件化,樣式分離是爲了方便複用和維護,但在組件化面前樣式分離只能是降低開發效率。

下面介紹一下 tailwind 提供了哪些提升效率的功能:

8.2 JIT 模式

如果你的環境支持 postcss8( vue/cli 構建的 vue2 項目是 postcss7 ),那麼 JIT 模式直接帶你起飛。

如果你使用 vscode 那你一定要安裝 Tailwind CSS IntelliSense[95] 插件,它可以自動補全類名,顯著降低學習成本。

8.3 關於打包體積

使用默認配置,未壓縮是 3739.4kB ,Gzip 壓縮 是 293.9kB,Brotli 壓縮 是 73.2kB。這似乎看起來很大,這是因爲 tailwind 提供了成千上萬的功能類,其中絕大部分你不會使用到。

當構建生產時,你應該使用 purge 選項來 tree-shake 優化未使用的樣式,並優化您的最終構建大小當使用 Tailwind 刪除未使用的樣式時,很難最終得到超過 10kb 的壓縮 CSS。

還有一點,Atom CSS 極大的提升了樣式的複用程度,從而直接降低了構建體積。

9.vuex 替代方案 pinia

由於 vuex 4 對 typescript 的支持讓人感到難過,所以狀態管理棄用了 vuex 而採取了 pinia[96]。

忘記在哪看到,尤大好像說 pinia[97] 可能會代替 vuex,所以請放心使用。

9.1 爲什麼採用 Pinia ?

下面簡單的介紹一下如何使用 Pinia,並對比 vuex 有哪些區別與注意事項,具體請參考官方文檔 [99]。

9.2 創建 Store

Pinia 已經內置在腳手架中,並且與 vue 已經做好了關聯,你可以在任何位置創建一個 store:

import { defineStore } from 'pinia'

export const useUserStore = defineStore({
  id: 'user',
  state: () =>({}),
  getters: {},
  actions: {}
})
複製代碼

這與 Vuex 有很大不同,它是標準的 Javascript 模塊導出,這種方式也讓開發人員和你的 IDE 更加清楚 store 來自哪裏。

Pinia 與 Vuex 的區別:

9.3 State

創建好 store 之後,可以在 state 中創建一些屬性了:

state: () =({ name: 'codexu', age: 18 })
複製代碼

將 store 中的 state 屬性設置爲一個函數,該函數返回一個包含不同狀態值的對象,這與我們在組件中定義數據的方式非常相似。

在模板中使用 store:

現在我們想從 store 中獲取到 name 的狀態,我們只需要使用以下的方式即可:

<h1>{{userStore.name}}</h1>

const userStore = useUserStore()
return { userStore }
複製代碼

注意這裏並不需要 userStore.state.name

雖然上面的寫法很舒適,但是你一定不要用解構的方式去提取它內部的值,這樣做的話,會失去它的響應式:

const { name, email } = useUserStore()
複製代碼

9.4 Getters

Pinia 中的 getter 與 Vuex 中的 getter 、組件中的計算屬性具有相同的功能,傳統的函數聲明使用 this 代替了 state 的傳參方法,但箭頭函數還是要使用函數的第一個參數來獲取 state ,因爲箭頭函數處理 this 的作用範圍:

getters: {
  nameLength() {
    return this.name.length
  },
  nameLength: state => state.name.length,
  nameLength: ()=> this.name.length ❌ 
}
複製代碼

9.5 Actions

這裏與 Vuex 有極大的不同,Pinia 僅提供了一種方法來定義如何更改狀態的規則,放棄 mutations 只依靠 Actions,這是一項重大的改變。

Pinia 讓 Actions 更加的靈活:

actions: {
  async insertPost(data){
    await doAjaxRequest(data);
    this.name = '...';
  }
}
複製代碼

9.6 Devtools

腳手架已內置下面的代碼,這將添加 devtools 支持:

import { createPinia, PiniaPlugin } from 'pinia'

Vue.use(PiniaPlugin)
const pinia = createPinia()
複製代碼

時間旅行功能貌似已經可以使用了,這塊後續會關注。

  1. 基於 mitt 處理組件間事件聯動

如果你曾經是 Vue2.x 的開發者,那麼請閱讀下面引用官方文檔 [100] 的一段話:

我們從實例中完全移除了 $on$off 和 $once 方法。$emit 仍然包含於現有的 API 中,因爲它用於觸發由父組件聲明式添加的事件處理函數。

在 Vue 3 中,已經不可能使用這些 API 從組件內部監聽組件自己發出的事件了,該用例暫沒有遷移的方法。但是該 eventHub 模式可以被替換爲實現了事件觸發器接口的外部庫,例如 mitt 或 tiny-emitter

10.1 爲什麼選擇 mitt ?

10.2 嚴重警告

我們已經無法在項目中使用 eventBus,僅推薦你在特殊場合下使用 mitt,它並不是開發的常態,你一定要確保知道自己在做什麼?否則你的項目將難以維護!!!

10.3 如何使用 mitt ?

在使用 mitt 前建議請閱讀官方文檔 [101]:

腳手架默認提供一個可以直接使用的對象:

import emitter from '@/libs/emitter';
複製代碼

當然你也可以引入已經安裝好的 mitt:

import mitt from 'mitt'

const emitter = mitt()
複製代碼

mitt 提供了非常簡單的 API,下面代碼是官方演示:

// listen to an event
emitter.on('foo'e => console.log('foo', e) )

// listen to all events
emitter.on('*'(type, e) => console.log(type, e) )

// fire an event
emitter.emit('foo'{ a: 'b' })

// clearing all events
emitter.all.clear()

// working with handler references:
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten
複製代碼
  1. 異步請求

絕大多數項目想必逃脫不了接口的對接,如果你的項目存在大量的接口,我建議做到以下幾點:

上述的主要目的就是在幫助我們簡化代碼和利於後期的更新維護。

11.1 基於 axios 的封裝

相信開發過 vue2 項目的同學已經對 axios 非常熟悉的,在這裏提供一些封裝的思路:

相關代碼(僅供參考)[102]

11.2 爲 axios 增加泛型的支持

到目前爲止,axios 請求返回的類型是 any,這時我們對請求後的數據進行操作時,沒有享受到 ts 帶來的類型提示,這顯然不符合我們的預期。

這時我們要做的就是重新聲明 axios 模塊:新建一個 shims.d.ts,然後在調用時加上泛型。

import { AxiosRequestConfig } from 'axios';

declare module 'axios' {
  export interface AxiosInstance {
    <T = any>(config: AxiosRequestConfig): Promise<T>;
    request<T = any>(config: AxiosRequestConfig): Promise<T>;
    get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  }
}
複製代碼

做好這一步後,你就必須在創建接口時,聲明請求相應數據的類型。

11.3 封裝更方便的 useRequest

設想一下,編寫請求代碼時,我們通常會定義這麼幾個變量:

尤其是 loading,我們需要在請求前設置爲 true,請求結束後設置爲 false。

上面的封裝方式,是對基礎的功能封裝,因爲我們在使用 vue3,所以可以進行再一次的封裝成爲 hook,我們使用起來會更加方便。

例如下面這個樣子:

使用 useRequest 定義一個接口:

export default getUserInfo(id) {
  return useRequest({
    method: 'get',
    url: '/api/user',
    params: { id }
  })
}
複製代碼

使用此接口:

const { data, loading } = getUserInfo();
複製代碼

注意這裏的 data 是響應式的。

這是我想到的一種思路,目前還沒有做很好的封裝,相關代碼僅供參考 [103],你也可以借鑑一些成熟方案,比如 vueuse 中的 useFetch[104],但是他是基於 Fetch API 設計的,並不符合我的預期要求,有更好的方案請大家在下面留言。

11.4 統一的 API 接口管理

自從前端和後端分家之後,前後端接口對接就成爲了常態,而對接接口的過程就離不開接口文檔,比較主流就是 Swagger,但是如何在前端項目中更好的去管理跟後端對接的接口呢?

在 src 目錄中 創建 api 目錄,內部目錄應按照後端制定的模塊創建。

每個模塊中創建多個 ts 文件,一個接口應對應一個 ts 文件,其中包含了以下內容:

統一去定義和管理 API 接口,只要後端規範的命名和你認真的寫好類型聲明,對前端來說 typescript 就是最好的接口文檔。

11.5 mock

vite 使用 mock 數據非常簡單,你可以使用 vite-plugin-mock[105] 插件,如果你瞭解 mockjs,你可以快速上手。

  1. 路由

路由和菜單是組織起一個應用的關鍵骨架。

12.1 創建路由三部曲

通常一個項目需要做到這幾步:

如果你的頁面比較多,建議你創建 routes 目錄,分模塊聲明路由。

參考代碼 [106]

12.2 使用 meta 豐富你的路由

vue-router4.x 支持 typescript,配置路由的類型是 RouteRecordRaw,這裏 meta 可以讓我們有更多的發揮空間,這裏提供一些參考:

這裏只提供一些思路,每個項目多多少少會涉及到這些問題,具體如何實現請查閱資料自行解決。

  1. 項目性能與細節優化

13.1 開啓 gzip

開啓 gzip 可以極大的壓縮靜態資源,對頁面加載的速度起到了顯著的作用。

使用 vite-plugin-compression[107] 可以 gzip 或 brotli 的方式來壓縮資源,這一步需要服務器端的配合,vite 只能幫你打包出 .gz 文件。此插件使用簡單,你甚至無需配置參數,引入即可。

13.2 頁面載入進度條

頁面路由切換時,附帶一個加載進度條會顯得非常友好,不至於白屏時間過長,讓用戶以爲頁面假死。

這時候我們可以用到 nprogress[108],在路由切換時開啓和關閉:

import NProgress from 'nprogress';

router.beforeEach(async (to, from, next) ={
  NProgress.start();
});

router.afterEach((to) ={
  NProgress.done();
});
複製代碼

13.3 Title

在不同的路由下顯示不同的標題是常規的操作,我們可以通過路由鉤子獲取 meta 中的 title 屬性改變標籤頁上的 title。

你可以使用 vueuse 提供的 useTitle[109],或者 window.document.title 自行封裝。

你也可以通過環境變量將你的主標題拼接在路由標題的後面:

const { VITE_APP_TITLE } = import.meta.env;
複製代碼

13.4 解決移動端使用 vh 的問題

有興趣的同學可以嘗試一下 chrome 移動端瀏覽器上的 100vh,是真正的視口高度的 100% 嘛。

爲了解決這一問題,我們可以通過 postCss 插件解決。

安裝 postcss-viewport-height-correction[110] 插件:

npm install -D postcss-viewport-height-correction
複製代碼

在 postcss.config.js 中增加 plugin:

module.exports = {
  plugins: {
    'postcss-viewport-height-correction'{},
  },
}
複製代碼

添加這一段 js 代碼在全局,你可以直接添加在 index.html 上即可:

const customViewportCorrectionVariable = 'vh';
function setViewportProperty(doc) {
  let prevClientHeight;
  const customVar = `--${customViewportCorrectionVariable || 'vh'}`;
  function handleResize() {
    const { clientHeight } = doc;
    if (clientHeight === prevClientHeight) return;
    requestAnimationFrame(function updateViewportHeight() {
      doc.style.setProperty(customVar, `${clientHeight * 0.01}px`);
      prevClientHeight = clientHeight;
    });
  }
  handleResize();
  return handleResize;
}
window.addEventListener('resize', setViewportProperty(document.documentElement));
複製代碼

13.5 可以常駐的 JavaScript 庫

  1. 代碼風格與流程規範

14.1 ESLint

不管是多人合作還是個人項目,代碼規範都是很重要的。這樣做不僅可以很大程度地避免基本語法錯誤,也保證了代碼的可讀性。

這裏推薦使用 airbnb 規範。

配置參考 [113]

14.2 StyleLint

儘管前文提到過 tailwind,可以讓你幾乎不寫 css,但是涉及到團隊協作,這一點也要嚴謹。

StyleLint 是一個強大的、現代化的 CSS 檢測工具, 與 ESLint 類似, 是通過定義一系列的編碼風格規則幫助我們避免在樣式表中出現錯誤,配合編輯器的自動修復,可以很好的統一團隊項目 css 風格。

配置參考 [114]

14.3 代碼提交規範

在多人協作的背景下,git 倉庫和 workflow 的作用很重要。而對於 commit 提交的信息說明存在一定規範,現使用 commitlint + husky 規範 git commit -m "" 中的描述信息。我們都知道,在使用 git commit 時,git 會提示我們填入此次提交的信息。可不要小看了這些 commit,團隊中規範了 commit 可以更清晰的查看每一次代碼提交記錄,還可以根據自定義的規則,自動生成 changeLog 文件。

提交格式(注意冒號後面有空格):

<type>[optional scope]: <description>
複製代碼

Type 類型

關於 commitlint + husky 的配置文章有很多,大同小異,請根據自己的實際情況配置。

  1. 編寫使用文檔

做到這一步,你的整個腳手架開發已經接近於尾聲,但是你做了這麼多,你的同事並不知道如何使用,甚至你過一段時間也會忘記,所以你必須養成良好的編寫文檔習慣。

15.1 使用 vitepress 搭建文檔

這裏我推薦使用 vuepress 或者 vitepress,說實話你只寫文檔 vitepress 會讓你更舒服,因爲它很快。

vitepress[115] 很適合構建博客網站、技術文檔,就是因爲它可以直接用 markdown 進行書寫,所有寫過博客的人,都應該對它不陌生。一個 .md 文件,即可生成一張頁面,十分方便。

創建一個 vitepress 文檔實在是太過於簡單,你可以參考官方文檔,或者參考我的文檔 [116]。

15.2 文檔部署

如果你的團隊可以幫助你搭建 CI/CD 自動部署是再好不過了,如果沒有這個條件,你也可以通過 github 提供的 actions 功能,完成自動部署。

代碼參考 [117]

  1. 插件

如果你想更痛快的用上述功能,建議你安裝下面的插件。

16.1 VSCode 插件

16.2 Chrome 插件

源碼

上述內容,均可在我的開源項目 X-BUILD[125] 中找到相關源碼,如果可以幫到你,請給一顆 star 或點贊鼓勵我貢獻出更多的開源項目或文章。

參考

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/cfw1KFdrwV8GzDN1pnu_kQ