寫在前面
早年間有幸在Raychee哥門下當小弟,學到兩把刷子。在編程路上,他的很多思想深深影響了我,比如筆者今天要分享的主題。在程序開發中,有個utils
包,叫做實用程序包,程序員們會把項目中通用的東西抽離出來放到這個里面,這有利於項目工程化的落地,提高項目的可維護性,減少代碼冗余,鍛煉編碼能力,提高編碼效率,理解編程思想。
在開始之前,我們先思考下,創建一個規范的項目我們需要關注哪些點?我覺得吧,第一個是創建信息的完整性,一個信息完整的項目可以引導讀者與作者交流與合作,這個在后面的package.json
里面向大家介紹;第二個是代碼的規范性和兼容性,正所謂,沒有規矩不成方圓,良好的代碼規范會巧妙地杜絕屎上雕花的行為發生,這個后面跟大家介紹下eslint+prettier+babal
的知識點;第三個項目目錄的規范性,這個也會在后面介紹。基於前面三點,我們可以做出一個自產自銷的項目,如果把這個過程比作拉翔,那它會很通暢,拉的很舒服,不會便秘。但是遠遠不夠,就比如程序員A拿到程序員B寫的項目,那么程序員B怎么去證明給A看,我的槍好使且我的話可信。這就引入了后面兩個話題,第四點就是把你的作品發出去讓別人能看得到,《何以笙簫默》中有句台詞,“如果我們走散你找不到我,那我就站在最高的舞台中央讓你看見我。”男同胞們聽懂了嗎?你想要脫單,一個不成熟的建議,站在舞台中央,發出滋滋滋的求偶聲,跳出Michael Jackson
妖嬈的舞步,just beat it
,just beat it
.喜歡你的說不定就有了,主動一點就會有故事。這個后面筆者介紹下git
工作流以及npm
的發包;第五點就是測試,提高可信度。這里我會結合karma
、mocha
、chai
、travis
、codecov
來向大家介紹單元測試、持續集成、代碼覆蓋率測試。最后的話,我會結合相關的開發工具做一個簡單的搭配使用介紹吧。好的,我們開始吧。
項目創建
注意: 因為筆者目前前端接觸的比較多,所以這個庫的定義就是給前端環境用的,不是很推薦用在nodejs開發上使用,因為其后面涉及到了一些DOM之類的操作是對nodjs沒什么卵用的,所以采用ES Module
的語法來書寫,若想在node環境使用,請配合babel
,webpack
等工具使用,請確保電腦上安裝了nodejs
環境。
舉個例子,比如我要創建一個項目叫utils
,可以怎么做?(考慮到0基礎的同學,我會講的比較細,老司機請直接跳過這章節)
如果你只是想玩玩,不想一步一步去配置,那么你只需要執行mkdir utils && npm init -y
, 這句話的意思是說創建了一個文件夾叫utils
,然后初始化一個npm
管理的項目,-y
表示yes
,也就是都選是。這個時候它就會在項目文件夾下創建一個粗糙的package.json
文件。
新手我還是建議你一步一個腳印走一遍,執行mkdir utils && npm init
,它會一步一步讓你確認該項目的相關描述啊,協議啊,聯系方式啊,項目地址啥的,這里筆者貼出一份該項目的npm配置。
配置說明:
name
: 項目名version
: 項目版本號description
: 項目描述main
: 項目主入口文件scripts
: 項目執行npm命令repository
: 項目倉庫keywords
: 項目關鍵詞author
: 項目作者license
: 授權協議bugs
: bug反饋homepages
: 項目主頁devDependencies
: 開發環境依賴,不會隨項目打包, 使用npm i @ataola/utils -D
安裝dependencies
: 開發環境依賴,會隨着項目打包,使用npm i @ataola/utils -S
安裝husky
: 在本地提交之前,做一次lint反饋,這個需要安裝相關npm包再配置lint-staged
: 只會校驗提交修改的部分,這個也是需要安裝相關npm包再配置,建議你和樓上那位一起用
{
"name": "@ataola/utils",
"version": "0.1.5",
"description": "ataola's utils: maybe publish a feature one week, to record something i think or meet.",
"main": "index.js",
"scripts": {
"push": "./push",
"pull": "./pull",
"codecov": "codecov",
"eslint": "eslint . --ext .js --fix",
"husky:prepare": "husky install",
"husky:add": "husky add .husky/pre-commit 'npm run lint'",
"git:add": "git add -A",
"lint": "lint-staged",
"karma:init": "karma init ./karma.conf.js",
"karma:test": "karma start ./karma.conf.js",
"format": "prettier --write '**/*.{js,jsx,ts,tsx,json,md}'"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ataola/utils.git"
},
"keywords": [
"javascript",
"utils"
],
"author": "ataola (zjt613@gmail.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/ataola/utils/issues"
},
"homepage": "https://github.com/ataola/utils#readme",
"devDependencies": {
"@babel/core": "^7.13.15",
"@babel/eslint-parser": "^7.13.14",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-transform-arrow-functions": "^7.13.0",
"@babel/plugin-transform-async-to-generator": "^7.13.0",
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.13.15",
"@babel/runtime": "^7.13.10",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-istanbul": "^6.0.0",
"chai": "^4.3.4",
"codecov": "^3.8.1",
"core-js": "^3.11.0",
"eslint": "^7.24.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"husky": "^6.0.0",
"karma": "^6.3.2",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-webpack": "^5.0.0",
"lint-staged": "^10.5.4",
"mocha": "^8.3.2",
"prettier": "^2.2.1",
"webpack": "^5.31.2"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint . --fix",
"prettier --config .prettierrc --write ."
]
}
}
查詢npm
的相關命令是npm --help
, 比如我不知道npm init
后面可以跟什么,那么執行npm init --help
就可以羅列出相關信息。
➜ ~ npm init --help
npm init [--force|-f|--yes|-y|--scope]
npm init <@scope> (same as `npx <@scope>/create`)
npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)
aliases: create, innit
➜ ~
如果你發現npm install
很慢,多半是長城的問題,建議你改成國內的淘寶源npm install --registry=https://registry.npm.taobao.org
, 如果發現也還是不好使,終極解決方案: 科X學X上X網,逃~。
代碼規范
努力做好六件事:
- 不同編輯器下的代碼規范
- eslint作語法規范
- prettier作格式規范
- 做好代碼兼容性處理
- 手動擋控制單文件格式化
- 提交代碼前確認所修改文件或者整個項目代碼規范
EditorConfig
這個對應我們上面努力做好的第一件事 - 不同編輯器下的代碼規范。在現實多人開發中,由於開發者的行為習慣不同可以會導致代碼的風格有所不同,有些人喜歡用vscode,有些人喜歡用webstorm,也許他們用的編輯器是一樣的,但是由於開發者在全局配置了一些設置,會導致整個項目代碼不符合預期,所以,我們需要一個在編輯器層面去協調各個編輯器環境下的代碼風格,EditorConfig是一個不錯的選擇,這個是本項目用到的關於EditorConfig的一些配置。
配置說明:
root=true
: 表示是最頂層的配置文件,發現設為true時,才會停止查找.editorconfig文件
When opening a file, EditorConfig plugins look for a file named .editorconfig in the directory of the opened file and in every parent directory. A search for .editorconfig files will stop if the root filepath is reached or an EditorConfig file with root=true is found.
EditorConfig files are read top to bottom and the most recent rules found take precedence. Properties from matching EditorConfig sections are applied in the order they were read, so properties in closer files take precedence.
原文地址:https://github.com/editorconfig/editorconfig/issues/376
[*]
: 表示所有文件end_of_line = lf
注意這個不是if
,而是lf
, 表示換行符,它有lf
、crlf
、cr
等等,跟系統關系比較大,反正大家都統一一下用lf
,可以看下這個有名的故事-GitHub 第一坑:換行符自動轉換insert_final_newline = true
: 表示在末尾插入新行[*.{js,py}]
: 表示js和python文件charset = utf-8
: 表示字符集為utf-8
indent_style = space
: 表示代碼鎖進格式用空格indent_size = 2
: 表示一個縮進大小兩個空格quote_type = single
: 字符串設置為單引號trim_trailing_whitespace = true
: 表示是否在行尾修剪空白
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Denotes whether to trim whitespace at the end of lines
trim_trailing_whitespace = true
# Matches multiple files with brace expansion notation
[*.{js}]
charset = utf-8
quote_type = single
indent_style = space
indent_size = 2
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
額,我覺得學這部分是有捷徑的,就是去嫖名項目它們的配置,然后把它們搞懂再應用到自己或者團隊的項目中。比如JQuery, Bootstrap的,跟對項目,做對事可以少走很多彎路的。
ESLint
這個對應第二件事 -eslint作語法規范。eslint用來做一些js語法規范,避免一些語法上的錯誤,當然也可以做格式上的規范。這個是本項目用到的關於eslint的一些配置。
配置說明:
-
extends
: 繼承,表示它繼承了某些配置, 比如eslint:recommended
表示繼承了其推薦的配置,可以繼承多個的,用數組表示 -
plugins
: 表示安裝的插件, 寫配置的時候可以省略前面的前綴eslint-plugin-
-
parserOptions
: 表示解析選項ecmaVersion
: 表示es語法的版本, 默認為 3, 5。 2015表示es6, 后面可自推sourceType
: 默認是scirpt
,如果是ES模塊用module
ecmaFeatures
: 表示額外的語言特性
-
parser
: 解析器,比如babel-eslint
, 表示一個對Babel解析器的包裝,使其能夠與 ESLint 兼容 -
rules
: 表示 啟用的規則及其各自的錯誤級別,0, 1,2
分別對應off, warn, error
no-console
: 表示禁止調用console
對象的方法func-names
: 禁止命名的 function 表達式no-unused-vars
: 表示禁止未使用的變量object-shorthand
: 要求變量自變量簡寫prettier/prettier
: 表示eslint下prettier的規則兼容arrow-body-style
: 要求箭頭函數使用大括號prefer-arrow-callback
: 要求使用箭頭函數作為回調camelcase
: 使用駝峰拼寫法space-before-function-paren
: 禁止函數圓括號之前有空格
-
env
: 指定腳本的運行環境,比如在其里面寫"es6": true
, 表示自動啟動es6語法,"browser": true
表示支持瀏覽器環境
{
"extends": ["prettier", "plugin:prettier/recommended"],
"plugins": ["prettier"],
"parserOptions": {
"ecmaVersion": 2015,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"globalReturn": true,
"impliedStrict": true
}
},
"parser": "babel-eslint",
"rules": {
"no-console": "off",
"func-names": "off",
"no-unused-vars": "warn",
"object-shorthand": "off",
"prettier/prettier": [
"error",
{
"endOfLine": "auto",
"singleQuote": true,
"trailingComma": "es5"
}
],
"arrow-body-style": "off",
"prefer-arrow-callback": "off",
"camelcase": "off",
"no-new": "off",
"space-before-function-paren": "off"
},
"env": {
"es6": true,
"browser": true
}
}
Prettier
這個對應第三件事 - prettier作代碼的格式規范, 這個是本項目關於prettier的配置
配置說明:
semi
: 句尾添加分號tabWidth
: 縮進字節數singleQuote
: 使用單引號代替雙引號endOfLine
: 結尾是\n \r \n\r auto
trailingComma
: 在對象或數組最后一個元素后面是否加逗號bracketSpacing
:在對象,數組括號與文字之間加空格 "{ foo: bar }"alwaysParens
:(x) => {}
箭頭函數參數只有一個時是否要有小括號。avoid:省略括號eslintIntegration
: 不讓prettier使用eslint的代碼格式進行校驗jsxSingleQuote
: 在jsx中使用單引號代替雙引號
{
"semi": true,
"tabWidth": 2,
"singleQuote": true,
"endOfLine": "lf",
"trailingComma": "es5",
"bracketSpacing": true,
"alwaysParens": "always",
"eslintIntegration": true,
"jsxSingleQuote": true
}
看到這里,我們先停一停思考下,這么多配置,它們會不會產生沖突呢?那我要怎么去避免沖突,或者解決沖突呢?其實樓上已經提到了用eslintIntegration
不讓prettier
使用eslint
的代碼風格校驗。然后在之前的eslint學習中,也可以通過在rule下新增規則作為補充。
babel
這個對應第四件事 -做好代碼兼容性處理。babel是一個Javascript編譯器,可以將高版本的es語法,轉換成低版本的,以便能夠運行在低版本瀏覽器或者其他環境,樓下是這個項目的babel
的配置文件
配置說明:
presets
: 預設,進行相關語法轉義plugins
: 插件,補丁轉義器,彌補樓上先天不足env
: 環境變量
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [">0.25%", "not ie 11", "not op_mini all"]
},
"exclude": [
"@babel/plugin-transform-async-to-generator",
"@babel/plugin-transform-arrow-functions"
],
"corejs": { "version": "3.8", "proposals": true },
"useBuiltIns": "usage"
}
]
],
"plugins": [
"@babel/transform-runtime",
"@babel/plugin-proposal-class-properties"
],
"env": {
"test": {
"plugins": ["istanbul"]
}
}
}
可以看下我之前寫的關於babel的一篇文章- Babel:下一代Javascript語法編譯器
一般來講有其配置文件,也會有其配置忽略文件, 比如``.prettierrc和
.prettierignore`, 其它的讀者自行觸類旁通,然后配置的文件格式也有很多種,比如說json文件,js文件,rc結尾的文件等等, 這里純粹是個人習慣, 筆者一般是用 .xxxrc
手動擋控制單文件格式化
這里筆者以手動擋開頭,我覺得非常應景和帶感。與之對應的便是自動擋智能格式化。舉個例子吧,比如你選擇邊打邊格式化,未免也太浪費資源了,而且可能它格式化的會和你當時的想法有沖突。所以每次按下CTRL + S
進行格式化的話,是一個很好的方案。它就好比開車,停車的話,掛空擋,拉手剎,下車干飯,是一氣呵成的,那個CTRL + S
就好比駕駛員手握的掛擋器,帶感。什么?剎車失靈?不存在的,阿Sir!!!
來看一下效果:
提交代碼前確認所修改文件或者整個項目代碼規范
前面我們提到的是我們在平時開發中,對於單個文件的代碼規范手段,那么對於整個項目,我們應該在每次提交前再去檢查確認一遍,這樣子我們提交到遠程的代碼才有保障。細心的同學可能已經發現了,是的,在文章開頭講到的package.json
中可以配置husky
和lint-staged
去做這件事。husky
做提交前的檢查, 而lint-staged
則優化了檢查的范圍是要提交檢查的,從而加快速度提高效率。
由於husky和lint-staged的版本不同配置也不同,這里筆者用的是最新的配置,具體的參考了這位國際友人的文章https://qiita.com/sprout2000/items/29e8a637dda259bab26d
我這里的話, 就是在每次提交的時候對js、ts等文件進行eslint和prettier格式化,配置如下:
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint . --fix",
]
}
來看一下效果:
這里為了讓大家更明顯直觀看到效果,筆者沒有加prettier格式化那一句在lint-staged里面,后續加上后,關於格式的問題會被自動修復, 如下
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint . --fix",
"prettier --config .prettierrc --write ."
]
}
效果如下
root@ccb5f768c839:/home/coder/utils# git commit -m "test husky and lint-staged"
> @ataola/utils@0.1.5 lint-staged
> lint-staged
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
[main 0e5f4d3] sadasd
1 file changed, 2 insertions(+), 1 deletion(-)
root@ccb5f768c839:/home/coder/utils#
項目目錄
目錄說明:
-
LICENSE
: 授權文件 -
README.md
: 說明文件 -
coverage
: 代碼覆蓋率文件夾 -
docs
: 文檔文件夾 -
img
: 圖片文件夾 -
index.js
: 入口文件 -
log
: 日志文件夾 -
node_modules
: 安裝的npm依賴文件夾 -
package-lock.json
: npm的配置文件鎖 -
package.json
: npm的配置文件 -
pull
: 拉取遠程github倉庫的腳本 -
push
: 上傳遠程github倉庫以及npm發包的腳本 -
test
: 單元測試文件夾
➜ utils git:(main) tree -L 1
.
├── LICENSE
├── README.md
├── coverage
├── docs
├── img
├── index.js
├── karma.conf.js
├── lib
├── logs
├── node_modules
├── package-lock.json
├── package.json
├── pull
├── push
└── test
7 directories, 8 files
➜ utils git:(main)
項目命名規范建議:
-
應該使其文件或文件名命名具有語意,不會你就翻字典
- 推薦
城市 city
- 鄙視
城市 chengshi
- 嚴重鄙視
城市 cs
- 推薦
-
要么用命名縮寫,要么用全名,建議全名用復數形式
- 推薦縮寫
img
- 不推薦縮寫
imgs
- 推薦全寫
images
- 不推薦全寫
image
因為img是image的縮寫,你再加個s就沒有啥語意了,而全名的images表示圖片,這里可能有讀者會鑽牛角尖,你那個
docs
不是和樓上沖突了嗎? 不是的, doc英文單詞是文檔,docs是其復數形式, 這要和document區分開。 - 推薦縮寫
我們可以通過tree
命令去查看項目文件結構,-L
表示深度層數, mac用戶可以通過brew install tree
安裝,ubuntu用戶可以通過apt-get install tree -y
安裝,centos用戶可以通過yum install tree -y
安裝,window用戶請下載相關tree包並配置到path環境變量里去, 或者去搜下window下的包管理命令`
git工作流和npm
努力做兩件事:
- 用腳本偷懶代替一行一行的敲命令,或者IDE點點點
- 把雞蛋放在牆內和牆外兩個籃子里
腳本一把梭,梭,梭哈
我們先思考下,在git工作流中,有這樣三個概念, 萌萌噠的我, 遠程倉庫,本地倉庫。那,以這三個概念造句子,可以這么玩。萌萌噠的我爽朗地把本地倉庫推向了遠程倉庫,遠程倉庫被萌萌噠的我瀟灑地拉到了本地倉庫。是的,這個在生活中有很形象的例子,還是萌萌噠的我飢饞碌碌地推開肯德基的大門去干飯,半個小時過去了,滿懷滋潤的我拉開了肯德基的大門揚長而去。綜上所述,我們大致可以概括出兩個行為,推(push)和拉(pull),好,上腳本。
push
#!/usr/bin/env bash
set -e
function git_branch_name() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'
}
function e() {
echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
printf "┃$(tput bold) %-40s $(tput sgr0)┃\n" "$*"
echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
"$@"
}
CURRENT_BRANCH=$(git_branch_name)
if [[ $CURRENT_BRANCH = feature/* ]]; then
e git stash
e git checkout main
e git pull
e git checkout "$CURRENT_BRANCH"
e git merge main
e git push
e git stash pop || true
elif [[ $CURRENT_BRANCH = main ]]; then
e git stash
e git push
e nrm use npm
e npm publish --access public
e nrm use taobao
e git stash pop || true
fi
在push這個行為上,我們需要考慮兩點。第一,遠程代碼有更新嗎?跟我本地會有沖突嗎? 第二,我當前是在哪個分支,我代碼才剛寫到一半,我不想提交這么辦?git stash
就是將你當前的代碼改動存入暫緩區,使得其恢復上一次提交的狀態,這個時候你從遠程拉下來代碼,再去merge
下,然后你執行git stash pop
,git checkout
是切換分支。
上面代碼的意思是,如果我是在某個特性分支,那么就先把我目前的改動存入暫緩區,然后切到主分支main,去拉取遠程代碼,然后切回我當前的分支,再去對主分支進行merge
,然后執行push
,最后再把我的改動從暫緩區拿出來,然后就可以繼續開發了。如果我當前是主分支,那太開心了,先把當前改動存入暫緩區,然后直接push,再來個npm發包,然后把當前改動彈出來。
pull
#!/usr/bin/env bash
set -e
function git_branch_name() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'
}
function e() {
echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
printf "┃$(tput bold) %-40s $(tput sgr0)┃\n" "$*"
echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
"$@"
}
CURRENT_BRANCH=$(git_branch_name)
if [[ $CURRENT_BRANCH = feature/* ]]; then
e git stash
e git checkout main
e git pull
e git checkout "$CURRENT_BRANCH"
e git merge main
e git stash pop || true
elif [[ $CURRENT_BRANCH = main ]]; then
e git stash
e git pull
e git stash pop || true
fi
這個pull和上面的push類似的,就不贅述了,讀者照着樓上的push去理解下pull吧。
多個remote的真香定律
為什么會有這個想法呢?由於不可描述的原因,牆對於天朝開發者來說始終是一個神秘的存在,當我們在使用GitHub的時候,有時會遇到DNS污染,有時可能是牆的問題,總之就是提交也很難提交上去,拉也拉不下來。特別是在自己的雲服務器上去拉GitHub上的代碼,等的花兒都謝了,算了放棄吧,先走為敬。這個時候碼雲是個神奇的存在,用它拉取代碼速度是相當的快,於是我蠢蠢欲動地加了一個碼雲的remote,直接提交到碼雲了,這樣子一個好處是,我本地就起一份代碼就好了,用不着同一個項目搞兩份代碼。嗯,remote真香!!!
下面是我添加碼雲的remote地址,然后把它上傳到碼雲的步驟:
git remote add gitee https://gitee.com/taoge2021/utils.git
git fetch gitee
git checkout -b gitee-main gitee/main
git merge main
git push gitee main
npm
這個其實在樓上代碼已經有所體現了,這里簡單講下就是,你先去https://www.npmjs.com/
去注冊一個賬號,然后本地npm login
去登陸這個賬號,如果你想發布一個形如@ataola/utils
的包,那么執行npm publish --access public
, 如果你不想的話npm publish
就可以了。
注意: 發包的時候不要切到淘寶源,是在npm源上提交,可以通過 npm config set registry作轉化, 也可以用nrm這個包作源的管理
測試、持續集成和代碼覆蓋率
努力做三件事:
- 單元測試
- 持續集成測試
- 代碼覆蓋率測試
karma + mocha + chai
做測試的技術選型搭配其實有很多,我這里用到樓上這三位。是這樣子的,因為我這個庫定義是給前端用的,后續會涉及到一些DOM,BOM等等的相關測試,我期望它是真的開了個瀏覽器去測試我的代碼。而Karma這個測試運行器它可以做到這點,而且它還是開源的。mocha是比較有名的測試框架,后面的chai是用來作斷言的。
karam的配置創建可以看下package.json
里面我配置的script腳本
"karma:init": "karma init ./karma.conf.js",
"karma:test": "karma start ./karma.conf.js",
npm run karma:init
表示創建一個karma的配置文件,而npm run karma:test
表示啟動karma相關測試。
附上一份karma.conf.js
, 由於配置較多,這里如果默認生成的話,大部分都不需要你動,就挑幾個講下,具體的還是要去看官方文檔的http://karma-runner.github.io/6.3/config/configuration-file.html
framework
: 表示你裝的一些框架plugins
: 故名思義,裝的插件files
: 表示要加載瀏覽器的文件preprocessors
: 一些預處理操作browsers
: 可提供的瀏覽器webpack
: 暴露的webpack配置接口mochaReporter
:暴露的mocha配置接口
// Karma configuration
// Generated on Sat Apr 10 2021 00:13:46 GMT+0800 (中國標准時間)
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'chai', 'webpack'],
plugins: [
'karma-chrome-launcher',
'karma-mocha',
'karma-mocha-reporter',
'karma-chai',
'karma-webpack',
'karma-coverage',
],
// list of files / patterns to load in the browser
// test all
files: ['lib/**/*.js', 'test/**/*.js'],
// test single file
// files: ['test/**/judge.test.js'],
// list of files / patterns to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
// test all
preprocessors: {
'lib/**/*.js': ['webpack', 'coverage'],
'test/**/*.js': ['webpack'],
},
// test single file
// preprocessors: {
// 'test/**/judge.test.js': ['webpack'],
// },
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// https://github.com/litixsoft/karma-mocha-reporter
reporters: ['mocha', 'coverage'],
mochaReporter: {
colors: {
success: 'blue',
info: 'bgGreen',
warning: 'cyan',
error: 'bgRed',
},
symbols: {
success: '+',
info: '#',
warning: '!',
error: 'x',
},
output: 'autowatch',
showDiff: true,
divider: '',
},
coverageReporter: {
dir: 'coverage/',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text', subdir: '.', file: 'text.txt' },
{ type: 'text-summary', subdir: '.', file: 'text-summary.txt' },
],
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessNoSandbox'],
// you can define custom flags
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox'],
},
},
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: !!process.env.CI,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
webpack: {
mode: 'development',
// entry: ['@babel/polyfill'],
// entry: ['./index.js'],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
corejs: { version: '3.8', proposals: true },
useBuiltIns: 'usage',
},
],
],
plugins: ['istanbul'],
},
},
},
],
},
},
});
};
這里如果我是寫Node的話,我會用jest,因為配置會簡單些。具體的讀者可以閱讀下我之前寫的文章使用jest進行單元測試, 附上一個完整實戰的例子,這個是我刷leetcode做的單元測試的項目地址,https://github.com/ataola/coding
travis
travis是做持續集成的,貼一份筆者的配置,需要注意的是,版本的不同可能配置也不太一樣,具體的還是要去看官方文檔https://docs.travis-ci.com/
language: node_js
node_js: stable
notifications:
email:
recipients:
- zjt613@gmail.com
on_success: change
on_failure: always
branches:
only:
- main
cache:
apt: true
directories:
- node_modules
os: linux
# https://docs.travis-ci.com/user/reference/overview/
dist: xenial
addons:
chrome: stable
services:
- xvfb
sudo: required
# turn off the clone of submodules for change the SSH to HTTPS in .gitmodules to avoid the error
git:
submodules: false
before_install:
- 'export DISPLAY=:99.0'
- sleep 3 # give xvfb some time to start
- '/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16'
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
install:
- npm set progress=false
- npm install
script:
- npm run karma:test
after_script:
- npm run codecov
codecov
codecov是做代碼覆蓋率測試的, 執行npm install codecov -D
去安裝它,然后在packge.json
里面配置好script就好了"codecov": "codecov"
, 我們在做持續集成的時候,最下面在執行完相關karma測試后,最后會執行npm run codecov
去讀取 coverage
目錄中的 lcov.info
文件,然后上傳到 Codecov 網站
測試這塊做了這么多工作,其實就是當了一回場面人,在倉庫首頁給它一個特寫,這里加了travis持續集成的構建結果和codecov的代碼覆蓋率以增加項目的可信度和逼格。
VSCode 開發環境
思考兩件事:
- 如何配置不同的開發環境,區分開發環境的共性和不同,以及其引起的不同(權衡不同項目利弊)
- 最小化插件原則,提高電腦運行效率,不搞花里胡哨,不裝逼,把電腦當朋友
環境的共性和不同
為什么會有這個問題,也還是源自生活中遇到的事。筆者最開始為了一步到位,將相關的prettier、eslint等等的相關配置都寫到了全局的,也就是user下面,后來在拉取項目的時候發現,很多時候特別是多人開發,由於eslint和prettier的配置不一樣,或者根本就沒有這塊的配置,導致代碼堆積如屎山難以維護,這促使我有了進一步的思考是,區分編輯器的共性和不同。 舉個例子,比如說terminal這個插件,它其實可以配置調節在終端光標的粗細,我就不是很喜歡那種肥肥的光標,就把它改成line
,這種是屬於不同,是你的個性,不會因為說你設置了這個會影響到整個項目,別人電腦里沒設置還是肥肥的光標。那么什么是共性,就比如最開始筆者說的將prettier、eslint配置到全局的做法,違背了共性,這里需要說明的一點是,違背不代表我是錯的,在這件事情上沒有對錯,大環境決定的,如果一只隊伍里大家都認為 1 + 1 =3,那么即使你認為1 + 1 = 2,從大局上考慮,這里就姑且遷就下1 + 1 = 3吧,你可以沉默不說話,但你心里要有你堅定的真理的答案,這個叫站隊。
具體的解決方案我認為是,你可以在全局里去配置以那種方式去做一件事,但是具體的規則和形式需要單獨拎出來,不能寫全局里面。可以新建一個.vscode
文件夾,然后在這個項目里面單獨配置,結合.prettierrc
、.eslintrc
等,可以參考下這個項目https://github.com/ataola/coding
推薦插件
- prettier: 代碼格式
- eslint: 語法格式
- GitLens: git輔助
- gi t-commit-plugin: 規范git提交信息
- Terminal: 終端工具
- Vetur: 寫vue輔助工具
- vscode-icons:編輯器主題
- TODO Highlight 高亮要做的事
筆者以前也是個使用插件狂魔,總喜歡去試試倒騰這個插件那個插件好不好使好不好玩,再后來我那個 多年前買的window不堪重負萎靡不振,我就沒有這個想法了,插件只是個輔助工具,根據使用頻繁度和實用性去考量吧,老羅有句話說得好, 又不是不能用?
示例講解
關於處理url參數轉成對象的格式,這個是前端開發面試的常考題,因為它實用性強,涉及基礎的數組字符串處理,答案還不唯一,所以這里筆者拋磚引玉,就以它為例子去講吧。
-
getQueryParameters
如果對正則不熟悉的話,這里可以用字符串分割分割再分割來做,具體的如下
/** * * @param {string} url * @returns {object} */ function getQueryParameters(url) { const paramStr = decodeURIComponent(url).split('?')[1]; if (!paramStr) { return {}; } const paramArr = paramStr.split('&'); const res = {}; paramArr.forEach((param) => { const [key, value] = param.split('='); res[key] = value; }); return res; }
相關測試
import { expect } from 'chai'; import { getQueryParameters } from '../lib/url'; describe('lib: url test', function () { it('getQueryParameters: expect { name: "ataola", age: "24" } when call function with params "https://zhengjiangtao.cn?name=ataola&age=24"', function () { expect( getQueryParameters('https://zhengjiangtao.cn?name=ataola&age=24') ).to.deep.equals({ name: 'ataola', age: '24' }); }); }); });
最后
至此,筆者已經向讀者們介紹了一個前端項目從有想法到去實踐再到總結分享的心路歷程。謝謝大家的賞臉閱讀,談起為什么寫這個項目,第一是項目做多了,自然而然就會有些想法,明人不說暗話我想偷點懶划水,想早點下班哇,所以工作之余就勤快點把平時工作或者刷題常用到的總結整理下,打磨成一把瑞士軍刀,提高戰斗力;第二是像我們搞程序的,都挺單純的,有句話說得好”no BB, show me the code!“,可能不是很會表達自己吧,那就上代碼吧,希望面試官看了能夠加點印象分或者綜合得分,哈哈。