從 0 開始手把手帶你搭建一套規範的 Vue3.x 項目工程環境

Vue3 跟 Vite 正式版發佈有很長一段時間了,生態圈也漸漸豐富起來,做者已在多個項目中使用,總結一下:就是快!也不用擔憂穩定性問題,開發體驗真不是通常好!還沒嘗試的同窗能夠從本文開始學習,從 0 開始手把手帶你搭建一套規範的 Vite + Vue3 + TypeScript 前端工程化項目環境。javascript

本文篇幅較長,從如下幾個方面展開:css

  • 架構搭建
  • 代碼規範
  • 提交規範
  • 單元測試
  • 自動部署

本項目完整的代碼託管在 GitHub 倉庫,歡迎點亮小星星 🌟🌟html

技術棧

架構搭建

請確保你的電腦上成功安裝 Node.js,本項目使用 Vite 構建工具,須要 Node.js 版本 >= 12.0.0前端

查看 Node.js 版本:vue

node -v
複製代碼

建議將 Node.js 升級到最新的穩定版本:java

# 使用 nvm 安裝最新穩定版 Node.js
nvm install stable
複製代碼

使用 Vite 快速初始化項目雛形

  • 使用 NPM:node

    npm init @vitejs/app
    複製代碼
  • 使用 Yarn:webpack

    yarn create @vitejs/app
    複製代碼

而後按照終端提示完成如下操做:ios

  1. 輸入項目名稱git

    例如:本項目名稱 vite-vue3-starter

    image

  2. 選擇模板

    本項目須要使用 Vue3 + TypeScript,因此咱們選擇 vue-ts,會自動安裝 Vue3 和 TypeScript。

    image

    image

    你還能夠經過附加的命令行選項直接指定項目名和模板,本項目要構建 Vite + Vue3 + TypeScript 項目,則運行:

    # npm 6.x
    npm init @vitejs/app vite-vue3-starter --template vue-ts
    
    # npm 7+(須要額外的雙橫線)
    npm init @vitejs/app vite-vue3-starter -- --template vue-ts
    
    # yarn
    yarn create @vitejs/app vite-vue3-starter --template vue-ts
    複製代碼
  3. 安裝依賴

    npm install
    複製代碼
  4. 啓動項目

    npm run dev
    複製代碼

    image

    如上圖,表示 Vite + Vue3 + TypeScript 簡單的項目骨架搭建完畢,下面咱們來爲這個項目集成 Vue Router、Vuex、Element Plus、Axios、Stylus/Sass/Less。

修改 Vite 配置文件

Vite 配置文件 vite.config.ts 位於根目錄下,項目啓動時會自動讀取。

本項目先作一些簡單配置,例如:設置 @ 指向 src 目錄、 服務啓動端口、打包路徑、代理等。

關於 Vite 更多配置項及用法,請查看 Vite 官網 vitejs.dev/config/

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 若是編輯器提示 path 模塊找不到,則能夠安裝一下 @types/node -> npm i @types/node -D
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src') // 設置 `@` 指向 `src` 目錄
    }
  },
  base: './', // 設置打包路徑
  server: {
    port: 4000, // 設置服務啓動端口號
    open: true, // 設置服務啓動時是否自動打開瀏覽器
    cors: true // 容許跨域

    // 設置代理,根據咱們項目實際狀況配置
    // proxy: {
    // '/api': {
    // target: 'http://xxx.xxx.xxx.xxx:8000',
    // changeOrigin: true,
    // secure: false,
    // rewrite: (path) => path.replace('/api/', '/')
    // }
    // }
  }
})
複製代碼

規範目錄結構

├── publish/
└── src/
    ├── assets/                    // 靜態資源目錄
    ├── common/                    // 通用類庫目錄
    ├── components/                // 公共組件目錄
    ├── router/                    // 路由配置目錄
    ├── store/                     // 狀態管理目錄
    ├── style/                     // 通用 CSS 目錄
    ├── utils/                     // 工具函數目錄
    ├── views/                     // 頁面組件目錄
    ├── App.vue
    ├── main.ts
    ├── shims-vue.d.ts
├── tests/                         // 單元測試目錄
├── index.html
├── tsconfig.json                  // TypeScript 配置文件
├── vite.config.ts                 // Vite 配置文件
└── package.json
複製代碼

集成路由工具 Vue Router

  1. 安裝支持 Vue3 的路由工具 vue-router@4

    npm i vue-router@4
    複製代碼
  2. 建立 src/router/index.ts 文件

    src 下建立 router 目錄,而後在 router 目錄裏新建 index.ts 文件:

    └── src/
         ├── router/
             ├── index.ts  // 路由配置文件
    複製代碼
    import {
      createRouter,
      createWebHashHistory,
      RouteRecordRaw
    } from 'vue-router'
    import Home from '@/views/home.vue'
    import Vuex from '@/views/vuex.vue'
    
    const routes: Array<RouteRecordRaw> = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/vuex',
        name: 'Vuex',
        component: Vuex
      },
      {
        path: '/axios',
        name: 'Axios',
        component: () => import('@/views/axios.vue') // 懶加載組件
      }
    ]
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes
    })
    
    export default router
    複製代碼

    根據本項目路由配置的實際狀況,你須要在 src 下建立 views 目錄,用來存儲頁面組件。

    咱們在 views 目錄下建立 home.vuevuex.vueaxios.vue

  3. main.ts 文件中掛載路由配置

    import { createApp } from 'vue'
    import App from './App.vue'
    
    import router from './router/index'
    
    createApp(App).use(router).mount('#app')
    複製代碼

