前端實用程式包utils - 開發工作流(一)

ataola(璀錯)發表於2021-04-29

寫在前面

早年間有幸在Raychee哥門下當小弟,學到兩把刷子。在程式設計路上,他的很多思想深深影響了我,比如筆者今天要分享的主題。在程式開發中,有個utils包,叫做實用程式包,程式設計師們會把專案中通用的東西抽離出來放到這個裡面,這有利於專案工程化的落地,提高專案的可維護性,減少程式碼冗餘,鍛鍊編碼能力,提高編碼效率,理解程式設計思想。

在開始之前,我們先思考下,建立一個規範的專案我們需要關注哪些點?我覺得吧,第一個是建立資訊的完整性,一個資訊完整的專案可以引導讀者與作者交流與合作,這個在後面的package.json裡面向大家介紹;第二個是程式碼的規範性和相容性,正所謂,沒有規矩不成方圓,良好的程式碼規範會巧妙地杜絕屎上雕花的行為發生,這個後面跟大家介紹下eslint+prettier+babal的知識點;第三個專案目錄的規範性,這個也會在後面介紹。基於前面三點,我們可以做出一個自產自銷的專案,如果把這個過程比作拉翔,那它會很通暢,拉的很舒服,不會便祕。但是遠遠不夠,就比如程式設計師A拿到程式設計師B寫的專案,那麼程式設計師B怎麼去證明給A看,我的槍好使且我的話可信。這就引入了後面兩個話題,第四點就是把你的作品發出去讓別人能看得到,《何以笙簫默》中有句臺詞,“如果我們走散你找不到我,那我就站在最高的舞臺中央讓你看見我。”男同胞們聽懂了嗎?你想要脫單,一個不成熟的建議,站在舞臺中央,發出滋滋滋的求偶聲,跳出Michael Jackson妖嬈的舞步,just beat it,just beat it.喜歡你的說不定就有了,主動一點就會有故事。這個後面筆者介紹下git工作流以及npm的發包;第五點就是測試,提高可信度。這裡我會結合karmamochachaitraviscodecov來向大家介紹單元測試、持續整合、程式碼覆蓋率測試。最後的話,我會結合相關的開發工具做一個簡單的搭配使用介紹吧。好的,我們開始吧。

專案建立

注意: 因為筆者目前前端接觸的比較多,所以這個庫的定義就是給前端環境用的,不是很推薦用在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, 表示換行符,它有lfcrlfcr等等,跟系統關係比較大,反正大家都統一一下用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中可以配置huskylint-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 popgit 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!“,可能不是很會表達自己吧,那就上程式碼吧,希望面試官看了能夠加點印象分或者綜合得分,哈哈。

專案地址: https://github.com/ataola/utils

相關文章