本文是算法與 TypeScript 實現中 TypeScript 項目總體的環境配置過程介紹。主要包括瞭如下一些配置內容:javascript
若是你對以上的某些配置很是熟悉,則能夠跳過閱讀。若是你不清楚是否要繼續閱讀其中的一些配置信息,則能夠經過工程問題來決定是否要繼續閱讀相關的內容。html
算法與 TypeScript 實現 關於當前配置的改造在 feat/framework 分支上,但願剛興趣的同窗能夠 star 一波。學習文檔 目前仍然是老版本的學習文檔,以後會進行持續更新。前端
舒適提示:若是你但願在項目中製做基於 TypeScript 實現的簡單易用的工具函數庫,你可使用一些成熟的 "零配置" 腳手架,例如 tsdx、microbundle 以及 typescript-starter 等。若是功能不能知足你的項目需求,你也能夠基於這些工具進行團隊的定製化改造,例如 ts-lib-scripts。vue
但願你讀完這篇文章可以瞭解如下一些問題(頗有可能成爲工程配置方面的面試題哦,細節決定成敗):java
pre-commit
和 commit-msg
鉤子的區別是什麼?各自可用於作什麼?除此以外若是你對其餘相關的知識感興趣(非本文相關的知識),但願你能額外深刻去探索:node
舒適提示:有些問題在本文中可以獲得答案,有些問題須要本身擴展閱讀或查看源碼才能獲得答案(做者一樣是工程化配置領域的小白,以上的這些問題一樣在問本身)。python
須要注意文檔中的配置說明可能會省略某些細節步驟(例如某些依賴的 npm 包安裝、某些配置文件說明等),若是想要知道更多細節信息,可查看各個配置的 Commit 提交信息:react
舒適提示:以上都是使用
npm run changelog
自動生成的版本日誌信息,你也能夠經過倉庫的 CHANGELOG.md 進行查看。webpack
Commitizen 是一個規範 Git 提交說明(Commit Message)的 CLI 工具,具體如何配置可查看 Cz 工具集使用介紹(這篇文章對於 Commit Message 的配置介紹已經很是詳細清楚,所以這裏再也不過多介紹)。本項目中主要使用瞭如下一些工具:git
配置後會產生如下一些特性:
git cz
代替 git commit
進行符合 Angular 規範的 Commit Message 信息提交npm run changelog
會在根目錄下自動生成 CHANGELOG.md
版本日誌舒適提示:husky 中文的意思是哈士奇,你們能夠想象一下爲何這個工具叫哈士奇,是否是咬着你不放的意思(or more 🐶 woof!),一旦它咬着你的代碼提交不放,這將會是很是有趣的一件事情,在後續的工具配置中,咱們仍然將於哈士奇見面,看看它會具體咬什麼東西!
例如當你提交了一個不符合規範的 Commit Message(此時提交失敗):
PS C:\Code\Git\algorithms> git commit -m "這是一個不符合規範的 Commit Message"
husky > commit-msg (node v12.13.1)
⧗ input: 這是一個不符合規範的 Commit Message
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky > commit-msg hook failed (add --no-verify to bypass)
複製代碼
舒適提示:若是不知道什麼是 CLI (命令行接口),可查看 使用 NPM 發佈和使用 CLI 工具。
工具函數庫的實現採用 TypeScript,除了能夠自動生成 ts 聲明文件供外部更好的提示使用以外,也能夠避免 JavaScript 動態性所帶來的一些沒法預料的錯誤信息(具體可查看 Top 10 JavaScript errors from 1000+ projects (and how to avoid them)),從而使算法的設計更加嚴謹。 TypeScript 的構建方式有不少種,除了原生編譯器 tsc 之外,還包括 Webpack、Rollup、Babel 以及 Gulp 等(更多構建工具的集成可查看 Integrating with Build Tools):
因爲算法的函數工具庫功能很是單一簡單,所以採用 TypeScript 官方推薦的 Gulp 工具進行構建便可知足需求。
舒適提示:更多構建工具能夠了解 esbuild、parcel以及 backpack 等。固然若是你想要更多瞭解這些構建工具的差別以及在什麼項目環境下應該作如何選型,能夠自行搜索前端構建工具的對比或差別,這裏推薦一篇我的以爲總結不錯的文章 前端構建:3 類 13 種熱門工具的選型參考。
本項目會構建輸出 CommonJS 工具包(npm 包)供外部使用,採用 TypeScript 設計並輸出聲明文件有助於外部更好的使用該資源包進行 API 的提示。TypeScript 編譯採用官方文檔推薦的 Gulp 工具並配合 gulp-typescript 和 tsconfig.json 配置文件。在根目錄下新建 tsconfig.json
文件並新增如下配置:
{
"compilerOptions": {
// 指定 ECMAScript 目標版本 "ES3"(默認), "ES5", "ES6" / "ES2015", "ES2016", "ES2017" 或 "ESNext"。
"target": "ES5",
// 構建的目標代碼刪除全部註釋,除了以 /!* 開頭的版權信息
"removeComments": true,
// 可配合 gulp-typescript 生成相應的 .d.ts 文件
"declaration": true,
// 啓用全部嚴格類型檢查選項。啓用 --strict 至關於啓用 --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks, --strictFunctionTypes 和 --strictPropertyInitialization
"strict": true,
// 禁止對同一個文件的不一致的引用
"forceConsistentCasingInFileNames": true,
// 報錯時不生成輸出文件
"noEmitOnError": true
}
}
複製代碼
舒適提示:這裏沒有新增
module
配置信息,由於默認輸出 CommonJS 規範,更多關於 TypeScript 配置信息可查看TypeScript 官方文檔 / 編譯選項。若是對於 CommonJS 和 ES modules 的區別不是很清晰,這裏有一些很是好的文檔能夠供你們閱讀:ES modules: A cartoon deep-dive、ES6 modules。若是想了解如何對外提供 ES Module 能夠查看 pkg.module。
同時在根目錄下新建 gulpfile.js
文件:
const gulp = require("gulp");
const ts = require("gulp-typescript");
const tsProject = ts.createProject("tsconfig.json");
// 輸出 CommonJS 規範到 dist 目錄下
gulp.task("default", function () {
const tsResult = tsProject.src().pipe(tsProject());
return tsResult.js.pipe(gulp.dest("dist"));
});
複製代碼
在 package.json
中新增 npm script 腳本:
"scripts": {
"build": "rimraf dist && gulp"
},
複製代碼
其中 rimfaf 用於在構建以前清除 dist
目錄文件內容。此時在 src
目錄下新增 TypeScript 源碼並使用 npm run build
命令能夠進行項目構建並輸出 CommonJS 規範的目標代碼到 dist
目錄下。
除此以外,此項目但願能夠快速生成聲明文件供外部進行代碼提示,此時仍然能夠藉助 gulp-typescript
工具自動生成聲明文件。在 gulpfile.js
中新增如下配置
const gulp = require("gulp");
const ts = require("gulp-typescript");
const tsProject = ts.createProject("tsconfig.json");
const merge = require("merge2");
// 輸出 CommonJS 規範到 dist 目錄下
gulp.task("default", function () {
const tsResult = tsProject.src().pipe(tsProject());
return merge([
tsResult.dts.pipe(gulp.dest("types")),
tsResult.js.pipe(gulp.dest("dist")),
]);
});
複製代碼
修改 build
命令使其在構建以前同時能夠刪除 types
目錄:
"scripts": {
"build": "rimraf dist types && gulp",
},
複製代碼
再次執行 npm run build
會在項目根目錄下生成 types
文件夾,該文件夾主要存放自動生成的 TypeScript 聲明文件。
須要注意發佈 npm 包時默認會將當前項目的全部文件進行發佈處理,但這裏但願發佈的包只包含使用者須要的編譯文件 dist
和 types
,所以能夠經過package.json
中的 files
(用於指定發佈的 npm 包包含哪些文件) 字段信息進行控制:
"files": [
"dist",
"types"
],
複製代碼
舒適提示:發佈的 npm 包中某些文件將忽視
files
字段信息的配置,包括package.json
、LICENSE
、README.md
等。
除此以外,若是但願發佈的 npm 包經過 require('algorithms-utils')
或 import
形式引入時指向 dist/index.js
文件,須要配置 package.json
中的 main
字段信息:
"main": "dist/index.js"
複製代碼
舒適提示: 對於工具包使用全量引入的方式並非一個好的選擇,能夠經過具體的工具方法進行按需引入。
TypeScript 的代碼檢查工具主要有 TSLint 和 ESLint 兩種。早期的 TypeScript 項目通常採用 TSLint 進行檢查。TSLint 和 TypeScript 採用一樣的 AST 格式進行編譯,但主要問題是對於 JavaScript 生態的項目支持不夠友好,所以 TypeScript 團隊在 2019 年宣佈全面轉向 ESLint(具體可查看 TypeScript 官方倉庫的 .eslintrc.json
配置),更多關於轉向 ESLint 的緣由可查看:
TypeScript 和 ESLint 使用不一樣的 AST 進行解析,所以爲了在 ESLint 中支持 TypeScript 代碼檢查須要製做額外的自定義解析器(Custom Parsers,ESLint 的自定義解析器功能須要基於 ESTree),目的是爲了可以解析 TypeScript 語法並轉成與 ESLint 兼容的 AST。@typescript-eslint/parser 在這樣的背景下誕生,它會處理全部 ESLint 特定的配置並調用 @typescript-eslint/typescript-estree 生成 ESTree-compatible AST(須要注意不只僅兼容 ESLint,也能兼容 Prettier)。
@typescript-eslint
是一個採用 Lerna 進行設計的 Monorepo 結構倉庫,除了上述提到的 npm 包以外,還包含如下兩個重要的 npm 包:
@typescript-eslint/parser
一塊兒使用的 ESLint 插件,能夠設置 TypeScript 的校驗規則。舒適提示:若是你正在使用 TSLint,而且你但願兼容 ESLint 或者向 ESLint 進行過渡(TSLint 和 ESLint 並存), 可查看 Migrating from TSLint to ESLint。除此以外,以上所介紹的這些包發佈時版本一致(爲了聯合使用的兼容性),須要額外注意
@typescript-eslint
對於 TypeScript 和 ESLint 的版本支持性,更多可查看 @typescript-eslint/parser 的倉庫信息。
從背景的介紹中能夠理解,對於全新的 TypeScript 項目(直接拋棄 TSLint)須要包含解析 AST 的解析器 @typescript-eslint/parser 和使用校驗規則的插件 @typescript-eslint/eslint-plugin,這裏須要在項目中進行安裝:
npm i --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
複製代碼
在根目錄新建 .eslintrc.js
配置文件,並設置如下配置:
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
};
複製代碼
其中:
parser: '@typescript-eslint/parser'
:使用 ESLint 解析 TypeScript 語法plugins: ['@typescript-eslint']
:在 ESLint 中加載插件 @typescript-eslint/eslint-plugin
,該插件可用於配置 TypeScript 校驗規則。extends: [ ... ]
:在 ESLint 中使用共享規則配置,其中 eslint:recommended
是 ESLint 內置的推薦校驗規則配置(也被稱做最佳規則實踐),plugin:@typescript-eslint/recommended
是相似於 eslint:recommended
的 TypeScript 推薦校驗規則配置。舒適提示:若是你稍微閱讀一下 recommanded 源碼你會發現,其實內部能夠理解爲推薦校驗規則的集合。所以若是想基於
@typescript-eslint/eslint-plugin
進行自定義規則,則可參考 TypeScript Supported Rules。
配置完成後在 package.json
中設置校驗命令
"scripts": {
"lint": "eslint src",
}
複製代碼
此時若是在 src
目錄下書寫錯誤的語法,執行 npm run lint
就會輸出錯誤信息:
> eslint src
C:\Code\Git\algorithms\src\greet.ts
2:16 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types ✖ 1 problem (0 errors, 1 warning) 複製代碼
舒適提示:輸出的錯誤信息是經過 ESLint Formatters 生成,查看 ESLint 源代碼並調試可發現默認採用的是 stylish formatter 。
若是不使用插件,很難發現代碼可能存在 TypeScript 格式錯誤,由於在書寫代碼的時候除了手動執行 npm run lint
之外沒有任何的實時提示信息(你固然也能夠經過 gulp
監聽文件的變化並執行 npm run lint
)。爲了能夠實時看到 TypeScript 錯誤信息,能夠經過 VS Code 插件進行處理。安裝 ESLint 插件後可進行代碼的實時提示,具體以下圖所示:
固然爲了防止不須要被校驗的文件出現校驗信息,能夠經過 .eslintignore
文件進行配置(例如如下都是一些不須要格式校驗的配置文件):
# gulp
gulpfile.js
# eslint
.eslintrc.js
# commitizen
commitlint.config.js
# jest
jest.config.js
# build
dist
複製代碼
此時能夠發現以前執行 lint
命令的錯誤經過插件的形式可實時在 VS Code 編輯器中進行顯示。除此以外,一些 ESLint 的格式校驗錯誤(例如多餘的;
等)可經過配置 Save Auto Fix 進行保存自動格式化處理。具體 VS Code 的配置可參考 ESLint 插件 的文檔說明,這邊應該須要進行以下配置:
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
}
複製代碼
舒適提示:VS Code 的配置分爲兩種類型(用戶和工做區),針對上述通用的配置主要放在用戶裏,針對不一樣項目的不一樣配置則能夠放入工做區進行處理。
VS Code 插件並不能確保代碼上傳或構建前無任何錯誤信息,此時仍然須要額外的流程可以避免錯誤。在構建前進行 ESLint 校驗可以確保構建時無任何錯誤信息,一旦 ESLint 校驗不經過則不容許進行源碼的構建操做:
"scripts": {
"lint": "eslint src --max-warnings 0",
"build": "npm run lint && rimraf dist types && gulp",
}
複製代碼
須要注意在構建時進行校驗的嚴格控制,一旦 lint 拋出 warning 或者 error 則立馬終止構建(詳情可查看 ESLint 退出代碼)。
舒適提示:須要注意 Shell 中的
&&
和&
是有差別的,&&
主要用於繼發執行,只有前一個任務執行成功,纔會執行下一個任務,&
主要用於併發執行,表示兩個腳本同時執行。這裏構建的命令須要等待lint
命令執行經過才能進行,一旦lint
失敗那麼構建命令將再也不執行。
儘管可能配置了 ESLint 的校驗腳本 以及 VS Code 插件,可是有些 ESLint 的規則校驗是沒法經過 Save Auto Fix 進行格式化修復的(例如質量規則),所以還須要一層保障可以確保代碼提交以前全部的代碼可以經過 ESLint 校驗,這個配置將在 Lint Staged 中進行講解。
Prettier 是一個統一代碼格式風格的工具,若是你不清楚爲何須要使用 Prettier,能夠查看 Why Prettier?。不少人可能疑惑,ESLint 已經可以規範咱們的代碼風格,爲何還須要 Prettier?在 Prettier vs Linters 中詳細說明了二者的區別,Linters 有兩種類型的規則:
ESLint 的規則校驗同時包含了 格式規則 和 質量規則,可是大部分狀況下只有 格式規則 能夠經過 --fix
或 VS Code 插件的 Sava Auto Fix 功能一鍵修復,而 質量規則 更多的是發現代碼可能出現的 Bug 從而防止代碼出錯,這類規則每每須要手動修復。所以 格式規則 並非必須的,而 質量規則 則是必須的。Prettier 與 ESLint 的區別在於 Prettier 專一於統一的格式規則,從而減輕 ESLint 在格式規則上的校驗,而對於質量規則 則交給專業的 ESLint 進行處理。總結一句話就是:Prettier for formatting and linters for catching bugs!(ESLint 是必須的,Prettier 是可選的!)
須要注意若是 ESLint(TSLint) 和 Prettier 配合使用時格式規則有重複且產生了衝突,那麼在編輯器中使用 Sava Auto Fix 時會讓你的一鍵格式化啼笑皆非。此時應該讓二者把各自注重的規則功能區分開,使用 ESLint 校驗質量規則,使用 Prettier 校驗格式規則,更多信息可查看 Integrating with Linters。
舒適提示:在 VS Code 中使用 ESLint 匹配到相應的規則時會產生黃色波浪線以及紅色文件名進行錯誤提醒。Prettier 更但願你對格式規則無感知,從而不會讓你以爲有任何使用的負擔。若是想要了解更多 Prettier,還能夠查看 Prettier 的背後思想 Option Philosophy,我的認爲了解一個產品設計的哲學能更好的指導你使用該產品。
首先安裝 Prettier 所須要的依賴:
npm i prettier eslint-config-prettier --save-dev
複製代碼
其中:
@typescript-eslint/eslint-plugin
、eslint-plugin-babel
、eslint-plugin-react
、eslint-plugin-vue
、eslint-plugin-standard
等格式規則。理論上而言,在項目中開啓 ESLint 的 extends
中設置的帶有格式規則校驗的規則集,那麼就須要經過 eslint-config-prettier
插件關閉可能產生衝突的格式規則:
{
"extends": [
"plugin:@typescript-eslint/recommended",
// 用於關閉 ESLint 相關的格式規則集,具體可查看 https://github.com/prettier/eslint-config-prettier/blob/master/index.js
"prettier",
// 用於關閉 @typescript-eslint/eslint-plugin 插件相關的格式規則集,具體可查看 https://github.com/prettier/eslint-config-prettier/blob/master/%40typescript-eslint.js
"prettier/@typescript-eslint",
]
}
複製代碼
配置完成後,能夠經過命令行接口運行 Prettier:
"scripts": {
"prettier": "prettier src test --write",
},
複製代碼
--write
參數相似於 ESLint 中的 --fix
(在 ESLint 中使用該參數仍是須要謹慎哈,建議仍是使用 VS Code 的 Save Auto Fix 功能),主要用於自動修復格式錯誤。此時書寫格式的錯誤代碼:
import great from "@/greet";
// 中間這麼多空行
export default {
great,
};
複製代碼
執行 npm run prettier
進行格式修復:
PS C:\Code\Git\algorithms> npm run prettier
> algorithms-utils@1.0.0 prettier C:\Code\Git\algorithms
> prettier src test --write
src\greet.ts 149ms
src\index.ts 5ms
test\greet.spec.ts 11ms
複製代碼
修復以後的的文件格式以下:
import great from "@/greet";
export default {
great,
};
複製代碼
須要注意若是某些規則集沒有對應的 eslint-config-prettier
關閉配置,那麼能夠先經過 CLI helper tool 檢測是否有重複的格式規則集在生效,而後能夠經過手動配置 eslintrc.js
的形式進行關閉:
PS C:\Code\Git\algorithms> npx eslint --print-config src/index.ts | npx eslint-config-prettier-check
No rules that are unnecessary or conflict with Prettier were found.
複製代碼
例如把 eslint-config-prettier
的配置去除,此時進行檢查重複規則:
PS C:\Code\Git\algorithms> npx eslint --print-config src/index.ts | npx eslint-config-prettier-check
The following rules are unnecessary or might conflict with Prettier:
- @typescript-eslint/no-extra-semi
- no-mixed-spaces-and-tabs
The following rules are enabled but cannot be automatically checked. See:
https://github.com/prettier/eslint-config-prettier#special-rules
- no-unexpected-multiline
複製代碼
此時假設 eslint-config-prettier
沒有相似的關閉格式規則集(例如本項目中配置的 plugin:jest/recommended
可能存在規則衝突),那麼能夠經過配置 .eslintrc.js
的形式本身手動關閉相應衝突的格式規則。
舒適提示:ESLint 能夠對不一樣的文件支持不一樣的規則校驗, 所以
--print-config
只能對應單個文件的衝突格式規則檢查。 因爲一般的項目是一套規則對應一整個項目,所以對於整個項目全部的規則只須要校驗一個文件是否有格式規則衝突便可。
經過命令行接口 --write
的形式能夠進行格式自動修復,可是相似 ESLint,咱們更但願項目在實時編輯時能夠經過保存就能自動格式化代碼(鬼知道 --fix
以及 --write
格式了什麼文件,固然更但願經過肉眼的形式當即感知代碼的格式化變化),此時能夠經過配置 VS Code 的 Prettier - Code formatter 插件進行 Save Auto Fix,具體的配置查看插件文檔。
和 ESLint 同樣,儘管可能配置了 Prettier 的自動修復格式腳本以及 VS Code 插件,可是沒法確保格式遺漏的狀況,所以還須要一層保障可以確保代碼提交以前可以進行 Prettier 格式化,這個配置將在 Lint Staged 中講解,更多配置方案也能夠查看 Prettier - Pre-commit Hook。
在 Git Commit Message 中使用了 commitlint 工具配合 husky 能夠防止生成不規範的 Git Commit Message,從而阻止用戶進行不規範的 Git 代碼提交,其原理就是監聽了 Git Hook 的執行腳本(會在特定的 Git 執行命令諸如 commit
、push
、merge
等觸發以前或以後執行相應的腳本鉤子)。Git Hook 實際上是進行項目約束很是好用的工具,它的做用包括但不限於:
Git Hook 的鉤子很是多,可是在客戶端中可能經常使用的鉤子是如下兩個:
pre-commit
:Git 中 pre
系列鉤子容許終止即將發生的 Git 操做,而post
系列每每用做通知行爲。pre-commit
鉤子在鍵入提交信息(運行 git commit
或 git cz
)前運行,主要用於檢查當前即將被提交的代碼快照,例如提交遺漏、測試用例以及代碼等。該鉤子若是以非零值退出則 Git 將放棄本次提交。固然你也能夠經過配置命令行參數 git commit --no-verify
繞過鉤子的運行。commit-msg
:該鉤子在用戶輸入 Commit Message 後被調用,接收存有當前 Commit Message 信息的臨時文件路徑做爲惟一參數,所以能夠利用該鉤子來覈對 Commit Meesage 信息(在 Git Commit Message 中使用了該鉤子對提交信息進行了是否符合 Angular 規範的校驗)。該鉤子和 pre-commit
相似,一旦以非零值退出 Git 將放棄本次提交。除了上述經常使用的客戶端鉤子,還有兩個經常使用的服務端鉤子:
pre-receive
:該鉤子會在遠程倉庫接收 git push
推送的代碼時執行(注意不是本地倉庫),該鉤子會比 pre-commit
更加有約束力(總會有這樣或那樣的開發人員不喜歡提交代碼時所作的一堆檢測,他們可能會選擇繞過這些鉤子)。pre-receive
鉤子可用於接收代碼時的強制規範校驗,若是某個開發人員採用了繞過 pre-commit
鉤子的方式提交了一堆 💩 同樣的代碼,那麼經過設置該鉤子能夠拒絕代碼提交。固然該鉤子最經常使用的操做仍是用於檢查是否有權限推送代碼、非快速向前合併等。post-receive
:該鉤子在推送代碼成功後執行,適合用於發送郵件通知或者觸發 CI 。舒適提示:想了解更多 Git Hook 信息能夠查看 Git Hook 官方文檔 或 Git 鉤子:自定義你的工做流。
須要注意初始化 Git 以後默認會在 .git/hooks
目錄下生成全部 Git 鉤子的 Shell 示例腳本,這些腳本是能夠被定製化的。對於前端開發而言去更改這些示例腳本適配前端項目很是不友好(大多數前端開發同窗壓根不會設計 Shell 腳本,儘管這個對於製做工具是一件很是高效的事情),所以社區就出現了相似的加強工具,它們對外拋出的是簡單的鉤子配置(例如 ghooks 在 package.json
中只須要進行簡單的鉤子屬性配置),而在內部則經過替換 Git 鉤子示例腳本的形式使得外部配置的鉤子能夠被執行,例如 husky、ghooks 以及 pre-commit 等。
舒適提示: Git Hook 還能夠定製腳本執行的語言環境,例如對於前端而言固然但願使用熟悉的 Node 進行腳本設計,此時能夠經過在腳本文件的頭部設置
#! /usr/bin/env node
將 Node 做爲可執行文件的環境解釋器,若是你以前看過 使用 NPM 發佈和使用 CLI 工具 可能會對這個環境解析器相對熟悉,這裏也給出一個使用 Node 解釋器的示例:ghooks - hook.template.raw,ghooks 的實現很是簡單,感興趣的同窗能夠仔細閱讀一些源碼的實現。
介紹 Git Hook 是爲了讓你們清晰的認知到使用 Hook 能夠在前端的工程化項目中作不少事情(原本應該放在 Git Commit Message 中介紹相對合適,可是鑑於那個小節引用了另一篇文章,所以將這個信息放在本小節進行科普)。
以前提到使用 Git Hook 能夠進行 ESLint 規範約束,所以你們其實應該可以猜到使用 pre-commit
鉤子(固然須要藉助 Git Hook 加強工具,本項目中一概選擇 husky
)配合 ESLint 能夠進行提交說明前的項目代碼規則校驗,可是若是項目愈來愈大,ESLint 校驗的時間可能愈來愈長,這對於頻繁的代碼提交者而言多是一件相對痛苦的事情,所以能夠藉助 lint-staged
工具(聽這個工具的名字就可以猜想 lint 的是已經放入 Git Stage 暫存區中的代碼,ed
在英文中代表已經作過)減小代碼的檢測量。
使用 commitlint 工具能夠防止生成不規範的 Git Commit Message,從而阻止用戶進行 Git 代碼提交。可是若是想要防止團隊協做時開發者提交不符合 ESLint 規則的代碼則能夠經過 lint-staged 工具來實現。lint-staged
能夠在用戶提交代碼以前(生成 Git Commit Message 信息以前)使用 ESLint 檢查 Git 暫存區中的代碼信息(git add
以後的修改代碼),一旦存在 💩 同樣不符合校驗規則的代碼,則能夠終止提交行爲。須要注意的是 lint-staged
不會檢查項目的全量代碼(全量使用 ESLint 校驗對於較大的項目可能會是一個相對耗時的過程),而只會檢查添加到 Git 暫存區中的代碼。根據官方文檔執行如下命令自動生成配置項信息:
npx mrm lint-staged
複製代碼
須要注意默認生成的配置文件是針對 JavaScript 環境的,手動修改 package.json
中的配置信息進行 TypeScript 適配:
// 咱們的哈士奇再次上場,此次它是要咬着你的 ESLint 不放了,這裏我簡稱它的動做爲 "咬 💩" ~~~
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
// 這裏須要注意 ESLint 腳本的 --max-warnings 0
// 不然就算存在 warning 也不會終止提交行爲
// 這裏追加了 Prettier 的自動格式化,確保代碼提交以前全部的格式可以修復
"*.ts": ["npm run lint", "npm run prettier"]
}
複製代碼
此時若是將要提交的代碼有 💩 , 則提交時會提示錯誤信息且提交會被強制終止:
husky > pre-commit (node v12.13.1)
[STARTED] Preparing...
[SUCCESS] Preparing...
[STARTED] Running tasks...
[STARTED] Running tasks for *.ts
[STARTED] npm run lint-strict
[FAILED] npm run lint-strict [FAILED]
[FAILED] npm run lint-strict [FAILED]
[SUCCESS] Running tasks...
[STARTED] Applying modifications...
[SKIPPED] Skipped because of errors from tasks.
[STARTED] Reverting to original state because of errors...
[SUCCESS] Reverting to original state because of errors...
[STARTED] Cleaning up...
[SUCCESS] Cleaning up...
× npm run lint-strict:
ESLint found too many warnings (maximum: 0).
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! algorithms-utils@1.0.0 lint-strict: `eslint src --max-warnings 0 "C:/Code/Git/algorithms/src/greet.ts"`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the algorithms-utils@1.0.0 lint-strict script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-11T07_25_39_102Z-debug.log
> algorithms-utils@1.0.0 lint-strict C:\Code\Git\algorithms
> eslint src --max-warnings 0 "C:/Code/Git/algorithms/src/greet.ts"
C:\Code\Git\algorithms\src\greet.ts
2:16 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types 2:34 warning Argument 'name' should be typed @typescript-eslint/explicit-module-boundary-types ✖ 2 problems (0 errors, 2 warnings) husky > pre-commit hook failed (add --no-verify to bypass) 複製代碼
husky 在 package.json
中配置了 pre-commit
和 commit-msg
兩個 Git 鉤子,優先使用 pre-commit
鉤子執行 ESLint 校驗,若是校驗失敗則終止運行。若是校驗成功則會繼續執行 commit-msg
校驗 Git Commit Message,例如如下是 ESLint 校驗經過可是 Commit Message 校驗失敗的例子:
PS C:\Code\Git\algorithms> git commit -m "這是一個不符合規範的 Commit Message"
// pre-commit 鉤子 ESLint 校驗經過
husky > pre-commit (node v12.13.1)
[STARTED] Preparing...
[SUCCESS] Preparing...
[STARTED] Running tasks...
[STARTED] Running tasks for *.ts
[STARTED] npm run lint-strict
[SUCCESS] npm run lint-strict
[SUCCESS] Running tasks for *.ts
[SUCCESS] Running tasks...
[STARTED] Applying modifications...
[SUCCESS] Applying modifications...
[STARTED] Cleaning up...
[SUCCESS] Cleaning up...
// commit-msg 鉤子 Git Commit Message 校驗失敗
husky > commit-msg (node v12.13.1)
⧗ input: 這是一個不符合規範的 Commit Message
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky > commit-msg hook failed (add --no-verify to bypass)
複製代碼
若是對於測試的概念和框架不是特別清楚,這裏推薦一些可查看的文章:
除此以外,若是想了解一些額外的測試技巧,這裏推薦一些社區的最佳實踐:
因爲這裏只是 Node 環境工具庫包的單元測試,在對比了各個測試框架以後決定採用 Jest 進行單元測試:
it
到 expect
, Jest 將整個工具包放在一個地方)舒適提示:前端測試框架不少,相比簡單的單元測試,e2e 測試會更復雜一些(無論是測試框架的支持以及測試用例的設計)。以前使用過 Karma 測試管理工具配合 Mocha 進行瀏覽器環境測試,也使用過 PhantomJS 以及 Nightwatch(使用的都是皮毛),印象最深入的是使用 testcafe 測試框架(複雜的 API 官方文檔),除此以外若是還感興趣,也能夠了解一下 cypress 測試框架。
本項目的單元測試主要採用了 Jest 測試框架。Jest 若是須要對 TypeScript 進行支持,能夠經過配合 Babel 的形式,具體可查看 Jest - Using TypeScript,可是採用 Babel 會產生一些限制(具體可查看 Babel 7 or TypeScript)。因爲本項目沒有采用 Babel 進行轉譯,而且但願可以完美支持類型檢查,所以採用 ts-jest 進行單元測試。按照官方教程進行依賴安裝和項目初始化:
npm install --save-dev jest typescript ts-jest @types/jest
npx ts-jest config:init
複製代碼
子啊根目錄的 ject.config.js
文件中進行 Jest 配置修改:
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
// 輸出覆蓋信息文件的目錄
coverageDirectory: "./coverage/",
// 覆蓋信息的忽略文件模式
testPathIgnorePatterns: ["<rootDir>/node_modules/"],
// 若是測試覆蓋率未達到 100%,則測試失敗
// 這裏可用於預防代碼構建和提交
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
// 路徑映射配置,具體可查看 https://kulshekhar.github.io/ts-jest/user/config/#paths-mapping
// 須要配合 TypeScript 路徑映射,具體可查看:https://www.tslang.cn/docs/handbook/module-resolution.html
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
};
複製代碼
須要注意路徑映射也須要配置 tsconfig.json
中的 paths
信息,同時注意將測試代碼包含到 TypeScript 的編譯目錄中。配置完成後在 package.json
中配置測試命令:
"scripts": {
"lint": "eslint src --max-warnings 0",
"test": "jest --bail --coverage",
"build": "npm run lint && npm run jest && rimraf dist types && gulp",
}
複製代碼
須要注意 Jest 中的這些配置信息(更多配置信息可查看 Jest CLI Options):
bail
的配置做用相對相似於 ESLint 中的 max-warnings
,設置爲 true
則代表一旦發現單元測試用例錯誤則中止運行其他測試用例,從而能夠防止運行用例過多時須要一直等待用例所有運行完畢的狀況。coverage
主要用於在當前根目錄下生成 coverage
代碼的測試覆蓋率報告,該報告還能夠上傳 coveralls 進行 Github 項目的 Badges 顯示。舒適提示:Jest CLI Options 中的
findRelatedTests
可用於配合pre-commit
鉤子去運行最少許的單元測試用例,可配合lint-staged
實現相似於 ESLint 的做用,更多細節可查看lint-staged - Use environment variables with linting commands
。
在當前根目錄的 test
目錄下新建 greet.spec.ts
文件,並設計如下測試代碼:
import greet from "@/greet";
describe("src/greet.ts", () => {
it("name param test", () => {
expect(greet("world")).toBe("Hello from world 1");
});
});
複製代碼
舒適提示:測試文件有兩種放置風格,一種是新建
test
文件夾,而後將全部的測試代碼集中在test
目錄下進行管理,另一種是在各個源碼文件的同級目錄下新建__test__
目錄,進行就近測試。大部分的項目可能都會傾向於採用第一種目錄結構(能夠隨便找一些 github 上的開源項目進行查看,這裏ts-test
則是採用了第二種測試結構)。除此以外,須要注意 Jest 經過配置testMatch
或testRegex
可使得項目識別特定格式文件做爲測試文件進行運行(本項目採用默認配置可識別後綴爲.spec
的文件進行單元測試)。
單獨經過執行 npm run test
命令進行單元測試,這裏演示執行構建命令時的單元測試(須要保證構建以前全部的單元測試用例都能經過)。若是測試失敗,那麼應該防止繼續構建,例如進行失敗的構建行爲:
PS C:\Code\Git\algorithms> npm run build
> algorithms-utils@1.0.0 build C:\Code\Git\algorithms
> npm run lint-strict && npm run jest && rimraf dist types && gulp
> algorithms-utils@1.0.0 lint-strict C:\Code\Git\algorithms
> eslint src --max-warnings 0
> algorithms-utils@1.0.0 jest C:\Code\Git\algorithms
> jest --coverage
PASS dist/test/greet.spec.js
FAIL test/greet.spec.ts
● src/greet.ts › name param test
expect(received).toBe(expected) // Object.is equality
Expected: "Hello from world 1"
Received: "Hello from world"
3 | describe("src/greet.ts", () => {
4 | it("name param test", () => {
> 5 | expect(greet("world")).toBe("Hello from world 1");
| ^
6 | });
7 | });
8 |
at Object.<anonymous> (test/greet.spec.ts:5:28)
----------|---------|----------|---------|---------|-------------------
| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
| ---------- | --------- | ---------- | --------- | --------- | ------------------- |
| All files | 100 | 100 | 100 | 100 |
| greet.ts | 100 | 100 | 100 | 100 |
| ---------- | --------- | ---------- | --------- | --------- | ------------------- |
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 3.45 s
Ran all test suites.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! algorithms-utils@1.0.0 jest: `jest --coverage`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the algorithms-utils@1.0.0 jest script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-12T13_42_11_628Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! algorithms-utils@1.0.0 build: `npm run lint-strict && npm run jest && rimraf dist types && gulp`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the algorithms-utils@1.0.0 build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-12T13_42_11_673Z-debug.log
複製代碼
須要注意因爲是並行(&&
)執行腳本,所以執行構建命令時(npm run build
)會先執行 ESLint 校驗,若是 ESLint 校驗失敗那麼退出構建,不然繼續進行 Jest 單元測試。若是單元測試失敗那麼退出構建,只有當二者都經過時纔會進行源碼構建。
除了預防不負責任的代碼構建之外,還須要預防不負責任的代碼提交。配合 lint-staged
能夠防止未跑通單元測試的代碼進行遠程提交:
"scripts": {
"lint": "eslint src --max-warnings 0",
"test": "jest --bail --coverage",
},
"lint-staged": {
"*.ts": [
"npm run lint",
"npm run test"
]
}
複製代碼
此時若是單元測試有誤,都會中止代碼提交:
husky > pre-commit (node v12.13.1)
[STARTED] Preparing...
[SUCCESS] Preparing...
[STARTED] Running tasks...
[STARTED] Running tasks for *.ts
[STARTED] npm run lint
[SUCCESS] npm run lint
[STARTED] npm run jest
[FAILED] npm run jest [FAILED]
[FAILED] npm run jest [FAILED]
[SUCCESS] Running tasks...
[STARTED] Applying modifications...
[SKIPPED] Skipped because of errors from tasks.
[STARTED] Reverting to original state because of errors...
[SUCCESS] Reverting to original state because of errors...
[STARTED] Cleaning up...
[SUCCESS] Cleaning up...
× npm run jest:
FAIL test/greet.spec.ts
src/greet.ts
× name param test (4 ms)
● src/greet.ts › name param test
expect(received).toBe(expected) // Object.is equality
Expected: "Hello from world 1"
Received: "Hello from world"
3 | describe("src/greet.ts", () => {
4 | it("name param test", () => {
> 5 | expect(greet("world")).toBe("Hello from world 1");
| ^
6 | });
7 | });
8 |
at Object.<anonymous> (test/greet.spec.ts:5:28)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 1.339 s, estimated 3 s
Ran all test suites related to files matching /C:\\Code\\Git\\algorithms\\src\\index.ts|C:\\Code\\Git\\algorithms\\test\\greet.spec.ts/i.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! algorithms-utils@1.0.0 jest: `jest --bail --findRelatedTests --coverage "C:/Code/Git/algorithms/src/index.ts" "C:/Code/Git/algorithms/test/greet.spec.ts"`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the algorithms-utils@1.0.0 jest script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-12T14_33_51_183Z-debug.log
> algorithms-utils@1.0.0 jest C:\Code\Git\algorithms
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the algorithms-utils@1.0.0 jest script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-12T14_33_51_183Z-debug.log
> algorithms-utils@1.0.0 jest C:\Code\Git\algorithms
> jest --bail --findRelatedTests --coverage "C:/Code/Git/algorithms/src/index.ts" "C:/Code/Git/algorithms/test/greet.spec.ts"
----------|---------|----------|---------|---------|-------------------
| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
| ---------- | --------- | ---------- | --------- | --------- | ------------------- |
| All files | 0 | 0 | 0 | 0 |
| ---------- | --------- | ---------- | --------- | --------- | ------------------- |
husky > pre-commit hook failed (add --no-verify to bypass)
git exited with error code 1
複製代碼
舒適提示:想要了解更多關於 Jest 的生態能夠查看 awesome-jest。
src
目錄下的源碼經過配置 @typescript-eslint/eslint-plugin
可進行推薦規則的 ESLint 校驗,爲了使得 test
目錄下的測試代碼可以進行符合 Jest 推薦規則的 ESLint 校驗,能夠經過配置 eslint-plugin-jest 進行支持(ts-jest
項目就是採用了該插件進行 ESLint 校驗,具體可查看配置文件 ts-jest/.eslintrc.js
),這裏仍然採用推薦的規則配置:
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
// 新增推薦的 ESLint 校驗規則
// 全部規則集查看:https://github.com/jest-community/eslint-plugin-jest#rules(recommended 標識代表是推薦規則)
"plugin:jest/recommended",
],
};
複製代碼
爲了驗證推薦規則是否生效,這裏能夠找一個 no-identical-title
規則進行驗證:
import greet from "@/greet";
describe("src/greet.ts", () => {
it("name param test", () => {
expect(greet("world")).toBe("Hello from world 1");
});
});
// 這裏輸入了重複的 title
describe("src/greet.ts", () => {
it("name param test", () => {
expect(greet("world")).toBe("Hello from world 1");
});
});
複製代碼
須要注意修改 package.json
中的 ESLint 校驗範圍:
"scripts": {
// 這裏對 src 和 test 目錄進行 ESLint 校驗
"lint": "eslint src test --max-warnings 0",
},
複製代碼
執行 npm run lint
進行單元測試的格式校驗:
PS C:\Code\Git\algorithms> npm run lint
> algorithms-utils@1.0.0 lint C:\Code\Git\algorithms
> eslint src test --max-warnings 0
C:\Code\Git\algorithms\test\greet.spec.ts
9:10 error Describe block title is used multiple times in the same describe block jest/no-identical-title
✖ 1 problem (1 error, 0 warnings)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! algorithms-utils@1.0.0 lint: `eslint src test --max-warnings 0`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the algorithms-utils@1.0.0 lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-13T02_25_12_043Z-debug.log
複製代碼
此時會發現 ESLint 拋出了相應的錯誤信息。須要注意採用此 ESLint 校驗以後也會在 VS Code 中實時生成錯誤提示(相應的代碼下會有紅色波浪線,鼠標移入後會產生 Tooltip 提示該錯誤的相應規則信息,除此以外當前工程目錄下對應的文件名也會變成紅色),此後的 Git 提交以及 Build 構建都會失敗!
舒適提示:若是你但願 Jest 測試的代碼須要一些格式規範,那麼能夠查看 eslint-plugin-jest-formatting 插件。
當你查看前端開源項目時第一時間可能會找 package.json
中的 main
、bin
以及 files
等字段信息,除此以外若是還想深刻了解項目的結構,可能還會查看 scripts
腳本字段信息用於瞭解項目的開發、構建、測試以及安裝等流程。npm 的腳本功能很是強大,你能夠利用腳本製做項目須要的任何流程工具。本文不會過多介紹 npm 腳本的功能,只是講解一下其中用到的 鉤子 功能。
目前在本項目中使用的一些腳本命令以下(就目前而言腳本相對較少,定義還蠻清晰的):
"lint": "eslint src test --max-warnings 0",
"test": "jest --bail --coverage",
"build": "npm run lint && npm run prettier && npm run test && rimraf dist types && gulp",
"changelog": "rimraf CHANGELOG.md && conventional-changelog -p angular -i CHANGELOG.md -s"
複製代碼
重點看下 build
腳本命令,會發現這個腳本命令包含了大量的繼發執行腳本,但真正和 build
相關的只有 rimraf dist types && gulp
這兩個腳本。這裏經過 npm 的腳本鉤子 pre
和 post
將腳本的功能區分開,從而使腳本的語義更加清晰(固然腳本愈來愈多的時候也可能容易增長開發者的認知負擔)。npm 除了指定一些特殊的腳本鉤子之外(例如 prepublish
、postpublish
、preinstall
、postinstall
等),還能夠對任意腳本增長 pre
和 post
鉤子,這裏經過自定義鉤子將併發執行的腳本進行簡化:
"lint": "eslint src test --max-warnings 0",
"test": "jest --bail --coverage",
"prebuild": "npm run lint && npm run prettier && npm run test",
"build": "rimraf dist types && gulp",
"changelog": "rimraf CHANGELOG.md && conventional-changelog -p angular -i CHANGELOG.md -s"
複製代碼
此時若是執行 npm run build
命令時事實上相似於執行了如下命令:
npm run prebuild && npm run build
複製代碼
以後設計的腳本若是繼發執行繁多,那麼都會採用 npm scripts hook 進行設計。
舒適提示:你們可能會奇怪什麼地方須要相似於
preinstall
或preuninstall
這樣的鉤子,例如查看 husky - package.json,husky 在安裝的時候由於要植入 Git Hook 腳本從而帶來了一些反作用(此時固然能夠經過preinstall
觸發 Git Hook 腳本植入的邏輯)。若是不想使用 husky,那麼卸載後須要清除植入的腳本從而不妨礙原有的 Git Hook 功能。 固然若是想要了解更多關於 npm 腳本的信息,能夠查看 npm-scripts 或 npm scripts 使用指南。
通常組件庫或工具庫都須要設計一個演示文檔(提供良好的開發體驗)。通常的工具庫能夠採用 tsdoc、jsdoc 或 esdoc 等工具進行 API 文檔的自動生成,但每每須要符合一些註釋規範,這些註釋規範在某種程度上可能會帶來開發負擔,固然也能夠交給 VS Code 的插件進行一鍵生成,例如 Document This For jsdoc 或 TSDoc Comment。
組件庫 Element UI 採用 vue-markdown-loader(Convert Markdown file to Vue Component using markdown-it) 進行組件的 Demo 演示設計,可是配置相對複雜。更簡單的方式是配合 Vuepress 進行設計,它的功能很是強大,但前提是熟悉 Vue,由於能夠在 Markdown 中使用 Vue 語法。固然若是是 React 組件庫的 Demo 演示,則能夠採用 dumi 生成組件 Demo 演示文檔(不知道沒有更加好用的類 Vuepress 的 React 組件文檔生成器, 更多和 React 文檔相關也能夠了解 react-markdown、react-static 等)。
因爲以前採用過 Vuepress 設計 Vue 組件庫的 Demo 演示文檔,所以這裏仍然沿用它來設計工具庫包的 API 文檔(若是你想自動生成 API 文檔,也能夠額外配合 tsdoc 工具)。採用 Vuepress 設計文檔的主要特色以下:
先按照官方的 快速上手 文檔進行依賴安裝和 npm scripts 腳本設置:
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
複製代碼
按照 Vuepress 官網約定優於配置的原則進行演示文檔的目錄結構設計,官方的文檔可能一會兒難以理解,能夠先設計一個最簡單的目錄:
.
├── docs
│ ├── .vuepress
│ │ └── config.js # 配置文件
│ └── README.md # 文檔首頁
└── package.json
複製代碼
根據默認主題 / 首頁在 docs/README.md
進行首頁設計:
---
home: true
# heroImage: /hero.png
heroText: algorithms-utils
tagline: 算法與 TypeScript 實現
actionText: 開始學習
actionLink: /guide/
features:
- title: 精簡理論
details: 精簡《算法導論》的內容,幫助本身更容易學習算法理論知識。
- title: 習題練習
details: 解答《算法導論》的習題,幫助本身更好的實踐算法理論知識。
- title: 面題精選
details: 蒐集常見的面試題目,提高本身的算法編程能力以及面試經過率。
footer: MIT Licensed | Copyright © 2020-present 子弈
---
複製代碼
根據配置 對 docs/.vuepress/config.js
文件進行基本配置:
const packageJson = require("../../package.json");
module.exports = {
// 配置網站標題
title: packageJson.name,
// 配置網站描述
description: packageJson.description,
// 配置基本路徑
base: "/algorithms/",
// 配置基本端口
port: "8080",
};
複製代碼
此時經過 npm run docs:dev
進行開發態文檔預覽:
PS C:\Code\Git\algorithms> npm run docs:dev
> algorithms-utils@1.0.0 docs:dev C:\Code\Git\algorithms
> vuepress dev docs
wait Extracting site metadata...
tip Apply theme @vuepress/theme-default ...
tip Apply plugin container (i.e. "vuepress-plugin-container") ...
tip Apply plugin @vuepress/register-components (i.e. "@vuepress/plugin-register-components") ...
tip Apply plugin @vuepress/active-header-links (i.e. "@vuepress/plugin-active-header-links") ...
tip Apply plugin @vuepress/search (i.e. "@vuepress/plugin-search") ...
tip Apply plugin @vuepress/nprogress (i.e. "@vuepress/plugin-nprogress") ...
√ Client
Compiled successfully in 5.31s
i 「wds」: Project is running at http://0.0.0.0:8080/
i 「wds」: webpack output is served from /algorithms-utils/
i 「wds」: Content not from webpack is served from C:\Code\Git\algorithms\docs\.vuepress\public
i 「wds」: 404s will fallback to /index.html
success [23:13:14] Build 10b15a finished in 5311 ms!
> VuePress dev server listening at http://localhost:8080/algorithms-utils/
複製代碼
效果以下:
固然除了以上設計的首頁,在本項目中還會設計導航欄、側邊欄、使用插件、使用組件等。這裏重點講解一下 Webpack 構建 配置。
爲了在 Markdown 文檔中可使用 src
目錄的 TypeScript 代碼,這裏對 .vuepress/config.js
文件進行配置處理:
const packageJson = require("../../package.json");
const sidebar = require("./config/sidebar.js");
const nav = require("./config/nav.js");
const path = require("path");
module.exports = {
title: packageJson.name,
description: packageJson.description,
base: "/algorithms/",
port: "8080",
themeConfig: {
nav,
sidebar,
},
plugins: [
"vuepress-plugin-cat",
[
"mathjax",
{
target: "svg",
macros: {
"*": "\\times",
},
},
],
// 增長 Markdown 文檔對於 TypeScript 語法的支持
[
"vuepress-plugin-typescript",
{
tsLoaderOptions: {
// ts-loader 的全部配置項
},
},
],
],
chainWebpack: (config) => {
config.resolve.alias.set("image", path.resolve(__dirname, "public"));
// 在文檔中模擬庫包的引入方式
// 例如發佈了 algorithms-utils 庫包以後,
// import greet from 'algorithms-utils/greet.ts' 在 Vuepress 演示文檔中等同於
// import greet from '~/src/greet.ts',
// 其中 ~ 在這裏只是表示項目根目錄
config.resolve.alias.set(
"algorithms-utils",
path.resolve(__dirname, "../../src")
);
},
};
複製代碼
舒適提示:這裏的 Webpack 配置採用了 webpack-chain 鏈式操做,若是想要採用 Webpack 對象的配置方式則能夠查看 Vuepress - 構建流程 - configurewebpack。
此時能夠在 Vuepress 的 Markdown 文檔中進行 TypeScript 引入的演示文檔設計:
# Test vuepress
::: danger 測試 Vuepress
引入 greet.ts 並進行調用測試。
:::
<template> <collapse title="查看答案">{{msg}}</collapse> </template>
<template> <div>{{msg}}</div> </template>
<script lang="ts"> import greet from 'algorithms-utils/greet' const msg = greet('ziyi') export default { data() { return { msg } }, } </script>
複製代碼
啓動 Vuepress 查看演示文檔:
能夠發如今 Markdown 中引入的 src/greet.ts
代碼生效了,最終經過 npm run docs:build
能夠生成演示文檔的靜態資源進行部署和訪問。
舒適提示:更多本項目的 Vuepress 配置信息可查看 Commit 信息,除此以外若是還想知道更多 Vuepress 的生態,例若有哪些有趣插件或主題,可查看 awesome-vuepress 或 Vuepress 社區。
一般在書寫文檔的時候不少同窗都不注重文檔的潔癖,其實書寫文檔和書寫代碼同樣須要一些格式規範。markdownlint 是相似於 ESLint 的 Markdown 格式校驗工具,經過它能夠更好的規範咱們書寫的文檔。固然 Markdown 的格式校驗不須要像 ESLint 或者 Prettier 校驗那樣進行強約束,簡單的可以作到提示和 Save Auto Fix 便可。
經過安裝 Vs Code 插件 markdownlint 並進行 Save Auto Fix 配置(在插件中明確列出了哪些規則是能夠被 Fix 的)。安裝完成後查看剛剛進行的測試文件:
此時會發現插件生效了,可是在 Markdown 中插入 html 是必須的一個能力(Vuepress 支持的能力就是在 Markdown 中使用 Vue),所以能夠經過 .markdownlintrc
文件將相應的規則屏蔽掉。
舒適提示:若是你但願在代碼提交以前或文檔構建以前可以進行 Markdown 格式校驗,則能夠嘗試它的命令行接口 markdownlint-cli。除此以外,若是對文檔的設計沒有想法或者不清楚如何書寫好的技術文檔,能夠查看 技術文章的寫做技巧分享,必定能讓你有所收穫。
前提提示:我的對於 CI / CD 可能相對不夠熟悉,只是簡單的玩過 Travis、Gitlab CI / CD 以及 Jenkins。
關於 CI / CD 的背景這裏就再也不過多介紹,有興趣的同窗能夠看看如下一些好文:
在 Introduction to CI/CD with GitLab(中文版) 中你能夠清晰的瞭解到 CI 和 CD 的職責功能:
經過如下圖能夠更清晰的發現 Gitlab 在每一個階段可用的功能:
因爲本項目依賴 Github,所以無法使用 Gitlab 默認集成的能力。以前的 Github 項目採用了 Travis 進行項目的 CI / CD 集成,如今由於有了更方便的 Github Actions,所以決定採用 Github 自帶的 Actions 進行 CI / CD 能力集成(你們若是想更多瞭解這些 CI / CD 的差別請自行 Google 哈)。Github Actions 所帶來的好處在於:
固然也會產生一些限制,這些限制主要是和執行時間以及次數相關。須要注意相似於 Jenkins 等支持本地鏈接運行,Github Actions 也支持鏈接到本地機器運行 workflow,所以部分限制可能不受本地運行的限制。
舒適提示:本項目中使用到的 CI / CD 功能相對簡單,若是想了解更多通用的 Actions,可查看 官方 Actions 和 awesome-actions。最近在使用 Jenkins 作前端的自動化構建優化,後續可能會出一篇簡單的教程文章(固然會跟普通講解的用法會有所不一樣嘍)。
本項目的配置可能會包含如下三個方面:
這裏主要講解自動更新靜態資源流程,大體須要分爲如下幾個步驟(如下都是在 Github 服務器上進行操做,你能夠理解爲新的服務環境):
經過查看 官方 Actions 和 awesome-actions,找到所需的 Actions:
$GITHUB_WORKSPACE
目錄下舒適提示:可用的 Action 不少,這裏只是設置了一個簡單的流程。
在 .github/workflows
下新增 mian.yml
配置文件:
# 如下都是官方文檔的簡單翻譯
# 當前的 yml(.yaml) 文件是一個 workflow,是持續集成一次運行的一個過程,必須放置在項目的 .github/workflow 目錄下
# 若是不清楚 .yml 文件格式語法,能夠查看 https://www.codeproject.com/Articles/1214409/Learn-YAML-in-five-minutes
# 初次編寫不免會產生格式問題,可使用 VS Code 插件進行格式檢測,https://marketplace.visualstudio.com/items?itemName=OmarTawfik.github-actions-vscode
# 具體各個配置屬性可查看 https: //docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
# workflow 的執行仍然會受到一些限制,例如
# - 每一個 job 最多執行 6 小時(本地機器不受限制)
# - 每一個 workflow 最多執行 72 小時
# - 併發 job 的數量會受到限制
# - 更多查看 https: //docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#usage-limits
# name: 當前 workflow 的名稱
name: Algorithms
# on: 指定 workflow 觸發的 event
#
# event 有如下幾種類型
# - webhook
# - scheduled
# - manual
on:
# push: 一個 webhook event,用於提交代碼時觸發 workflow,也能夠是觸發列表,例如 [push, pull_request]
# workflows 觸發的 event 大部分是基於 webhook 配置,如下列舉幾個常見的 webhook:
# - delete: 刪除一個 branch 或 tag 時觸發
# - fork / watch: 某人 fork / watch 項目時觸發(你問有什麼用,發送郵件通知不香嗎?)
# - pull_request: 提交 PR 時觸發
# - page_build: 提交 Github Pages-enabled 分支代碼時觸發
# - push: 提交代碼到特定分支時觸發
# - registry_package: 發佈或跟新 package 時觸發
# 更多 webhook 可查看 https: //docs.github.com/en/actions/reference/events-that-trigger-workflows
# 從這裏能夠看出 Git Actions 的一大特色就是 Gihub 官方提供的一系列 webhook
push:
# branches: 指定 push 觸發的特定分支,這裏你能夠經過列表的形式指定多個分支
branches:
- feat/framework
#
# branches 的指定能夠是通配符類型,例如如下配置能夠匹配 refs/heads/releases/10
# - 'releases/**'
#
# branches 也可使用反向匹配,例如如下不會匹配 refs/heads/releases/10
# - '!releases/**'
#
# branches-ignore: 只對 [push, pull_request] 兩個 webhook 起做用,用於指定當前 webhook 不觸發的分支
# 須要注意在同一個 webhook 中不能和 branches 同時使用
#
# tags: 只對 [push, pull_request] 兩個 webhook 起做用,用於指定當前 webhook 觸發的 tag
#
# tags:
# - v1 # Push events to v1 tag
# - v1.* # Push events to v1.0, v1.1, and v1.9 tags
#
# tags-ignore: 相似於 branches-ignore
#
# paths、paths-ignore...
#
# 更多關於特定過濾模式可查看 https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
#
# 其餘的 webhook 控制項還包括 types(不是全部的 webhook 都有 types),例如已 issues 爲例,能夠在 issues 被 open、reopened、closed 等狀況下觸發 workflow
# 更多 webhook 的 types 可查看 https: //docs.github.com/en/actions/reference/events-that-trigger-workflows#webhook-events
#
# on:
# issues:
# types: [opened, edited, closed]
# 除此以外若是對於每一個分支有不一樣的 webhook 觸發,則能夠經過如下形式進行多個 webhook 配置
#
# push:
# branches:
# - master
# pull_request:
# branches:
# - dev
#
# 除了以上所說的 webhook event,還有 scheduled event 和 manual event
# scheduled event: 用於定時構建,例如最小的時間間隔是 5 min 構建一次
# 具體可查看 https: //docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events
# env: 指定環境變量(全部的 job 生效,每個 job 能夠獨立經過 jobs.<job_id>.env、jobs.<job_id>.steps.env 配置)
# defaults / defaults.run: 全部的 job 生效,每個 job 能夠獨立經過 jobs.<job_id>.defaults 配置
# deafults
# defaults.run
# jobs: 一個 workflow 由一個或多個 job 組成
jobs:
# job id: 是 job 的惟一標識,能夠經過 _ 進行鏈接,例如: my_first_job,例如這裏的 build 就是一個 job id
build_and_deploy:
# name: 在 Github 中顯示的 job 名稱
name: Build And Deploy
#
# needs: 用於繼發執行 job,例如當前 job build 必須在 job1 和 job2 都執行成功的基礎上執行
# needs: [job1, job2]
#
# runs-on: job 運行的環境配置,包括:
# - windows-latest
# - windows-2019
# - ubuntu-20.04
# - ubuntu-latest
# - ubuntu-18.04
# - ubuntu-16.04
# - macos-latest
# - macos-10.15
# - self-hosted(本地機器,具體可查看 https: //docs.github.com/en/actions/hosting-your-own-runners/using-self-hosted-runners-in-a-workflow)
runs-on: ubuntu-latest
#
# outputs: 用於輸出信息
#
# env: 用於設置環境變量
#
# defaults: 當前全部 step 的默認配置
#
# defaults.run
# if: 知足條件執行當前 job
# steps: 一個 job 由多個 step 組成,step 能夠
# - 執行一系列 tasks
# - 執行命令
# - 執行 action
# - 執行公共的 repository
# - 在 Docker registry 中的 action
steps:
#
# id: 相似於 job id
#
# if: 相似於 job if
#
# name: 當前 step 的名字
- name: Checkout
#
# uses: 用於執行 action
#
# action: 能夠重複使用的單元代碼
# - 爲了 workflow 的安全和穩定建議指定 action 的發佈版本或 commit SHA
# - 使用指定 action 的 major 版本,這樣能夠容許你接收 fixs 以及 安全補丁並同時保持兼容性
# - 儘可能不建議使用 master 版本,由於 master 頗有可能會被髮布新的 major 版本從而破壞了 action 的兼容性
# - action 多是 JavaScript 文件或 Docker 容器,若是是 Docker 容器,那麼 runs-on 必須指定 Linux 環境
#
# 指定固定 commit SHA
# uses: actions/setup-node@74bc508
# 指定一個 major 發佈版本
# uses: actions/setup-node@v1
# 指定一個 minor 發佈版本
# uses: actions/setup-node@v1.2
# 指定一個分支
# uses: actions/setup-node@master
# 指定一個 Github 倉庫子目錄的特定分支、ref 或 SHA
# uses: actions/aws/ec2@master
# 指定當前倉庫所在 workflows 的目錄地址
# uses: ./.github/actions/my-action
# 指定在 Dock Hub 發佈的 Docker 鏡像地址
# uses: docker: //alpine: 3.8
# A Docker image in a public registry
# uses: docker: //gcr.io/cloud-builders/gradle
# checkout action 主要用於向 github 倉庫拉取源代碼(須要注意 workflow 是運行在服務器上,所以須要向當前 github 拉取倉庫源代碼)
# 它的功能包括但不限於
# - Fetch all history for all tags and branches
# - Checkout a different branch
# - Checkout HEAD^
# - Checkout multiple repos (side by side)
# - Checkout multiple repos (nested)
# - Checkout multiple repos (private)
# - Checkout pull request HEAD commit instead of merge commit
# - Checkout pull request on closed event
# - Push a commit using the built-in token
# checkout action: https: //github.com/actions/checkout
uses: actions/checkout@v2
# with: action 提供的輸入參數
with:
# 指定 checkout 的分支、tag 或 SHA
# 更多 checkout action 的配置可查看 https: //github.com/actions/checkout#usage
ref: feat/ci
# args: 用於 Docker 容器的 CMD 指令參數
# entrypoint: Docker 容器 action(覆蓋 Dockerfile 的 ENTRYPOINT) 和 JavaScript action 均可以使用
#
# run: 使用當前的操做系統的默認的 non-login shell 執行命令行程序
# 運行單個腳本
# run: npm install
# 運行多個腳本
# run: |
# npm ci
# npm run build
#
# working-directory: 用於指定當前腳本運行的目錄
# working-directory: ./temp
#
# shell: 能夠指定 shell 類型進行執行,例如 bash、pwsh、python、sh、cmd、powershell
# shell: bash
#
# env: 除了能夠設置 workflow 以及 job 的 env,也能夠設置 step 的 env(能夠理解爲做用域不一樣,局部做用域的優先級更高)
#
# comtinue-on-error: 默認當前 step 失敗則會阻止當前 job 繼續執行,設置 true 時當前 step 失敗則能夠跳過當前 job 的執行
- name: Cache
# cache action: https://github.com/actions/cache
# cache 在這裏主要用於緩存 npm,提高構建速率
uses: actions/cache@v2
# npm 緩存的路徑可查看 https://docs.npmjs.com/cli/cache#cache
# 因爲這裏 runs-on 是 ubuntu-latest,所以配置 ~/.npm
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: | ${{ runner.os }}-node- # github-script action: https://github.com/actions/github-script
# 在 workflow 中使用 Script 語法調用 Github API 或引用 workflow context
# setup-node action: https://github.com/actions/setup-node
# 配置 Node 執行環境(當前構建的服務器默認沒有 Node 環境,能夠經過 Action 安裝 Node)
# 須要注意安裝 Node 的同時會捆綁安裝 npm,若是想了解爲何會捆綁,能夠 Google 一下有趣的故事哦
# 所以使用了該 action 後就可使用 npm 的腳本在服務器進行執行啦
# 這裏也能夠嘗試 v2-beta 版本哦
- name: Set Node
uses: actions/setup-node@v1
with:
# 也能夠經過 strategy.matrix.node 進行靈活配置
# 這裏本地使用 node 的 12 版本構建,所以這裏就進行版本固定啦
node-version: "12"
- run: npm install
- run: npm run build
- run: npm run docs:build
- name: Deploy
# 用於發佈靜態站點資源
# actions-gh-pages action: https://github.com/peaceiris/actions-gh-pages
uses: peaceiris/actions-gh-pages@v3
with:
# GTIHUB_TOKEN:https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
# Github 會在 workflow 中自動生成 GIHUBT_TOKEN,用於認證 workflow 的運行
github_token: ${{ secrets.GITHUB_TOKEN }}
# 靜態資源目錄設置
publish_dir: ./docs/.vuepress/dist
# 默認發佈到 gh-pages 分支上,能夠指定特定的發佈分支
publish_branch: gh-pages1 # default: gh-pages
full_commit_message: ${{ github.event.head_commit.message }}
#
# timeout-minutes: 一個 job 執行的最大時間,默認是 6h,若是超過期間則取消執行
#
# strategy.matrix: 例如指定當前 job 的 node 版本列表、操做系統類型列表等
# strategy.fail-fast
# strategy.max-parallel
# continue-on-error: 一旦當前 job 執行失敗,那麼 workflow 中止執行。設置爲 true 能夠跳過當前 job 執行
# container: Docker 容器配置,包括 image、env、ports、volumes、options 等配置
#
# services: 使用 Docker 容器 Action 或者 服務 Action 必須使用 Linux 環境運行
複製代碼
舒適提示:這裏再也不敘述具體的配置過程,更多可查看配置文件中貼出的連接信息。
上傳 CI 的配置文件後,Github 就會進行自動構建,具體以下:
正在構建或者構建完成後可查看每一個構建的信息,若是初次構建失敗則能夠經過構建信息找出失敗緣由,並從新修改構建配置嘗試再次構建。除此以外,每次構建失敗 Github 都會經過郵件的形式進行通知:
若是構建成功,則每次你推送新的代碼後,Github 服務會進行一系列流程並自動更新靜態資源站點。
但願你們看完這篇文檔以後若是想使用其中某些工具可以養成如下一些習慣:
通篇閱讀工具對應的官方 Github README 文檔以及官方站點文檔,瞭解該工具設計的核心哲學、核心功能、解決什麼核心問題。前端的工具百花齊放,一樣的功能可能能夠採用多種不一樣的工具實現。若是想要在項目中使用適當的工具,就得知道這些工具的差別。完整的閱讀相應的官方文檔,有助於你理解各自的核心功能和差別。
在實踐的過程當中你會對該工具的使用愈來愈熟悉。此時若是遇到一些問題或者想要實現某些功能,在通篇閱讀文檔的基礎上會變得相對容易。固然若是遇到一些報錯信息沒法解決,此時第一時間應該是搜索當前工具所對應的 Github Issues。除此以外,你也能夠根據錯誤的堆棧信息追蹤工具的源碼,瞭解源碼以後可能會對錯誤信息產生的緣由更加清晰。