集成狀態管理工具 Vuex

  1. 安裝支持 Vue3 的狀態管理工具 vuex@next

    npm i vuex@next
    複製代碼
  2. 建立 src/store/index.ts 文件

    src 下建立 store 目錄,而後在 store 目錄裏新建 index.ts 文件:

    └── src/
        ├── store/
            ├── index.ts  // store 配置文件
    複製代碼
    import { createStore } from 'vuex'
    
    const defaultState = {
      count: 0
    }
    
    // Create a new store instance.
    export default createStore({
      state() {
        return defaultState
      },
      mutations: {
        increment(state: typeof defaultState) {
          state.count++
        }
      },
      actions: {
        increment(context) {
          context.commit('increment')
        }
      },
      getters: {
        double(state: typeof defaultState) {
          return 2 * state.count
        }
      }
    })
    複製代碼
  3. main.ts 文件中掛載 Vuex 配置

    import { createApp } from 'vue'
    import App from './App.vue'
    
    import store from './store/index'
    
    createApp(App).use(store).mount('#app')
    複製代碼

集成 UI 框架 Element Plus

  1. 安裝支持 Vue3 的 UI 框架 Element Plus

    npm i element-plus
    複製代碼
  2. main.ts 文件中掛載 Element Plus

    import { createApp } from 'vue'
    import App from './App.vue'
    
    import ElementPlus from 'element-plus'
    import 'element-plus/lib/theme-chalk/index.css'
    
    createApp(App).use(ElementPlus).mount('#app')
    複製代碼

集成 HTTP 工具 Axios

  1. 安裝 Axios(Axios 跟 Vue 版本沒有直接關係,安裝最新便可)

    npm i axios
    複製代碼
  2. 配置 Axios

    爲了使項目的目錄結構合理且規範,咱們在 src 下建立 utils 目錄來存儲咱們經常使用的工具函數。

    Axios 做爲 HTTP 工具,咱們在 utils 目錄下建立 axios.ts 做爲 Axios 配置文件:

    └── src/
        ├── utils/
            ├── axios.ts  // Axios 配置文件
    複製代碼
    import Axios from 'axios'
    import { ElMessage } from 'element-plus'
    
    const baseURL = 'https://api.github.com'
    
    const axios = Axios.create({
      baseURL,
      timeout: 20000 // 請求超時 20s
    })
    
    // 前置攔截器(發起請求以前的攔截)
    axios.interceptors.request.use(
      (response) => {
        /** * 根據你的項目實際狀況來對 config 作處理 * 這裏對 config 不作任何處理,直接返回 */
        return response
      },
      (error) => {
        return Promise.reject(error)
      }
    )
    
    // 後置攔截器(獲取到響應時的攔截)
    axios.interceptors.response.use(
      (response) => {
        /** * 根據你的項目實際狀況來對 response 和 error 作處理 * 這裏對 response 和 error 不作任何處理,直接返回 */
        return response
      },
      (error) => {
        if (error.response && error.response.data) {
          const code = error.response.status
          const msg = error.response.data.message
          ElMessage.error(`Code: ${code}, Message: ${msg}`)
          console.error(`[Axios Error]`, error.response)
        } else {
          ElMessage.error(`${error}`)
        }
        return Promise.reject(error)
      }
    )
    
    export default axios
    複製代碼
  3. 使用 Axios
    在須要使用 Axios 文件裏,引入 Axios 配置文件,參考以下:

    <template></template>
    <script lang="ts"> import { defineComponent } from 'vue' import axios from '../utils/axios' export default defineComponent({ setup() { axios .get('/users/XPoet') .then((res) => { console.log('res: ', res) }) .catch((err) => { console.log('err: ', err) }) } }) </script>
    複製代碼

集成 CSS 預編譯器 Stylus/Sass/Less

本項目使用 CSS 預編譯器 Stylus,直接安裝爲開發依賴便可。Vite 內部已幫咱們集成了相關的 loader,不須要額外配置。同理,你也可使用 Sass 或 Less 等。

  1. 安裝

    npm i stylus -D
    # or
    npm i sass -D
    npm i less -D
    複製代碼
  2. 使用

    <style lang="stylus">
      ...
    </style>
    複製代碼

至此,一個基於 TypeScript + Vite + Vue3 + Vue Router + Vuex + Element Plus + Axios + Stylus/Sass/Less 的前端項目開發環境搭建完畢,項目 Demo 託管在 GitHub 倉庫,須要的同窗能夠去下載下來,參考學習。

下面咱們來打磨這個項目,增長代碼規範約束、提交規範約束、單元測試、自動部署等,讓其更完善、更健壯。

代碼規範

隨着前端應用逐漸變得大型化和複雜化,在同一個項目中有多我的員參與時,每一個人的前端能力程度不等,他們每每會用不一樣的編碼風格和習慣在項目中寫代碼,長此下去,勢必會讓項目的健壯性愈來愈差。解決這些問題,理論上講,口頭約定和代碼審查均可以,可是這種方式沒法實時反饋,並且溝通成本太高,不夠靈活,更關鍵的是沒法把控。不以規矩,不能成方圓,咱們不得不在項目使用一些工具來約束代碼規範。

本文講解如何使用 EditorConfig + Prettier + ESLint 組合來實現代碼規範化。

這樣作帶來好處:

  • 解決團隊之間代碼不規範致使的可讀性差和可維護性差的問題。
  • 解決團隊成員不一樣編輯器致使的編碼規範不統一問題。
  • 提早發現代碼風格問題,給出對應規範提示,及時修復。
  • 減小代碼審查過程當中反反覆覆的修改過程,節約時間。
  • 自動格式化,統一編碼風格,今後和髒亂差的代碼說再見。

集成 EditorConfig 配置

EditorConfig 有助於爲不一樣 IDE 編輯器上處理同一項目的多個開發人員維護一致的編碼風格。

官網:editorconfig.org

在項目根目錄下增長 .editorconfig 文件:

# Editor configuration, see http://editorconfig.org

# 表示是最頂層的 EditorConfig 配置文件
root = true

[*] # 表示全部文件適用
charset = utf-8 # 設置文件字符集爲 utf-8
indent_style = space # 縮進風格(tab | space)
indent_size = 2 # 縮進大小
end_of_line = lf # 控制換行類型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始終在文件末尾插入一個新行

