從 0 開始手把手帶你搭建一套規範的 Vue3 項目工程環境
Vue3 跟 Vite 正式版發佈有很長一段時間了,生態圈也漸漸豐富起來,作者已在多個項目中使用,總結一下:就是快!也不用擔心穩定性問題,開發體驗真不是一般好!還沒嘗試的同學可以從本文開始學習,從 0 開始手把手帶你搭建一套基於 Vite + Vue3 + TypeScript 規範的前端工程化項目環境。
本文篇幅較長,從以下幾個方面展開:
-
架構搭建
-
代碼規範
-
提交規範
-
單元測試
-
自動部署
本項目完整的代碼託管在 GitHub 倉庫 [1] 歡迎點亮小星星 🌟🌟
技術棧
-
編程語言:TypeScript 4.x[2] + JavaScript[3]
-
構建工具:Vite 2.x[4]
-
前端框架:Vue 3.x[5]
-
路由工具:Vue Router 4.x[6]
-
狀態管理:Vuex 4.x[7]
-
UI 框架:Element Plus[8]
-
CSS 預編譯:Stylus[9] / Sass[10] / Less[11]
-
HTTP 工具:Axios[12]
-
Git Hook 工具:husky[13] + lint-staged[14]
-
代碼規範:EditorConfig[15] + Prettier[16] + ESLint[17] + Airbnb JavaScript Style Guide[18]
-
提交規範:Commitizen[19] + Commitlint[20]
-
單元測試:vue-test-utils[21] + jest[22] + vue-jest[23] + ts-jest[24]
-
自動部署:GitHub Actions[25]
架構搭建
請確保你的電腦上成功安裝 Node.js,本項目使用 Vite 構建工具,需要 Node.js 版本 >= 12.0.0。
查看 Node.js 版本:
node -v
建議將 Node.js 升級到最新的穩定版本:
# 使用 nvm 安裝最新穩定版 Node.js
nvm install stable
使用 Vite 快速初始化項目雛形
-
使用 NPM:
npm init @vitejs/app
-
使用 Yarn:
yarn create @vitejs/app
然後按照終端提示完成以下操作:
-
輸入項目名稱
例如:本項目名稱 vite-vue3-starter
image
-
選擇模板
本項目需要使用 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
-
安裝依賴
npm install
-
啓動項目
npm run dev
image
如上圖,表示 Vite + Vue3 + TypeScript 簡單的項目骨架搭建完畢,下面我們來爲這個項目集成 Vue Router、Vuex、Element Plus、Axios、Stylus/Sass/Less。
修改 Vite 配置文件
Vite 配置文件 vite.config.ts
位於根目錄下,項目啓動時會自動讀取。
本項目先做一些簡單配置,例如:設置 @
指向 src
目錄、 服務啓動端口、打包路徑、代理等。
關於 Vite 更多配置項及用法,請查看 Vite 官網 https://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
-
安裝支持 Vue3 的路由工具 vue-router@4
npm i vue-router@4
-
創建
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.vue
、vuex.vue
、axios.vue
。 -
在
main.ts
文件中掛載路由配置import { createApp } from "vue"; import App from "./App.vue"; import router from "./router/index"; createApp(App).use(router).mount("#app");
集成狀態管理工具 Vuex
-
安裝支持 Vue3 的狀態管理工具 vuex@next
npm i vuex@next
-
創建
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; }, }, });
-
在
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
-
安裝支持 Vue3 的 UI 框架 Element Plus
npm i element-plus
-
在
main.ts
文件中掛載 Element Plusimport { 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
-
安裝 Axios(Axios 跟 Vue 版本沒有直接關係,安裝最新即可)
npm i axios
-
配置 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;
-
使用 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 等。
-
安裝
npm i stylus -D # or npm i sass -D npm i less -D
-
使用
<style lang="stylus"> ... </style>
至此,一個基於 TypeScript + Vite + Vue3 + Vue Router + Vuex + Element Plus + Axios + Stylus/Sass/Less 的前端項目開發環境搭建完畢,項目 Demo 託管在 GitHub 倉庫 [26],需要的同學可以去下載下來,參考學習。
下面我們來打磨這個項目,增加代碼規範約束、提交規範約束、單元測試、自動部署等,讓其更完善、更健壯。
代碼規範
隨着前端應用逐漸變得大型化和複雜化,在同一個項目中有多個人員參與時,每個人的前端能力程度不等,他們往往會用不同的編碼風格和習慣在項目中寫代碼,長此下去,勢必會讓項目的健壯性越來越差。解決這些問題,理論上講,口頭約定和代碼審查都可以,但是這種方式無法實時反饋,而且溝通成本過高,不夠靈活,更關鍵的是無法把控。不以規矩,不能成方圓,我們不得不在項目使用一些工具來約束代碼規範。
本文講解如何使用 EditorConfig + Prettier + ESLint 組合來實現代碼規範化。
這樣做帶來好處:
-
解決團隊之間代碼不規範導致的可讀性差和可維護性差的問題。
-
解決團隊成員不同編輯器導致的編碼規範不統一問題。
-
提前發現代碼風格問題,給出對應規範提示,及時修復。
-
減少代碼審查過程中反反覆覆的修改過程,節約時間。
-
自動格式化,統一編碼風格,從此和髒亂差的代碼說再見。
集成 EditorConfig 配置
EditorConfig 有助於爲不同 IDE 編輯器上處理同一項目的多個開發人員維護一致的編碼風格。
官網:http://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 等語言,基本上前端能用到的文件格式它都可以搞定,是當下最流行的代碼格式化工具。
官網:https://prettier.io/
-
安裝 Prettier
npm i prettier -D
-
創建 Prettier 配置文件
Prettier 支持多種格式的配置文件 [27],比如
.json
、.yml
、.yaml
、.js
等。在本項目根目錄下創建
.prettierrc
文件。 -
配置
.prettierrc
在本項目中,我們進行如下簡單配置,關於更多配置項信息,請前往官網查看 Prettier-Options[28] 。
{ "useTabs": false, "tabWidth": 2, "printWidth": 100, "singleQuote": true, "trailingComma": "none", "bracketSpacing": true, "semi": false }
-
Prettier 安裝且配置好之後,就能使用命令來格式化代碼
# 格式化所有文件(. 表示所有文件) npx prettier --write .
注意:
-
VSCode 編輯器使用 Prettier 配置需要下載插件 Prettier - Code formatter 。
image
-
JetBrains 系列編輯器(WebStorm、IntelliJ IDEA 等)則不用額外安裝插件,可直接使用 Prettier 配置。
Prettier 配置好以後,在使用 VSCode 或 WebStorm 等編輯器的格式化功能時,編輯器就會按照 Prettier 配置文件的規則來進行格式化,避免了因爲大家編輯器配置不一樣而導致格式化後的代碼風格不統一的問題。
集成 ESLint 配置
ESLint[29] 是一款用於查找並報告代碼中問題的工具,並且支持部分問題自動修復。其核心是通過對代碼解析得到的 AST(Abstract Syntax Tree 抽象語法樹)進行模式匹配,來分析代碼達到檢查代碼質量和風格問題的能力。
正如前面我們提到的因團隊成員之間編程能力和編碼習慣不同所造成的代碼質量問題,我們使用 ESLint 來解決,一邊寫代碼一邊查找問題,如果發現錯誤,就給出規則提示,並且自動修復,長期下去,可以促使團隊成員往同一種編碼風格靠攏。
-
安裝 ESLint
可以全局或者本地安裝,作者推薦本地安裝(只在當前項目中安裝)。
npm i eslint -D
-
配置 ESLint
ESLint 安裝成功後,執行
npx eslint --init
,然後按照終端操作提示完成一系列設置來創建配置文件。
-
Airbnb JavaScript Style Guide[30]
Airbnb JavaScript 風格指南 - 中文版 [31]
image
-
JavaScript Standard Style[32]
JavaScript Standard Style - 中文版 [33]
image
-
Google JavaScript Style Guide[34]
-
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: https://github.com/airbnb/javascript
ESLint 爲我們列出了三種社區流行的 JavaScript 風格指南,分別是 Airbnb、Standard、Google。
這三份風格指南都是由衆多大佬根據多年開發經驗編寫,足夠優秀,全球很多大小公司都在使用。我們選用 GitHub 上 star 最多的 Airbnb,免去繁瑣的配置 ESLint 規則時間,然後讓團隊成員去學習 Airbnb JavaScript 風格指南即可。
此時,我們在 ESLint 配置了 Airbnb JavaScript 規則,在編碼時,所有不符合 Airbnb 風格的代碼,編輯器都會給出提示,並且可以自動修復。
這裏作者不建議大家去自由配置 ESLint 規則,相信我,這三份 JavaScript 代碼風格指南值得我們反覆學習,掌握後,編程能力能上一大臺階。
-
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
-
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
-
WebStorm
雖然,現在編輯器已經給出錯誤提示和修復方案,但需要我們一個一個去點擊修復,還是挺麻煩的。很簡單,我們只需設置編輯器保存文件時自動執行 eslint --fix
命令進行代碼風格修復。
-
VSCode 在
settings.json
設置文件中,增加以下代碼:"editor.codeActionsOnSave": { "source.fixAll.eslint": true }
-
WebStorm 打開設置窗口,按如下操作,最後點擊
Apply
->OK
。
解決 Prettier 和 ESLint 的衝突
通常大家會在項目中根據實際情況添加一些額外的 ESLint 和 Prettier 配置規則,難免會存在規則衝突情況。
本項目中的 ESLint 配置中使用了 Airbnb JavaScript 風格指南校驗,其規則之一是_代碼結束後面要加分號_,而我們在 Prettier 配置文件中加了_代碼結束後面不加分號_的配置項,這樣就有衝突了,會出現用 Prettier 格式化後的代碼,ESLint 檢測到格式有問題的,從而拋出錯誤提示。
解決兩者衝突問題,需要用到 eslint-plugin-prettier 和 eslint-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[35] + lint-staged[36] 。
husky[37] —— Git Hook 工具,可以設置在 git 各個階段(
pre-commit
、commit-msg
、pre-push
等)觸發我們的命令。
lint-staged[38] —— 在 git 暫存的文件上運行 linters。
配置 husky
-
自動配置(推薦)
使用
husky-init
命令快速在項目初始化一個 husky 配置。npx husky-init && npm install
這行命令做了四件事:
-
安裝 husky 到開發依賴
-
在項目根目錄下創建
.husky
目錄 -
在
.husky
目錄創建pre-commit
hook,並初始化pre-commit
命令爲npm test
-
修改
package.json
的scripts
,增加"prepare": "husky install"
-
手動配置(不推薦,懶是程序員第一生產力)
-
在項目根目錄下創建
.husky
目錄 -
在
.husky
目錄創建pre-commit
hook,並初始化pre-commit
命令爲npm test
-
安裝 husky
npm i husky -D
-
創建 Git hooks
npx husky install
該命令做了兩件事:
-
手動修改
package.json
的scripts
,增加"prepare": "husky install"
特別注意:本項目使用 husky 6.x 版本,6.x 版本配置方式跟之前的版本有較大差異。目前網上大部分有關 husky 的教程都是 6 以前的版本 ,跟本文教程不太一樣,當發現配置方法不一致時,一切以 husky 官網 [39] 爲準。
到這裏,husky 配置完畢,現在我們來使用它:
husky 包含很多 hook
(鉤子),常用有:pre-commit
、commit-msg
、pre-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 繼續優化項目。
-
安裝 lint-staged
npm i lint-staged -D
-
在
package.json
裏增加 lint-staged 配置項image
"lint-staged": { "*.{vue,js,ts}": "eslint --fix" },
這行命令表示:只對 git 暫存區的
.vue
、.js
、.ts
文件執行eslint --fix
。 -
修改
.husky/pre-commit
hook 的觸發命令爲:npx lint-staged
image
至此,husky 和 lint-staged 組合配置完成。
現在我們提交代碼時就會變成這樣:
假如我們修改了 scr
目錄下的 test-1.js
、test-2.ts
和 test-3.md
文件,然後 git add ./src/
,最後 git commit -m "test..."
,這時候就會只對 test-1.js
、test-2.ts
這兩個文件執行 eslint --fix
。如果 ESLint 通過,成功提交,否則終止提交。從而保證了我們提交到 Git 倉庫的代碼都是規範的。
image
-
提交前
test-1.js
、test-2.ts
-
提交後
test-1.js
、test-2.ts
自動修復代碼格式
無論寫代碼還是做其他事情,都應該用長遠的眼光來看,剛開始使用 ESint 的時候可能會有很多問題,改起來也很費時費力,只要堅持下去,代碼質量和開發效率都會得到提升,前期的付出都是值得的。
這些工具並不是必須的,沒有它們你同樣可以可以完成功能開發,但是利用好這些工具,你可以寫出更高質量的代碼。特別是一些剛剛接觸的人,可能會覺得麻煩而放棄使用這些工具,失去了一次提升編程能力的好機會。
提交規範
前面我們已經統一代碼規範,並且在提交代碼時進行強約束來保證倉庫代碼質量。多人協作的項目中,在提交代碼這個環節,也存在一種情況:不能保證每個人對提交信息的準確描述,因此會出現提交信息紊亂、風格不一致的情況。
如果 git commit
的描述信息精準,在後期維護和 Bug 處理時會變得有據可查,項目開發週期內還可以根據規範的提交信息快速生成開發日誌,從而方便我們追蹤項目和把控進度。
這裏,我們使用社區最流行、最知名、最受認可的 Angular 團隊提交規範。
先看看 Angular 項目的提交記錄 [40]:
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 的提交類型(必須是以下幾種之一)。
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。
最後,在 Git 提交歷史中就能看到剛剛規範的提交記錄了:
自定義配置提交說明
從上面的截圖可以看到,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
文件,然後按照官方提供的示例 [41] 來配置。
在本項目中我們修改成中文:
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
不符合規範,提交失敗。 -
符合規範的提交信息
如下圖,提交信息test: commitlint test
符合規範,成功提交到倉庫。
因爲已在項目中集成 commitizen,建議大家用 git cz
來代替 git commit
提交代碼,可以保證提交信息規範。
單元測試
單元測試是項目開發中一個非常重要的環節,完整的測試能爲代碼和業務提供質量保證,減少 Bug 的出現。
本章節將帶領大家在 Vite + Vue3 + TypeScript 的項目中集成單元測試工具。
安裝核心依賴
我們使用 Vue 官方提供的 vue-test-utils 和社區流行的測試工具 jest 來進行 Vue 組件的單元測試。
-
vue-test-utils[42] The next iteration of Vue Test Utils. It targets Vue 3.
-
jest[43] Delightful JavaScript Testing.
-
vue-jest[44] Jest Vue transformer
-
ts-jest[45] 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 會提示某些方法不存在(如 test
、describe
、it
、expect
等),安裝 @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
配置文件中,自由配置單元測試文件的目錄。
-
單元測試全部通過時的終端顯示信息
-
單元測試未全部通過時的終端顯示信息
當單元測試沒有全部通過時,我們需要根據報錯信息去優化對應組件的代碼,進一步提高項目健壯性。但是寫單元測試是件比較痛苦的事,我個人覺得也沒必要全部組件都寫單元測試,根據項目實際情況有針對性去寫就行了。
單元測試約束
前面,我們使用 husky 在 Git 的 pre-commit
和 commit-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、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 倉庫 [46] 來託管項目代碼。
image
其中,我們用:
-
master
分支存儲項目源代碼 -
gh-pages
分支存儲打包後的靜態文件
gh-pages
分支,是 GitHub Pages 服務的固定分支,可以通過 HTTP 的方式訪問到這個分支的靜態文件資源。
創建 GitHub Token
創建一個有 repo 和 workflow 權限的 GitHub Token[47]
image
注意:新生成的 Token 只會顯示一次,保存起來,後面要用到。如有遺失,重新生成即可。
image
在倉庫中添加 secret
將上面新創建的 Token 添加到 GitHub 倉庫的 Secrets
裏,並將這個新增的 secret
命名爲 VUE3_DEPLOY
(名字無所謂,看你喜歡)。
步驟:倉庫 -> settings
-> Secrets
-> New repository secret
。
image
新創建的 secret
VUE3_DEPLOY
在 Actions 配置文件中要用到,兩個地方需保持一致!
創建 Actions 配置文件
-
在項目根目錄下創建
.github
目錄。 -
在
.github
目錄下創建workflows
目錄。 -
在
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 上,最後,我們就能通過域名訪問了。
🌏 通過域名 https://vite-vue3-starter.xpoet.cn/ 訪問本項目
使用自動部署,我們只需專注於項目開發階段,任何重複且枯燥的行爲都交由程序去完成,懶纔是程序員第一生產力。
事實上,自動部署只是 GitHub Actions 功能的冰山一角,GitHub Actions 能做的事還很多很多,大家感興趣的話自行查閱。
最後
本文從技術選項到架構搭建、從代碼規範約束到提交信息規範約束,從單元測試到自動部署,一步一步帶領大家如何從一個最簡單的前端項目骨架到規範的前端工程化環境,基本上涵蓋了前端項目開發的整個週期。
因篇幅較長,所涉及技術點較多,難免會出現錯誤,希望大家多多指正,謝謝大家!
本項目完整的代碼託管在 GitHub 倉庫 [48],需要的同學自取,別忘了點 Star 支持作者~
參考資料
[1]
GitHub 倉庫: https://github.com/XPoet/vite-vue3-starter
[2]
TypeScript 4.x: https://www.typescriptlang.org/zh/
[3]
JavaScript: https://www.javascript.com/
[4]
Vite 2.x: https://cn.vitejs.dev/
[5]
Vue 3.x: https://v3.cn.vuejs.org/
[6]
Vue Router 4.x: https://next.router.vuejs.org/zh/index.html
[7]
Vuex 4.x: https://next.vuex.vuejs.org/
[8]
Element Plus: https://element-plus.org/#/zh-CN
[9]
Stylus: https://stylus-lang.com/
[10]
Sass: https://sass.bootcss.com/documentation
[11]
Less: http://lesscss.cn/
[12]
Axios: https://axios-http.com/
[13]
husky: https://typicode.github.io/husky/#/
[14]
lint-staged: https://github.com/okonet/lint-staged
[15]
EditorConfig: http://editorconfig.org
[16]
Prettier: https://prettier.io/
[17]
ESLint: https://eslint.org/
[18]
Airbnb JavaScript Style Guide: https://github.com/airbnb/javascript#translation
[19]
Commitizen: http://commitizen.github.io/cz-cli/
[20]
Commitlint: https://commitlint.js.org/#/
[21]
vue-test-utils: https://next.vue-test-utils.vuejs.org/
[22]
jest: https://jestjs.io/
[23]
vue-jest: https://github.com/vuejs/vue-jest
[24]
ts-jest: https://kulshekhar.github.io/ts-jest/
[25]
GitHub Actions: https://docs.github.com/cn/actions/learn-github-actions
[26]
GitHub 倉庫: https://github.com/XPoet/vite-vue3-starter
[27]
配置文件: https://prettier.io/docs/en/configuration.html
[28]
Prettier-Options: https://prettier.io/docs/en/options.html
[29]
ESLint: https://github.com/eslint/eslint
[30]
Airbnb JavaScript Style Guide: https://github.com/airbnb/javascript
[31]
Airbnb JavaScript 風格指南 - 中文版: https://github.com/lin-123/javascript
[32]
JavaScript Standard Style: https://github.com/standard/standard
[33]
JavaScript Standard Style - 中文版: https://github.com/standard/standard/blob/master/docs/README-zhcn.md
[34]
Google JavaScript Style Guide: https://google.github.io/styleguide/jsguide.html
[35]
husky: https://github.com/typicode/husky
[36]
lint-staged: https://github.com/okonet/lint-staged
[37]
husky: https://github.com/typicode/husky
[38]
lint-staged: https://github.com/okonet/lint-staged
[39]
husky 官網: https://typicode.github.io/husky/#/?id=usage
[40]
Angular 項目的提交記錄: https://github.com/angular/angular/commits/master
[41]
示例: https://github.com/leoforfree/cz-customizable/blob/master/cz-config-EXAMPLE.js
[42]
vue-test-utils: https://github.com/vuejs/vue-test-utils-next
[43]
jest: https://github.com/facebook/jest
[44]
vue-jest: https://github.com/vuejs/vue-jest
[45]
ts-jest: https://github.com/kulshekhar/ts-jest
[46]
創建 GitHub 倉庫: https://github.com/new
[47]
GitHub Token: https://github.com/settings/tokens/new
[48]
GitHub 倉庫: https://github.com/XPoet/vite-vue3-starter
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/uLliWY6-PGNA9jog4myEkg