tag-2021-08-30-tag
編寫代碼只是我們整個軟件開發流程中很小的一部分,一個完整的軟件開發流程還包含其准備,測試,部署等其他階段。在這些階段,我們會用到除了編程語言之外的很多工具幫助我們搭起來一套工作流程。本這篇文章,通過一個實際項目,將這套流程輔助工具用到項目中,從代碼開發-測試-部署-集成一套基礎的規范做下來。
建庫
單個人開發的項目可以任你自由發揮,基本上不需要各種版本管理工具,你把自己寫的代碼存在雲上或者自己硬盤上就行。但是如今的軟件應用,尤其是大型的軟件應用都是多人合作的結晶。我們在開始項目之前要選擇好的版本管理工具。
SVN依舊是很多企業,尤其是傳統企業(例如我們公司)喜歡采用的版本控制工具。SVN作為一套版本管理工具我本人是非常喜歡它的,並且能夠解決我們日常開發工作中的絕大對數需求。但是考慮它是一款相對封閉的管理系統,我們這次的項目就不打算使用它,而是使用git分布式管理系統來控制我們的版本;
git
git是目前最流行的版本管理工具,作為git的公共倉庫,github也是非常受歡迎,你需要注冊一個github帳號來創建你的倉庫。
git私有倉庫
很多時候我們的代碼是不能放到公網上的,這就需要我們建立私有的倉庫,github上的私有項目只能三個人同時開發,或者需要你交錢之后才能給你服務。作為全球公共的開源倉庫,這種做法本身是沒毛病的。如果你的團隊不大,可以選擇自己服務器創建倉庫,如果你是在大型的公司,合作分工的人非常多,你可以使用gitlab。我們這里介紹如何搭建git私有倉庫。
默認進入root權限sudo -i
#在linux上安裝git
apt-get install git
#添加git用戶
adduser git
#進入git目錄
cd /home/git/
#創建.ssh目錄
mkdir .ssh
cd .ssh/
#新建存放公鑰的文件 后續想里面添加開發人員的公鑰
touch authorized_keys
#初始化一個git倉庫
git init --bare /usr/local/sample.git
chown -R git:git /usr/local/sample.git
如果你的代碼需要在服務器上進行備份保存,就需要新建目錄用來保存大家提交的代碼:
#在服務器上建立一個目錄,用來同步提交的代碼
mkdir /usr/local/ngnix/sync-folder
chown -R git:git /usr/local/ngnix/sync-folder
#添加鈎子文件
cd /usr/local/sample.git/hooks
touch post-receive
#編輯
vim post-receive
#加入以下同步代碼
#!/bin/bash
#git --work-tree=/usr/local/ngnix/sync-folder checkout -f
#最后修改權限
chmod +x post-receive
這樣配置后代碼一旦被遠程提交,服務器會觸發鈎子函數,自動將提交的代碼放到我們制定的目錄下面。
verdaccio
我們經常編寫npm的包放到公網上,但是有時候這些模塊是不能面向公眾的,例如一些涉及公司業務的組件或者內部代碼,但是我們確實還是希望用到node_modules模塊方式來組織我們的代碼。這就需要在自己的服務器上搭建私有npm模塊管理倉庫,我們安裝第三方的包來實現:verdaccio
#進入管理人員模式
sudo -i
#在linux上安裝verdaccio和node程序管理器
npm install verdaccio pm2 -g
#退出管理員權限
exit
我們需要對verdaccio做一些修改,例如修改備用倉庫以及內網地址:
#修改默認配置文件
cd /usr/local/lib/node_modules/verdaccio/conf/
vi default.yml
在最底部映射本地地址:
+ listen: 0.0.0.0:4873
你可以根據官方文檔的說明對其他的配置進行個性化配置,例如代理,緩存等。不過一般來說,在加入最后一行的映射IP和端口之后就可以順利跑起來了。
pm2 start verdaccio -i 1
在瀏覽器中打開 http://(你的.服務器.IP.端口):4873
接下來,只需要在發布/安裝模塊的時候指定下載路徑,就可以把我們的包發布/下載。
#發布模塊
npm publish --registry http://xxx.xxx.xx.xx:4873
#更新模塊
npm i test_module --registry http://xxx.xxx.xx:4873
請使用一個核來跑verdaccio,verdaccio與pm2 據說還不兼容,起用多核跑程序會出現包顯示不完整的問題。
構建
typescript
Javascript前端開發中的主要編程語言。因為它的動態和弱類型語言的特性,使得它很容易上手,也非常的靈活。這種特性同時也帶來了格式混亂以及運行時錯誤頻發,Typescript的大名大家都已經知道了,如果說它是一門語言更是一門代碼檢查/注釋工具。ts的最大作用就是對JavaScript進行類型的檢查,將潛在的類型錯誤在暴露編譯階段。你可甚至認為ts是一種編譯規則的工具,按照自定義的type來保證我們的javascrit這種動態弱類型語言變成靜態的強類型語言。Typescript安裝並不難,本篇博文我們就從頭開始來創建一個發布到公網上的node模塊——panda-warrior。
#創建&進入目錄
mkdir panda-warrior & cd panda-warrior/
#初始化項目
npm init -y
#初始化殘酷
git init
#創建忽略提交的文件
touch .gitignore
#建立遠程的倉庫鏈接
git remote add origin yourgithubrepoaddress@github.com
#開始安裝typescript ts-node:node版本的ts, node-mon:nodejs實時編譯運行的監聽模塊
npm install typescript node-ts nodemon -D
#如果你的typscript不是安裝在全局的,需要在對應目錄下的。bin中去運行該命令
node_modules/.bin/tsc --init
#創建入口文件
mkdir src && touch index.ts
編寫入口文件代碼:
import Speak from './speak';
import Jump from './jump';
import Sing from './sing';
class PandaWarrior {
constructor() {
console.log('hello world');
}
armour() {
return true;
}
speak = Speak;
jump = Jump;
sing = Sing;
weapon() {
return 'bamboo';
}
furColor() {
return 'black-and-white';
}
shout(type: string) {
return type === 'frined' ? 'hi would like an banoo' : 'get out of here';
}
}
export default PandaWarrior;
進入package.json文件中,編寫scripts字段:
{
"name": "panda-warrior",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^2.0.7",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
}
在package.json中的scripts字段中可以用npm 運行安裝的腳本。一般來說它會去node_modules/.bin目錄下面運行本地的腳本,我們將其配置在scripts中就可以方便的使用了,當然你也可以使用npx代替scripts配置,它們的作用基本上來說都是去執行本地命令,並無太大差別。現在我們在控制台執行npm start就可以看到我們的打印輸出,說明我們已經搭建起來一個簡單的typescript程序。
eslint
在編寫代碼的過程為了遵守規范,需要對的團隊開發者的代碼格式做規范,多人開發的團隊尤其如此。因此我們選擇一款檢查代碼的工具,來強制對我們的代碼進行檢查,eslint就是這樣一種工具,它能按照既定的規則檢查代碼是否符合規范。配置eslint也非常簡單,只需要幾個命令就好,現在我們把eslint的代碼規范導入到項目中來:
npm install eslint -D #安裝eslint
安裝好了eslint之后你需要配置一些eslint文件.eslintrc.js
。從零開始配置一個eslint文件很讓人抓狂,幸好這個工具為我們通過了類似腳手架的功能,你只需要運行初始化命令,然后選擇你要的eslint選項即可。初始化eslint根據你安裝的位置,大致有三種方式。
#如果你的eslint模塊是全局安裝的
eslint --init
#你的eslint只安裝在本項目
npx eslint --init
#你的eslint只安裝在本項目
node_modules/.bin/eslint --init
這三條命令的作用都是相同的,你安裝的eslint的位置決定了你如何使用。接下來你會看到控制台出現了一些選項,只需要按照提示,一步步完成對應的選項選擇:
eslint回自動為你生成基礎的配置文件.eslintrc
。文件生成之后,我們把eslint命令也加入到腳本中:
"script": {
...
"check": "eslint . --ext .ts"
}
當我們運行npm run check
命令時,eslint會分析所有ts文件的格式是否符合預期規范,並且將你的代碼按照規范進行格式化,例如換行,空格,逗號等,但是有些代碼規范的錯誤無法修復的格式,只會給你拋出錯誤,你需要手動的去修改。
Tslint團隊已經聲明不再維護Tslint了,因為Tslint和Eslint在很多方面的工作是重復的,因此一般我們也用Elinst來做代碼格式檢查的工作,即使你的項目是typescript編寫的。
prettier
eslint在代碼格檢查方面做的很好,但我們每次跑npm run lint
時會拋出一些無法解決的錯誤,為此我們需要引入一個更懂我們的修復代碼規范的工具prettier
。prettier
能夠更加智能的補充你的代碼:如加上逗號,去掉大括號,簡而言之eslint給你拋出的錯誤大部分它都能夠直接幫你解決。我們先安裝prettier
npm install prettier -D #安裝包
touch .prettierrc #創建配置文件
vi .prettierrc #編輯配置文件,將規則寫入配置文件中
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": false,
"jsxBracketSameLine": false,
"arrowParens": "always"
}
以上是一份基礎的配置規則,你也可以在官方文檔中查找對應的規則配置,然后我們把啟動命令寫入配置腳本:
"script":{
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write"
}
在控制台執行npm run prettier-format
后,你的代碼會變得如絲般柔滑,這時候你用eslint去檢查,不會拋出任何報錯,很好得解決了代碼格式問題。prettier
同時也提供了插件供eslint使用的,將prettier的插件安裝到eslint中,eslint會按照prettier的風格檢查代碼。安裝插件以及配置即可。
npm install eslint-plugin-prettier eslint-config-prettier -D
然后在.eslintrc.js
中加入擴展配置
extends: [
...
"plugin:prettier/recommended",
"prettier"
],
rules: {
"prettier/prettier": 2
}
如果你不想頻繁地執行格式化代碼命令,你可以考慮在你的項目中引用文件監聽模塊,來減少你的重復工作,監聽模塊會監聽你的文件然后自動格式化你的代碼:
npm install --save-dev onchange #安裝onchange文件監聽包
然后在package.json中的腳本中加入:
"script": {
...
"prettier-watch": "onchange 'src/**/*.ts' -- prettier --write {{changed}}"
}
運行npm run prettier-watch
,開啟一個監聽文件變化的目錄,每當你保存文件,代碼自動被格式化。
rollup
因為我們采用typscript編寫的模塊,這些模塊最終需要最終編譯成js,為此我們需要引入構建工具rollup來構建我們的工程。rollup幫助我們把指定的ts打包成js文件,它相較於webpack更輕量好用,非常適合打包單文件模塊。我們首先來安裝rollup以及必要的插件:
npm install rollup rollup-plugin-typescript2 rollup-plugin-commonjs -D
然后我們創建打包工具的配置文件.rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import commonjs from 'rollup-plugin-commonjs';
export default {
input: "src/index.ts", //入口文件
output: [{
file: 'dist/index.js', //目標路徑
format: 'cjs' //打包目標模式,我們編寫的是node模塊,請使用commonjs規范
}
],
plugins: [//打包插件
commonjs(),
typescript()
]
}
然后把編譯命令寫入scripts中:
"scripts": {
...
"compile": "rollup -c" //rollup 通過配置文件編譯項目
}
運行npm run compile
rollup就會在將我們的應用構建成普通的javascript模塊並且存放到dist目錄下,dist目錄結構如下所示:
測試
Mocha
編寫單元測試在大型的應用中非常普遍,代碼除了在格式上符合規范,更需要在快速迭代的功能上保證穩定。單元測試的前提是代碼的模塊化,這樣模塊才能作為“單元”被測試。我們在構建大型多人協作時,也應該遵循編寫模塊的規范,做到各個模塊獨立,解耦,同時做到固定的輸入有穩定的輸出即無副作用。如今的單元測試框架不少,例如jest, mocha等。我們這里選用了較為流行的測試框架mocha進行單元測試,(如果你使用的是react,可以用jest來測試)把測試功能加入我們的項目當中。測試環境應該和斷言庫一起使用,我們這里采用chai斷言庫來配合測試的工作。
npm install mocha chai -D
mkdir test && cd test && touch index.test.js
const chai = require('chai');
//從打包出來的入口文件模塊中引入js
const Panda = require('../dist/index');
var expect = chai.expect;
var p = new Panda;
describe('And Last sing a song', function () {
it('listen to the panda\'s song', function () {
expect(p.sing({ name: 'The song of bamboo', singer: 'Panda' })).to.equal('A song named The song of bamboo sang by Panda');
});
});
describe('Say sth to the world', function () {
it('What will you sing about', function () {
expect(p.speak("save the world")).to.equal("save the world");
});
});
describe('Now jump ahead', function () {
it('How tall can panda jump(cm)', function () {
expect(p.jump(1, 2)).to.equal(3);
});
});
describe('What is your weapon? Panda', function () {
it('should return an weapon', function () {
expect(p.weapon()).to.equal('bamboo');
});
});
describe('What is the color of your fur..? Panda', function () {
it('should return fur color', function () {
expect(p.furColor()).to.equal('black-and-white');
});
});
describe('Do you have armour? Panda', function () {
it('should return a boolean value', function () {
expect(p.armour()).to.be.ok;
});
});
然后執行命令,會得到單元測試的結果:
node_modules/.bin/mocha #啟用測試,自動尋找test目錄下被編譯成的index.test.js
得到了一下結果:
我們接着把運行命令簡化到scripts
中, 后續只需要執行npm test
,程序就會首先對項目進行構建,然后運行單元測試。
"scripts": {
"test": "npm run compile && mocha"
}
npm test 是npm內置的命令,無需加上run關鍵字即可運行
nyc
模塊單元測試提供給我們的是單個模塊功能的測試情況,為更好的統計我們的單元測試結果,我們引入測試覆蓋率。測試覆蓋率則告訴我們項目中所有被納入測試模塊的運行狀況,包括判斷語句,函數,文件,甚至是分支的代碼,為我們全面地提供了項目的測試狀況。
npm install nyc -D #按照單元測試覆蓋率模塊
添加配置文件.nycrc
{
"include": [
"dist/*.js" //測試覆蓋的范圍
],
"reporter": [
"text", // 生成控制台報告
"html" // 是否生成html界面的報告
],
"lines": 60, //測試覆蓋行數及格率
"statements": 60, //測試覆條件分支及格率
"functions": 60, //測試覆函數及格率
"branches": 0, //這如果是去測試dist目錄中,dist沒被提交到任何分支中,就是0
"all": true,
"check-coverage": true,
"sourceMap": true,
"instrument": true
}
將其與測試框架mocha結合,我們可以在scripts中添加腳本並且寫入部分配置:
"scripts": {
"cover": "npm run compile && nyc mocha"
}
建議將你的代碼測試覆蓋率(函數,添加,分支等)設置為合理的值,不應該期待覆蓋率達到100%的應用規模,並不是每個模塊都需要被測試,只需要測試到具體的核心模塊即可。
測試通過之后你可以直接在控制台查看對應的覆蓋率情況,如果你在.nycrc
中配置了html選項,那么程序會在根目錄下生成coverage目錄,里面包含了一個html文件,點擊打開就可以看到網頁版本的測試情況,非常直觀。
部署
husky
假設我們的編寫代碼階段已經完成,我們還需要把代碼提交到git倉庫中去。統一提交格式規范不僅僅約束着我們的注釋不會產生較大的奇異,便於代碼檢查時發現問題,同時也在我們歸檔或者建立里程碑的時候提供歷史記錄,呈現一致的UI展示和歸類整理。husky為我們提供了git提交的一些生命周期鈎子,在git提交過程中提供給我們干預的能力,例如提交之前就檢查我們輸入的注釋的格式,代碼的格式等。現在開始向我們的項目中安裝對應的依賴模塊:
npm i husky lint-staged @commitlint/cli @commitlint/config-angular -D
- husky: git-hook模塊
- lint-staged: 將工作區的文件lint格式化 實現只檢查提交到工作區的內容而非全部內容
- @commitlint/cli: 提交命令依賴cli工具
- @commitlint/config-angular: angular代碼提交規范
盡量使用npm而非cnpm安裝,否則,依賴會安裝失敗!!!!。
在根目錄下創建commitlint.config.js
寫入我們指定提交格式規范:
module.exports = {extends: ['@commitlint/config-angular']};
angular團隊的代碼提交注釋規范是是一種比較流行的注釋規范,很多大項目的團隊都在使用,我們的項目也按照這種規范進行提交注釋的編寫,下面是我們在git commit時遵循的規范:
<type>(<scope>): <subject>
<blank line>
<body>
<blank line>
<footer>
- type: 本次提交的類型,具體的值如下表。(必填)
- scope:代碼影響的范圍。(非必填)
- subject:本次提交的主題。(必填)
- body:提交的內容詳細說明。(非必填)
- footer:底部結尾說明。(非必填)
命令 | 描述 |
---|---|
feat | 新增二樓功能或者特性 |
fix | 修復bug |
docs | 只修改文檔 |
style | 只修改了樣式、空格、分號等 |
refactor | 重構了某塊業務模塊 |
perf | 優化了項目,例如性能 |
test | 測試用例 |
build | 改變構建流程、集成測試 |
revert | 回滾到上一個版本 |
merge | 合並一個版本進來 |
ci | 構建相關基礎流程 |
接着我們在package中加入**husky**
和**lint-staged**
配置,確保在提交代碼之前強制規范我們的代碼格式。
...
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS", //首先檢查調注釋規范
"pre-commit": "lint-staged", //提交之前執行的命令
}
},
"lint-staged": {
"*.ts": [ //校驗的目標文件類型
"npm run check", //規范所有的提交的代碼,如果失敗或報錯
"git add" // 自動將格式化代碼提交到工作區
]
}
如果我們的提交規范或者代碼格式不符合規范,提交命令會被打回,需要我們重新提交。
git commit -m "xxx" --no-verify 忽略提交格式強制提交。建議不這么做。
ignore
在提交我們的模塊到遠程倉庫之前,需要對一些文件進行設置,告訴我們的項目,哪些文件需要被發布到npm上去,哪些又應該提交到git倉庫中。我們以本項目為模板,說明哪些需要提交,以及這樣做的原因。
首先決定哪些文件需要被提交到git, 創建忽略配置文件:
.gitignore
node_modules/ #這個無需解釋
.vscode/ #如果你使用的是vscode編輯器,會產生一些相關文件,這些文件無需提交
.nyc_output/ # nyc模塊產生的一些文件
coverage/ #測試覆蓋率的靜態資源也無需提交
dist/打包后的目錄,不提交
*.d.ts #理由同上
.npmignore
需要發布到npm上的文件,應該是所有被編譯成的js文件,以及.d.ts文件,除了這些其他的都是我們開發用的文件,無需提交。你可以在.npmignore中一一地把不需要的文件填入,但有個更簡便的方法,在package.json中配置fileds字段,指定哪些目錄需要被發布即可。一般生成的目錄約定為dist目錄,你只需要填寫dist即可:
"files": ["dist"],//提到npm倉庫的最終編譯后的文件
"main": "dist/index.js",//引用入口文件
當你運行npm publish
時,dist目錄下的所有文件會被發布到npm庫中,其他文件沒有帶上。當然你也可以結合兩者一起,設置files的同時,也寫入.npmignore
文件在dis目錄中那些是不需要寫入。
- ts項目,只需要在tsconfig.js中設置編譯后js存放的的路徑為dist即可。
- 好的工具一定不能缺少文檔,請一並創建README.md 文件上傳到github。
持續測試/集成
Travis cli
目前位置,我們的開發任務、測試、發布任務已經基本接近完成,軟件開發周期也到此為止。如果新的功能繼續迭代,我們只需按照業務編寫代碼即可。持續集成的概念在軟件領域早已存在很久,只是在近幾年隨着前端工程的復雜化,為了保證產品快速穩定迭代而被引入了到了前端領域。我們需要持續集成基於以下兩點考慮:
- 快速發現錯誤。每完成一點更新,就集成到主干,可以快速發現錯誤,定位錯誤也比較容易。
- 防止分支大幅偏離主干。如果不是經常集成,主干又在不斷更新,會導致以后集成的難度變大,甚至難以集成。
持續集成已經有很多平台,例如jekins,travis cli等平台都支持我們的集成工作。我這里使用與git配套的travis cli來集成部署集成我們的代碼。首先我們提交代碼到git倉庫中:
git push origin master
接着,我們用github授權登陸Travis Cli上找到激活倉庫集成測試的按鈕,選擇需要集成的倉庫:
然后我們在項目中創建.travis.yml文件,告訴travis我們的集成測試流程:
language: node_js
sudo: false
cache:
apt: true
directories:
- node_modules
node_js: stable
install:
- npm install -D
scripts:
- npm run cover
deploy:
provider: npm
email: your-email-address
api_key: "$NPM_TOKEN"
skip_cleanup: true
$NPM_TOKEN是你本地的npm auth_key值,cat ~/.npmrc
可以查看,從中拷貝然后把這個值放到travis cli里面去,發布的時候就自動用你的信息對包進行發布處理操作。我們用github的權限對travis-cli進行授權驗證,然后導入你的倉庫代碼,點擊進入,再點擊右上角的more option->setting->Environment variables ,將你本地的auth_key添加到變量環境當中:
隨后我們把代碼提交上去,travis會按照我們編寫的yml文件對項目進行集成測試,如果成功,它會繼續發布到npm上去,如果報錯則停止構建流程:
這樣,每當我們把代碼推送到遠程,集成框架就會幫我們自動測試校驗,並且發布到npm上去。減少了我們的人工測試和發布的流程處理,從而完成一個軟件的完整發布流程。
由於我們是在master分支開發的項目,不需要合並多個分支。所以到此為止,我們的模塊開發流程就基本完成了一個閉環。
puppy-cli
本篇從頭到尾講解了一個npm 包的編寫過程,創建倉庫-開發-測試-部署-集成。可以看到,這樣的一個流程很長,而且里面的每個工具如果每次都從頭開始配置的話會較為繁瑣。為了簡化流程,我們需要把所有這些步驟統一成一套SOP,並且盡量地簡化每個工具的安裝,配置細節。正是因此,很多cli工具才應運而生。用cli工具,只需要幾行命令,就能夠幫助我們快速創建模板,自動測試發布,集成操作,從而快速提高我們的工作效率,讓開發人員只需要關注在業務邏輯即可。我把工作中的業務功能就抽象成了一個工具,puppy-cli。
總結
本篇博文通過介紹編寫一個npm
模塊的大致流程,介紹了前端開發領域中應用開發的大致通用規范。不同的業務形態可能在不同的階段有所差異:例如編寫前端業務項目時我們較多的使用webpack構建工具,並且還需要自動化UI測試,和業務測試介入。在發布部署環節上也會因為公司環境不同而表現不一。無論如何了解這些工具和發布的流程,有助於你了解整個前端工程,而不僅僅局限在編寫業務代碼的環境上😂。