[*.md] # 表示僅 md 文件適用如下規則
max_line_length = off
trim_trailing_whitespace = false
複製代碼

注意:

  • VSCode 使用 EditorConfig 須要去插件市場下載插件 EditorConfig for VS Code

    image

  • JetBrains 系列(WebStorm、IntelliJ IDEA 等)則不用額外安裝插件,可直接使用 EditorConfig 配置。

集成 Prettier 配置

Prettier 是一款強大的代碼格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等語言,基本上前端能用到的文件格式它均可以搞定,是當下最流行的代碼格式化工具。

官網:prettier.io/

  1. 安裝 Prettier

    npm i prettier -D
    複製代碼
  2. 建立 Prettier 配置文件

    Prettier 支持多種格式的配置文件,好比 .json.yml.yaml.js等。

    在本項目根目錄下建立 .prettierrc 文件。

  3. 配置 .prettierrc

    在本項目中,咱們進行以下簡單配置,關於更多配置項信息,請前往官網查看 Prettier-Options

    {
      "useTabs": false,
      "tabWidth": 2,
      "printWidth": 100,
      "singleQuote": true,
      "trailingComma": "none",
      "bracketSpacing": true,
      "semi": false
    }
    複製代碼
  4. Prettier 安裝且配置好以後,就能使用命令來格式化代碼

    # 格式化全部文件(. 表示全部文件)
    npx prettier --write .
    複製代碼

注意:

  • VSCode 編輯器使用 Prettier 配置須要下載插件 Prettier - Code formatter

    image

  • JetBrains 系列編輯器(WebStorm、IntelliJ IDEA 等)則不用額外安裝插件,可直接使用 Prettier 配置。

Prettier 配置好之後,在使用 VSCode 或 WebStorm 等編輯器的格式化功能時,編輯器就會按照 Prettier 配置文件的規則來進行格式化,避免了由於你們編輯器配置不同而致使格式化後的代碼風格不統一的問題。

集成 ESLint 配置

ESLint 是一款用於查找並報告代碼中問題的工具,而且支持部分問題自動修復。其核心是經過對代碼解析獲得的 AST(Abstract Syntax Tree 抽象語法樹)進行模式匹配,來分析代碼達到檢查代碼質量和風格問題的能力。

正如前面咱們提到的因團隊成員之間編程能力和編碼習慣不一樣所形成的代碼質量問題,咱們使用 ESLint 來解決,一邊寫代碼一邊查找問題,若是發現錯誤,就給出規則提示,而且自動修復,長期下去,能夠促使團隊成員往同一種編碼風格靠攏。

  1. 安裝 ESLint

    能夠全局或者本地安裝,做者推薦本地安裝(只在當前項目中安裝)。

    npm i eslint -D
    複製代碼
  2. 配置 ESLint

    ESLint 安裝成功後,執行 npx eslint --init,而後按照終端操做提示完成一系列設置來建立配置文件。

    • How would you like to use ESLint? (你想如何使用 ESLint?)

      image

      咱們這裏選擇 To check syntax, find problems, and enforce code style(檢查語法、發現問題並強制執行代碼風格)

    • What type of modules does your project use?(你的項目使用哪一種類型的模塊?)

      image

      咱們這裏選擇 JavaScript modules (import/export)

    • Which framework does your project use? (你的項目使用哪一種框架?)

      image

      咱們這裏選擇 Vue.js

    • Does your project use TypeScript?(你的項目是否使用 TypeScript?)

      image

      咱們這裏選擇 Yes

    • Where does your code run?(你的代碼在哪裏運行?)

      image

      咱們這裏選擇 Browser 和 Node(按空格鍵進行選擇,選完按回車鍵肯定)

    • How would you like to define a style for your project?(你想怎樣爲你的項目定義風格?)

      image

      咱們這裏選擇 Use a popular style guide(使用一種流行的風格指南)

    • Which style guide do you want to follow?(你想遵循哪種風格指南?)

      image

      咱們這裏選擇 Airbnb: github.com/airbnb/java…

      ESLint 爲咱們列出了三種社區流行的 JavaScript 風格指南,分別是 Airbnb、Standard、Google。

      這三份風格指南都是由衆多大佬根據多年開發經驗編寫,足夠優秀,全球不少大小公司都在使用。咱們選用 GitHub 上 star 最多的 Airbnb,免去繁瑣的配置 ESLint 規則時間,而後讓團隊成員去學習 Airbnb JavaScript 風格指南便可。

      此時,咱們在 ESLint 配置了 Airbnb JavaScript 規則,在編碼時,全部不符合 Airbnb 風格的代碼,編輯器都會給出提示,而且能夠自動修復。

      這裏做者不建議你們去自由配置 ESLint 規則,相信我,這三份 JavaScript 代碼風格指南值得咱們反覆學習,掌握後,編程能力能上一大臺階。

      image

    • What format do you want your config file to be in?(你但願你的配置文件是什麼格式?)

      image

      咱們這裏選擇 JavaScript

    • Would you like to install them now with npm?(你想如今就用 NPM 安裝它們嗎?)

      image

      根據上面的選擇,ESLint 會自動去查找缺失的依賴,咱們這裏選擇 Yes,使用 NPM 下載安裝這些依賴包。

      注意:若是自動安裝依賴失敗,那麼須要手動安裝

      npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue -D
      複製代碼
  3. ESLint 配置文件 .eslintrc.js

    上一步操做完成後,會在項目根目錄下自動生成 .eslintrc.js 配置文件:

    module.exports = {
      env: {
        browser: true,
        es2021: true,
        node: true
      },
      extends: ['plugin:vue/essential', 'airbnb-base'],
      parserOptions: {
        ecmaVersion: 12,
        parser: '@typescript-eslint/parser',
        sourceType: 'module'
      },
      plugins: ['vue', '@typescript-eslint'],
      rules: {}
    }
    複製代碼

    根據項目實際狀況,若是咱們有額外的 ESLint 規則,也在此文件中追加。

注意:

  • VSCode 使用 ESLint 配置文件須要去插件市場下載插件 ESLint

    image

  • JetBrains 系列(WebStorm、IntelliJ IDEA 等)則不用額外安裝插件。

配置好之後,咱們在 VSCode 或 WebStorm 等編輯器中開啓 ESLin,寫代碼時,ESLint 就會按照咱們配置的規則來進行實時代碼檢查,發現問題會給出對應錯誤提示和修復方案。

如圖:

  • VSCode image

  • WebStorm image

雖然,如今編輯器已經給出錯誤提示和修復方案,但須要咱們一個一個去點擊修復,仍是挺麻煩的。很簡單,咱們只需設置編輯器保存文件時自動執行 eslint --fix 命令進行代碼風格修復。

  • VSCode 在 settings.json 設置文件中,增長如下代碼:

    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
     }
    複製代碼
  • WebStorm 打開設置窗口,按以下操做,最後點擊 Apply -> OKimage

解決 Prettier 和 ESLint 的衝突

一般你們會在項目中根據實際狀況添加一些額外的 ESLint 和 Prettier 配置規則,不免會存在規則衝突狀況。

本項目中的 ESLint 配置中使用了 Airbnb JavaScript 風格指南校驗,其規則之一是代碼結束後面要加分號,而咱們在 Prettier 配置文件中加了代碼結束後面不加分號的配置項,這樣就有衝突了,會出現用 Prettier 格式化後的代碼,ESLint 檢測到格式有問題的,從而拋出錯誤提示。

解決二者衝突問題,須要用到 eslint-plugin-prettiereslint-config-prettier

  • eslint-plugin-prettier 將 Prettier 的規則設置到 ESLint 的規則中。

  • eslint-config-prettier 關閉 ESLint 中與 Prettier 中會發生衝突的規則。

最後造成優先級:Prettier 配置規則 > ESLint 配置規則

  • 安裝插件

    npm i eslint-plugin-prettier eslint-config-prettier -D
    複製代碼
  • .eslintrc.js 添加 prettier 插件

    module.exports = {
      ...
      extends: [
        'plugin:vue/essential',
        'airbnb-base',
        'plugin:prettier/recommended' // 添加 prettier 插件
      ],
      ...
    }
    複製代碼

這樣,咱們在執行 eslint --fix 命令時,ESLint 就會按照 Prettier 的配置規則來格式化代碼,輕鬆解決兩者衝突問題。

集成 husky 和 lint-staged

咱們在項目中已集成 ESLint 和 Prettier,在編碼時,這些工具能夠對咱們寫的代碼進行實時校驗,在必定程度上能有效規範咱們寫的代碼,但團隊可能會有些人以爲這些條條框框的限制很麻煩,選擇視「提示」而不見,依舊按本身的一套風格來寫代碼,或者乾脆禁用掉這些工具,開發完成就直接把代碼提交到了倉庫,日積月累,ESLint 也就形同虛設。

因此,咱們還須要作一些限制,讓沒經過 ESLint 檢測和修復的代碼禁止提交,從而保證倉庫代碼都是符合規範的。

爲了解決這個問題,咱們須要用到 Git Hook,在本地執行 git commit 的時候,就對所提交的代碼進行 ESLint 檢測和修復(即執行 eslint --fix),若是這些代碼沒經過 ESLint 規則校驗,則禁止提交。

實現這一功能,咱們藉助 husky + lint-staged

husky —— Git Hook 工具,能夠設置在 git 各個階段(pre-commitcommit-msgpre-push 等)觸發咱們的命令。
lint-staged —— 在 git 暫存的文件上運行 linters。

配置 husky

  • 自動配置(推薦)

    使用 husky-init 命令快速在項目初始化一個 husky 配置。

    npx husky-init && npm install
    複製代碼

    這行命令作了四件事:

    1. 安裝 husky 到開發依賴 image

    2. 在項目根目錄下建立 .husky 目錄 image

    3. .husky 目錄建立 pre-commit hook,並初始化 pre-commit 命令爲 npm test image

    4. 修改 package.jsonscripts,增長 "prepare": "husky install" image

  • 手動配置(不推薦,懶是程序員第一輩子產力)

    1. 安裝 husky

      npm i husky -D
      複製代碼
    2. 建立 Git hooks

      npx husky install
      複製代碼

      該命令作了兩件事:

      • 在項目根目錄下建立 .husky 目錄 image

      • .husky 目錄建立 pre-commit hook,並初始化 pre-commit 命令爲 npm test image

    3. 手動修改 package.jsonscripts,增長 "prepare": "husky install" image

特別注意:本項目使用 husky 6.x 版本,6.x 版本配置方式跟以前的版本有較大差別。目前網上大部分有關 husky 的教程都是 6 之前的版本 ,跟本文教程不太同樣,當發現配置方法不一致時,一切以 husky 官網爲準。

到這裏,husky 配置完畢,如今咱們來使用它:

husky 包含不少 hook(鉤子),經常使用有:pre-commitcommit-msgpre-push。這裏,咱們使用 pre-commit 來觸發 ESLint 命令。

修改 .husky/pre-commit hook 文件的觸發命令:

eslint --fix ./src --ext .vue,.js,.ts
複製代碼

image

上面這個 pre-commit hook 文件的做用是:當咱們執行 git commit -m "xxx" 時,會先對 src 目錄下全部的 .vue.js.ts 文件執行 eslint --fix 命令,若是 ESLint 經過,成功 commit,不然終止 commit

可是又存在一個問題:有時候咱們明明只改動了一兩個文件,卻要對全部的文件執行 eslint --fix。假如這是一個歷史項目,咱們在中途配置了 ESLint 規則,那麼在提交代碼時,也會對其餘未修改的「歷史」文件都進行檢查,可能會形成大量文件出現 ESLint 錯誤,顯然不是咱們想要的結果。

咱們要作到只用 ESLint 修復本身這次寫的代碼,而不去影響其餘的代碼。因此咱們還需藉助一個神奇的工具 lint-staged

配置 lint-staged

lint-staged 這個工具通常結合 husky 來使用,它可讓 husky 的 hook 觸發的命令只做用於 git add那些文件(即 git 暫存區的文件),而不會影響到其餘文件。

接下來,咱們使用 lint-staged 繼續優化項目。

  1. 安裝 lint-staged

    npm i lint-staged -D
    複製代碼
  2. package.json裏增長 lint-staged 配置項

    image

    "lint-staged": {
      "*.{vue,js,ts}": "eslint --fix"
    },
    複製代碼

    這行命令表示:只對 git 暫存區的 .vue.js.ts 文件執行 eslint --fix

  3. 修改 .husky/pre-commit hook 的觸發命令爲:npx lint-staged

    image

至此,husky 和 lint-staged 組合配置完成。

如今咱們提交代碼時就會變成這樣:

假如咱們修改了 scr 目錄下的 test-1.jstest-2.tstest-3.md 文件,而後 git add ./src/,最後 git commit -m "test...",這時候就會只對 test-1.jstest-2.ts 這兩個文件執行 eslint --fix。若是 ESLint 經過,成功提交,不然終止提交。從而保證了咱們提交到 Git 倉庫的代碼都是規範的。

image

  • 提交前 test-1.jstest-2.ts image

  • 提交後 test-1.jstest-2.ts 自動修復代碼格式 image

不管寫代碼仍是作其餘事情,都應該用長遠的眼光來看,剛開始使用 ESint 的時候可能會有不少問題,改起來也很費時費力,只要堅持下去,代碼質量和開發效率都會獲得提高,前期的付出都是值得的。

這些工具並非必須的,沒有它們你一樣能夠能夠完成功能開發,可是利用好這些工具,你能夠寫出更高質量的代碼。特別是一些剛剛接觸的人,可能會以爲麻煩而放棄使用這些工具,失去了一次提高編程能力的好機會。

本項目完整的代碼託管在 GitHub 倉庫,有須要的同窗能夠去下載下來,參考學習。
點亮小星星 🌟 支持做者~

提交規範

前面咱們已經統一代碼規範,而且在提交代碼時進行強約束來保證倉庫代碼質量。多人協做的項目中,在提交代碼這個環節,也存在一種狀況:不能保證每一個人對提交信息的準確描述,所以會出現提交信息紊亂、風格不一致的狀況。

若是 git commit 的描述信息精準,在後期維護和 Bug 處理時會變得有據可查,項目開發週期內還能夠根據規範的提交信息快速生成開發日誌,從而方便咱們追蹤項目和把控進度。

這裏,咱們使用社區最流行、最知名、最受承認的 Angular 團隊提交規範。

先看看 Angular 項目的提交記錄

image

如上圖,能夠看出這些提交信息都是有固定格式的,下面咱們來學習 Angular 規範的 commit message 格式。

commit message 格式規範

commit message 由 Header、Body、Footer 組成。

<Header>

<Body>

<Footer>
複製代碼

Header

Header 部分包括三個字段 type(必需)、scope(可選)和 subject(必需)。

<type>(<scope>): <subject>
複製代碼
type

type 用於說明 commit 的提交類型(必須是如下幾種之一)。

描述
feat 新增一個功能
fix 修復一個 Bug
docs 文檔變動
style 代碼格式(不影響功能,例如空格、分號等格式修正)
refactor 代碼重構
perf 改善性能
test 測試
build 變動項目構建或外部依賴(例如 scopes: webpack、gulp、npm 等)
ci 更改持續集成軟件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
chore 變動構建流程或輔助工具
revert 代碼回退
scope

scope 用於指定本次 commit 影響的範圍。scope 依據項目而定,例如在業務項目中能夠依據菜單或者功能模塊劃分,若是是組件庫開發,則能夠依據組件劃分。(scope 可省略)

subject

subject 是本次 commit 的簡潔描述,長度約定在 50 個字符之內,一般遵循如下幾個規範:

  • 用動詞開頭,第一人稱如今時表述,例如:change 代替 changed 或 changes
  • 第一個字母小寫
  • 結尾不加句號(.)

Body

body 是對本次 commit 的詳細描述,能夠分紅多行。(body 可省略)

跟 subject 相似,用動詞開頭,body 應該說明修改的緣由和更改先後的行爲對比。

Footer

若是本次提交的代碼是突破性的變動或關閉缺陷,則 Footer 必需,不然能夠省略。

  • 突破性的變動

    當前代碼與上一個版本有突破性改變,則 Footer 以 BREAKING CHANGE 開頭,後面是對變更的描述、以及變更的理由。

  • 關閉缺陷

    若是當前提交是針對特定的 issue,那麼能夠在 Footer 部分填寫須要關閉的單個 issue 或一系列 issues。

參考例子

  • feat

    feat(browser): onUrlChange event (popstate/hashchange/polling)
    
    Added new event to browser:
    - forward popstate event if available
    - forward hashchange event if popstate not available
    - do polling when neither popstate nor hashchange available
    
    Breaks $browser.onHashChange, which was removed (use onUrlChange instead)
    複製代碼
  • fix

    fix(compile): couple of unit tests for IE9
    
    Older IEs serialize html uppercased, but IE9 does not...
    Would be better to expect case insensitive, unfortunately jasmine does
    not allow to user regexps for throw expectations.
    
    Closes #392
    Breaks foo.bar api, foo.baz should be used instead
    複製代碼
  • style

    style(location): add couple of missing semi colons
    複製代碼
  • chore

    chore(release): v3.4.2
    複製代碼

規範 commit message 的好處

  • 首行就是簡潔實用的關鍵信息,方便在 git history 中快速瀏覽。
  • 具備更加詳細的 body 和 footer,能夠清晰的看出某次提交的目的和影響。
  • 能夠經過 type 過濾出想要查找的信息,也能夠經過關鍵字快速查找相關提交。
  • 能夠直接從 commit 生成 change log。

集成 Commitizen 實現規範提交

上面介紹了 Angular 規範提交的格式,初次接觸的同窗咋一看可能會以爲複雜,其實否則,若是讓你們在 git commit 的時候嚴格按照上面的格式來寫,確定是有壓力的,首先得記住不一樣的類型究竟是用來定義什麼,subject 怎麼寫,body 怎麼寫,footer 要不要寫等等問題,懶纔是程序員第一輩子產力,爲此咱們使用 Commitizen 工具來幫助咱們自動生成 commit message 格式,從而實現規範提交。

Commitizen 是一個幫助撰寫規範 commit message 的工具。它有一個命令行工具 cz-cli。

安裝 Commitizen

npm install commitizen -D
複製代碼

初始化項目

成功安裝 Commitizen 後,咱們用 cz-conventional-changelog 適配器來初始化項目:

npx commitizen init cz-conventional-changelog --save-dev --save-exact
複製代碼

這行命令作了兩件事:

  • 安裝 cz-conventional-changelog 到開發依賴(devDependencies)
  • package.json 中增長了 config.commitizen
    "config": {
      "commitizen": {
        "path": "./node_modules/cz-conventional-changelog"
      }
    }
    複製代碼
    image

使用 Commitizen

之前咱們提交代碼都是 git commit -m "xxx",如今改成 git cz,而後按照終端操做提示,逐步填入信息,就能自動生成規範的 commit message。

image image

最後,在 Git 提交歷史中就能看到剛剛規範的提交記錄了: image

自定義配置提交說明

從上面的截圖能夠看到,git cz 終端操做提示都是英文的,若是想改爲中文的或者自定義這些配置選項,咱們使用 cz-customizable 適配器。

cz-customizable 初始化項目

運行以下命令使用 cz-customizable 初始化項目,注意以前已經初始化過一次,此次再初始化,須要加 --force 覆蓋。

npx commitizen init cz-customizable --save-dev --save-exact --force
複製代碼

這行命令作了兩件事:

  • 安裝 cz-customizable 到開發依賴(devDependencies)

    "devDependencies": {
      ...
      "cz-customizable": "^6.3.0",
      ...
    },
    複製代碼
  • 修改 package.json 中的 config.commitizen 字段爲:

    "config": {
      "commitizen": {
        "path": "./node_modules/cz-customizable"
      }
    }
    複製代碼
使用 cz-customizable

在項目根目錄下建立 .cz-config.js 文件,而後按照官方提供的示例來配置。

在本項目中咱們修改爲中文:

module.exports = {
  // type 類型(定義以後,可經過上下鍵選擇)
  types: [
    { value: 'feat', name: 'feat: 新增功能' },
    { value: 'fix', name: 'fix: 修復 bug' },
    { value: 'docs', name: 'docs: 文檔變動' },
    { value: 'style', name: 'style: 代碼格式(不影響功能,例如空格、分號等格式修正)' },
    { value: 'refactor', name: 'refactor: 代碼重構(不包括 bug 修復、功能新增)' },
    { value: 'perf', name: 'perf: 性能優化' },
    { value: 'test', name: 'test: 添加、修改測試用例' },
    { value: 'build', name: 'build: 構建流程、外部依賴變動(如升級 npm 包、修改 webpack 配置等)' },
    { value: 'ci', name: 'ci: 修改 CI 配置、腳本' },
    { value: 'chore', name: 'chore: 對構建過程或輔助工具和庫的更改(不影響源文件、測試用例)' },
    { value: 'revert', name: 'revert: 回滾 commit' }
  ],

  // scope 類型(定義以後,可經過上下鍵選擇)
  scopes: [
    ['components', '組件相關'],
    ['hooks', 'hook 相關'],
    ['utils', 'utils 相關'],
    ['element-ui', '對 element-ui 的調整'],
    ['styles', '樣式相關'],
    ['deps', '項目依賴'],
    ['auth', '對 auth 修改'],
    ['other', '其餘修改'],
    // 若是選擇 custom,後面會讓你再輸入一個自定義的 scope。也能夠不設置此項,把後面的 allowCustomScopes 設置爲 true
    ['custom', '以上都不是?我要自定義']
  ].map(([value, description]) => {
    return {
      value,
      name: `${value.padEnd(30)} (${description})`
    }
  }),

  // 是否容許自定義填寫 scope,在 scope 選擇的時候,會有 empty 和 custom 能夠選擇。
  // allowCustomScopes: true,

  // allowTicketNumber: false,
  // isTicketNumberRequired: false,
  // ticketNumberPrefix: 'TICKET-',
  // ticketNumberRegExp: '\\d{1,5}',


  // 針對每個 type 去定義對應的 scopes,例如 fix
  /* scopeOverrides: { fix: [ { name: 'merge' }, { name: 'style' }, { name: 'e2eTest' }, { name: 'unitTest' } ] }, */

  // 交互提示信息
  messages: {
    type: '確保本次提交遵循 Angular 規範!\n選擇你要提交的類型:',
    scope: '\n選擇一個 scope(可選):',
    // 選擇 scope: custom 時會出下面的提示
    customScope: '請輸入自定義的 scope:',
    subject: '填寫簡短精煉的變動描述:\n',
    body:
      '填寫更加詳細的變動描述(可選)。使用 "|" 換行:\n',
    breaking: '列舉非兼容性重大的變動(可選):\n',
    footer: '列舉出全部變動的 ISSUES CLOSED(可選)。 例如: #31, #34:\n',
    confirmCommit: '確認提交?'
  },

  // 設置只有 type 選擇了 feat 或 fix,才詢問 breaking message
  allowBreakingChanges: ['feat', 'fix'],

  // 跳過要詢問的步驟
  // skipQuestions: ['body', 'footer'],

  // subject 限制長度
  subjectLimit: 100
  breaklineChar: '|', // 支持 body 和 footer
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true,
}
複製代碼

建議你們結合項目實際狀況來自定義配置提交規則,例如不少時候咱們不須要寫長描述,公司內部的代碼倉庫也不須要管理 issue,那麼能夠把詢問 body 和 footer 的步驟跳過(在 .cz-config.js 中修改爲 skipQuestions: ['body', 'footer'])。

image

集成 commitlint 驗證提交規範

在「代碼規範」章節,咱們已經講到過,儘管制定了規範,但在多人協做的項目中,總有些人依舊我行我素,所以提交代碼這個環節,咱們也增長一個限制:只讓符合 Angular 規範的 commit message 經過,咱們藉助 @commitlint/config-conventional 和 @commitlint/cli 來實現。

安裝 commitlint

安裝 @commitlint/config-conventional 和 @commitlint/cli

npm i @commitlint/config-conventional @commitlint/cli -D
複製代碼

配置 commitlint

  • 建立 commitlint.config.js 文件 在項目根目錄下建立 commitlint.config.js 文件,並填入如下內容:

    module.exports = { extends: ['@commitlint/config-conventional'] }
    複製代碼

    或直接使用快捷命令:

    echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
    複製代碼
  • 使用 husky 的 commit-msg hook 觸發驗證提交信息的命令
    咱們使用 husky 命令在 .husky 目錄下建立 commit-msg 文件,並在此執行 commit message 的驗證命令。

    npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
    複製代碼

    image

commitlint 驗證

  • 不符合規範的提交信息
    以下圖,提交信息 test commitlint 不符合規範,提交失敗。 image

  • 符合規範的提交信息
    以下圖,提交信息 test: commitlint test 符合規範,成功提交到倉庫。 image

由於已在項目中集成 commitizen,建議你們用 git cz 來代替 git commit 提交代碼,能夠保證提交信息規範。

本項目完整的代碼託管在 GitHub 倉庫,同窗能夠去下載下來,參考學習。
點亮小星星 🌟 支持做者~

單元測試

單元測試是項目開發中一個很是重要的環節,完整的測試能爲代碼和業務提供質量保證,減小 Bug 的出現。

本章節將帶領你們在 Vite + Vue3 + TypeScript 的項目中集成單元測試工具。

安裝核心依賴

咱們使用 Vue 官方提供的 vue-test-utils 和社區流行的測試工具 jest 來進行 Vue 組件的單元測試。

  • vue-test-utils The next iteration of Vue Test Utils. It targets Vue 3.
  • jest Delightful JavaScript Testing.
  • vue-jest Jest Vue transformer
  • ts-jest A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.

安裝這些工具爲開發依賴(devDependencies):

npm i @vue/test-utils@next jest vue-jest@next ts-jest -D
複製代碼

建立 jest 配置文件

在項目根目錄下新建 jest.config.js 文件:

module.exports = {
  moduleFileExtensions: ['vue', 'js', 'ts'],
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.vue$': 'vue-jest', // vue 文件用 vue-jest 轉換
    '^.+\\.ts$': 'ts-jest' // ts 文件用 ts-jest 轉換
  },
  // 匹配 __tests__ 目錄下的 .js/.ts 文件 或其餘目錄下的 xx.test.js/ts xx.spec.js/ts
  testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts)$'
}
複製代碼

建立單元測試文件

在上面的 jest.config.js 文件中,咱們配置只匹配 __tests__ 目錄下的任意 .ts 文件或其餘目錄下的 xx.test.ts/xx.spec.ts 文件進行單元測試。

這裏,咱們在項目根目錄下建立 tests 目錄來存儲單元測試文件

├── src/
└── tests/                           // 單元測試目錄
    ├── Test.spec.ts                 // Test 組件測試
複製代碼
  • Test.vue
<template>
  <div class="test-container page-container">
    <div class="page-title">Unit Test Page</div>
    <p>count is: {{ count }}</p>
    <button @click="increment">increment</button>
  </div>
</template>

<script lang="ts"> import { defineComponent, ref } from 'vue' export default defineComponent({ name: 'Vuex', setup() { const count = ref<number>(0) const increment = () => { count.value += 1 } return { count, increment } } }) </script>
複製代碼
  • Test.spec.ts

    import { mount } from '@vue/test-utils'
    import Test from '../src/views/Test.vue'
    
    test('Test.vue', async () => {
      const wrapper = mount(Test)
      expect(wrapper.html()).toContain('Unit Test Page')
      expect(wrapper.html()).toContain('count is: 0')
      await wrapper.find('button').trigger('click')
      expect(wrapper.html()).toContain('count is: 1')
    })
    複製代碼

集成 @types/jest

image

如上圖,咱們使用 VSCode / WebStrom / IDEA 等編輯器時,在單元測試文件中,IDE 會提示某些方法不存在(如 testdescribeitexpect等),安裝 @types/jest 便可解決。

npm i @types/jest -D
複製代碼

TypeScript 的編譯器也會提示 jest 的方法和類型找不到,咱們還需把 @types/jest 添加根目錄下的 ts.config.json(TypeScript 配置文件)中:

{
  "compilerOptions": {
    ...
    "types": ["vite/client", "jest"]
  },
}
複製代碼

添加 eslint-plugin-jest

image

由於咱們在項目中集成了 ESLint,如上圖很明顯是沒經過 ESLint 規則檢驗。所以,咱們還須要在 ESLint 中增長 eslint-plugin-jest 插件來解除對 jest 的校驗。

  • 安裝 eslint-plugin-jest

    npm i eslint-plugin-jest -D
    複製代碼
  • 添加 eslint-plugin-jest 到 ESLint 配置文件 .eslintrc.js

    module.exports = {
      ...
      extends: [
        ...
        'plugin:jest/recommended'
      ],
      ...
    }
    複製代碼

如今,咱們的單元測試代碼就不會有錯誤提示信息了 ؏؏☝ᖗ 乛 ◡ 乛 ᖘ☝؏؏

image

執行單元測試

在根目錄下 package.json 文件的 scripts 中,添加一條單元測試命令: "test": "jest"

image

執行命令 npm run test 便可進行單元測試,jest 會根據 jest.config.js 配置文件去查找 __tests__ 目錄下的 .ts 文件或其餘任意目錄下的 .spec.ts.test.ts 文件,而後執行單元測試方法。

你能夠在 jest.config.js 配置文件中,自由配置單元測試文件的目錄。

  • 單元測試所有經過時的終端顯示信息 image

  • 單元測試未所有經過時的終端顯示信息 image

當單元測試沒有所有經過時,咱們須要根據報錯信息去優化對應組件的代碼,進一步提升項目健壯性。可是寫單元測試是件比較痛苦的事,我我的以爲也不必所有組件都寫單元測試,根據項目實際狀況有針對性去寫就好了。

單元測試約束

前面,咱們使用 husky 在 Git 的 pre-commitcommit-msg 階段分別約束代碼風格規範和提交信息規範。這一步,咱們在 pre-push 階段進行單元測試,只有單元測試所有經過才讓代碼 push 到遠端倉庫,不然終止 push

使用 husky 命令在 .husky 目錄下自動建立 pre-push hook 文件,並在此執行單元測試命令 npm run test

npx husky add .husky/pre-push "npm run test $1"
複製代碼

image

如今,咱們在 git push 時就能先進行單元測試了,只有單元測試所有經過,才能成功 push

本項目完整的代碼託管在 GitHub 倉庫,同窗能夠去下載下來,參考學習。
點亮小星星 🌟 支持做者~

自動部署

到了這一步,咱們已經在項目中集成代碼規範約束提交信息規範約束單元測試約束,從而保證咱們遠端倉庫(如 GitHub、GitLab、Gitee 倉庫等)的代碼都是高質量的。

本項目是要搭建一套規範的前端工程化環境,爲此咱們使用 CI(Continuous Integration 持續集成)來完成項目最後的部署工做。

常見的 CI 工具備 GitHub Actions、GitLab CI、Travis CI、Circle CI 等。

這裏,咱們使用 GitHub Actions。

什麼是 GitHub Actions

GitHub Actions 是 GitHub 的持續集成服務,持續集成由不少操做組成,好比抓取代碼、運行測試、登陸遠程服務器、發佈到第三方服務等等,GitHub 把這些操做稱爲 actions。

配置 GitHub Actions

建立 GitHub 倉庫

由於 GitHub Actions 只對 GitHub 倉庫有效,因此咱們建立 GitHub 倉庫來託管項目代碼。

image

其中,咱們用:

  • master 分支存儲項目源代碼
  • gh-pages 分支存儲打包後的靜態文件

gh-pages 分支,是 GitHub Pages 服務的固定分支,能夠經過 HTTP 的方式訪問到這個分支的靜態文件資源。

建立 GitHub Token

建立一個有 repoworkflow 權限的 GitHub Token

image

注意:新生成的 Token 只會顯示一次,保存起來,後面要用到。若有遺失,從新生成便可。

image

在倉庫中添加 secret

將上面新建立的 Token 添加到 GitHub 倉庫的 Secrets 裏,並將這個新增的 secret 命名爲 VUE3_DEPLOY (名字無所謂,看你喜歡)。

步驟:倉庫 -> settings -> Secrets -> New repository secret

image

新建立的 secret VUE3_DEPLOY 在 Actions 配置文件中要用到,兩個地方需保持一致!

建立 Actions 配置文件

  1. 在項目根目錄下建立 .github 目錄。
  2. .github 目錄下建立 workflows 目錄。
  3. workflows 目錄下建立 deploy.yml 文件。

image

deploy.yml 文件的內容:

name: deploy

on:
  push:
    branches: [master] # master 分支有 push 時觸發

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup Node.js v14.x
        uses: actions/setup-node@v1
        with:
          node-version: '14.x'

      - name: Install
        run: npm install # 安裝依賴

      - name: Build
        run: npm run build # 打包

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3 # 使用部署到 GitHub pages 的 action
        with:
          publish_dir: ./dist # 部署打包後的 dist 目錄
          github_token: ${{ secrets.VUE3_DEPLOY }} # secret 名
          user_name: ${{ secrets.MY_USER_NAME }}
          user_email: ${{ secrets.MY_USER_EMAIL }}
          commit_message: Update Vite2.x + Vue3.x + TypeScript Starter # 部署時的 git 提交信息,自由填寫
複製代碼

自動部署觸發原理

當有新提交的代碼 push 到 GitHub 倉庫時,就會觸發 GitHub Actions,在 GitHub 服務器上執行 Action 配置文件裏面的命令,例如:安裝依賴項目打包等,而後將打包好的靜態文件部署到 GitHub Pages 上,最後,咱們就能經過域名訪問了。

🌏 經過域名 vite-vue3-starter.xpoet.cn/ 訪問本項目

使用自動部署,咱們只需專一於項目開發階段,任何重複且枯燥的行爲都交由程序去完成,懶纔是程序員第一輩子產力。

事實上,自動部署只是 GitHub Actions 功能的冰山一角,GitHub Actions 能作的事還不少不少,你們感興趣的話自行查閱。

最後

本文從技術選項到架構搭建、從代碼規範約束到提交信息規範約束,從單元測試到到自動部署,一步一步帶領你們如何從一個最簡單的前端項目骨架到規範的前端工程化環境,基本涵蓋前端項目開發的整個流程,特別適合剛接觸前端工程化的同窗學習。

因篇幅較長,所涉及技術點較多,不免會出現錯誤,但願你們多多指正,謝謝你們!


本文首發在公衆號@前端鼓勵師

關注我,爲您獻上做者精心準備的 2021 前端大禮包,涵蓋最新的 Vue / React / Angular / Node.js / Vite / 視頻教程 / 學習文檔 / 面試指南 / 大廠題庫 等等資料。

相關文章
相關標籤/搜索