開發流程
初始化
首先在npm官網進行註冊登入
執行npm init
,可以通過命令列進行一些初始化的設定,如果想快速進行設定,可以執行npm init -y
,會在專案的根目錄生成一個package.json的檔案,具體包含哪些配置可以參考官方文件,下面介紹一些常用的配置。
name:npm包的名稱
version:包的版本號
description:對包的功能進行描述
main:包的入口檔案,預設是index.js
repository:程式碼的託管資訊,一般是github地址
keywords:關鍵字資訊,便於包的搜尋
author:作者
license:開源協議,一般是MIT和ISC
bugs:提bug的頁面,預設是github的issue頁面
homepage:專案的主頁
最好增加README.md,用來對專案進行簡單的說明,比如如何安裝使用,以及一些api的介紹和例子。
在程式碼開發完成以後,在命令列進行npm登陸npm login
。
最後使用npm publish
對包進行釋出。
程式碼規範
使用eslint,對JavaScript的書寫規範做一定的限制,可以在一些通過配置的基礎上增加一些團隊自己的限制項。
使用stylelint對樣式檔案做一些規範化的工作,也可以根據團隊的需要做一些定製化。
使用.editorconfig來配置編輯器的規範,保證縮排和換行等的一致性。
SemVer
SemVer的中文名稱是語義化版本控制規範。npm預設使用SemVer來進行模組的版本控制。一個釋出到npm的包要嚴格遵守SemVer的版本規範,不然會發布失敗。
版本格式
主版本號.次版本號.修訂號,可以用x.y.z的寫法來簡單表示。
修訂號(patch):只有在做了向下相容的修正時才可以遞增,可以理解為bug fix版本
次版本號(minor):只有在新增了可以向下相容的新功能的時候,才可以遞增,可以理解為feature版本。
主版本號(major):只有在新增了無法向下相容的API的時候,才可以遞增。
先行版本
當要進行大版本迭代的時候,或者增加一些核心的功能,但又不能保證新版本百分之百正常,這個時候就可以釋出先行版本。SemVer規範中使用alpha、beta和rc來修飾先行版本。
alpha:內部版本
beta:公測版本
rc:Release candiate,正式版本的候選版本
先行版本的版本號可以使用:1.0.0-alpha、1.0.0-beta.1、1.0.0- rc.1、1.0.0-0.3.7等。
版本號的優先順序
進行版本號比較時,x、y、z依次比較
先行版本號的規則是rc > beta > alpha
1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0- rc.1 < 1.0.0複製程式碼
更多內容可以看SemVer
husky & lint-staged
在專案中需要單測和對程式碼規範的校驗,如果每次修改,都對專案的所有程式碼進行校驗,會有效能和時間上的浪費;還有如果老專案沒有接入單測和程式碼規範,那麼如果對所有的程式碼都進行校驗的話,會導致錯誤太多無法提交程式碼。現在專案中已經使用的方案是husky & lint-staged。
husky在安裝的時候,會執行這個包的npm install這個script,對專案的Git鉤子進行重寫,我們就可以在git的鉤子函式中做一些程式碼方面的校驗工作。lint-staged這個庫只會新加入暫存區的檔案進行相關的操作,這樣就可以優化觸發操作的檔案範圍。
package.json
{
...
"scripts": {
"lint": "eslint --fix src/",
"lint:style": "stylelint --fix 'src/**/*.less'",
"test": "cross-env BABEL_ENV=test jest --colors --config .jest.js",
"pre-commit": "lint-staged"
},
"lint-staged": {
"ignore": [
"build/*",
"node_modules"
],
"linters": {
"src/*.js": [
"eslint --fix",
"git add"
],
"src/**/*.less": [
"stylelint --fix",
"git add"
],
"src/components/**/*.js": [
"jest --findRelatedTests --config .jest.js",
"git add"
],
"src/utils/*.js": [
"jest --findRelatedTests --config .jest.js",
"git add"
]
}
}
...
}
複製程式碼
git commit & changelog
規範化commit資訊,有助於將修改的問題進行分類,快速定位修復的問題,並提取出有用的提交資訊來生成最終的changelog檔案。
社群中比較好的方案是commitizen和conventional-changelog。
commitizen
commitizen
用來規範commit message,比較主流是的是使用AngularJS的規範來編寫commit message。
全域性安裝commitizen
sudo npm install -g commitizen複製程式碼
然後在專案裡執行下面的語句,讓commitizen支援AngularJS的message規範。
commitizen init cz-conventional-changelog --save-dev --save-exact複製程式碼
執行以後,會在專案的devDependencies加入cz-conventional-changelog這個依賴,並在package.json中加入如下的配置項
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}複製程式碼
完成上面的步驟以後,以後所有的git commit 命令都用git cz來替換。
AngularJS的提交風格如下
<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>複製程式碼
由Header、Body和Footer三個部分組成,其中Header是必須的,Body和Footer都可以省略。
type表示commit的型別,有如下七種型別:
feat:新功能(feature)
fix:修補bug
docs:文件(documentation)
style: 格式(不影響程式碼執行的變動)
refactor:重構(即不是新增功能,也不是修改bug的程式碼變動)
test:增加測試
chore:構建過程或輔助工具的變動
scope表示這次commit的影響範圍
subject是commit的簡單描述,不能超過50個字元
body是對這次commit的具體描述,可以是多行的
footer只用於兩種情況
不相容變動,如果是上個版本不相容的改動,用BREAKING CHANGE作為開頭
關閉 Issue,例如 Closes #234
生成changelog
如果所有的提交記錄都符合AngularJS的規範,那麼可以使用命令來自動生成changelog檔案。
必須安裝conventional-changelog-cli的依賴
npm install --save-dev conventional-changelog-cli
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
}
}複製程式碼
生成的文件只會收集type為feat、fix還有Breaking changes這三種型別的提交記錄。
如果強制使用的話,可以validate-commit-msg來對commit message進行校驗,如果格式不符合,就阻止提交。
如果不想使用規範化的提交,也可以使用下面的方法,收集所有的提交資訊來生changelog。
gitCommitMsg.js
const { execFile } = require('child_process');
const fs = require('fs');
const path = require('path');
const formatOptions = ['log', '--pretty=format:%ad %cn committed %s %h', '--date=format:%Y-%m-%d'];
const writeStream = fs.createWriteStream(path.join(process.cwd(), 'CHANGELOG.md'));
const child = execFile('git', formatOptions, {
cwd: process.cwd(),
maxBuffer: Infinity,
});
child.stdout
.pipe(writeStream);複製程式碼
編譯和打包
在專案開發的時候都是通過npm去安裝第三方包到本地的node_modules裡面,而且為了加快專案的構建速度,會忽略對node_modules裡面模組的處理,所以這就需要我們在開發npm包的時候提前做好編譯打包的工作。
一般來說,用於node環境的包,只要提供符合CMD規範的包即可,但是用於web的包,就需要提供更多的選項。
lib:符合commonjs規範的檔案,一般放在lib這個資料夾裡面,入口是mian
es:符合ES module對方的檔案,一般放在es這個資料夾裡面,入口是module
dist:經過壓縮的檔案,一般是可以通過script標籤直接引用的檔案
babel VS TypeScript
Babel是JavaScript的一個編譯器,用來將ES6的程式碼轉換成ES5的程式碼,關於babel更多的介紹可以參考之前的文章babel從入門到放棄。
TypeScript是JavaScript的一個超集,支援JavaScript的多有語法和語義,對於一些新的語法也會有及時的跟進,並且在此之上提供了更多額外的特性,比如靜態型別和風豐富的語法。TS的程式碼也可以通過編譯轉換成正常的JavaScript程式碼,所有現在也有一種思路是用JavaScript的語法去進行開發,但是用TS的編譯器對程式碼進行轉換。
webpack VS rollup
webpack是現在主流的打包工具,有著活躍和龐大的社群支援。rollup號稱是下一代打包方案,很多實驗性的功能都是它最先實現的,比如scope hoisting 和tree shaking。webpack由於自己實現了一套類似於node的module方案,所以在打包檔案的大小上以及檔案的可讀性上都存在一定的問題,而且相比於webpack複雜的配置檔案,rollup的配置相來說更簡單。所以庫檔案的打包比較好的方案是rollup + babel。
持續迭代
在一般的迭代過程中,步驟可能是
修改完原生程式碼以後,提交這次的修改,執行git add . && git commit && git push
修改package.json中的version欄位,實現版本號的自增
執行git add . && git commit && git push
給這個版本打一個tag,git tag <package.version> && git push --tags
釋出到npm,執行npm publish
npm version
npm version用來自動更新npm包的version,對SemVer的版本規範有很好的支援。
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
例:初始版本為1.0.0
npm version prepatch
//預備補丁版本號 v1.0.1-0
npm version prerelease
//預釋出版本號 v1.0.1-1
npm version patch
//補丁版本號 v1.0.2
npm version preminor
//預備次版本號 v1.1.0-0
npm version minor
//次版本號 v1.1.0
npm version premajor
//預備主版本號 v2.0.0-0
npm version major
//主版本號 v2.0.0
常用的是major
,minor
和patch
,分別對應規範中的x,y,z。
當倉庫已經被git初始化了,那麼執行npm version
修改完版本號以後,還會執行git add 、git commit和git tag的命令,其中commit的資訊預設是自改完的版本號。如果想自定義commit的資訊,可以提供 -m 或者 —message 的選項,如果有"%s"的符號,會被替換為版本號。
npm version patch -m "Upgrade to %s for reasons"
npm version還支援pre和post的鉤子,可以利用這兩個鉤子函式做一些自動化的配置。
在preversion這個鉤子中,生成changelog檔案,並將新生成的檔案推入到快取區中。
在postversion這個鉤子中,進行倉庫和tag的推送。
簡化操作,可以做如下配置
{
"scripts": {
"simple": "node gitCommitMsg.js", //生成簡單的changelog檔案
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"preversion": "npm run changelog && git add CHANGELOG.md",
"postversion": "git push && git push --tags",
"x": "npm version major -m 'Upgrade version to %s '",
"y": "npm version minor -m 'Upgrade version to %s '",
"z": "npm version patch -m 'Upgrade version to %s '"
},
}複製程式碼
npm publish
和npm version一樣,在執行npm publish這個命令的時候,npm會依次執行scripts中的prepublish、publish、postpublish的命令,如果有定義的話。
npm5的版本中,prepublish用來代替prepublishOnly這個鉤子,只在publish之前進行呼叫,建議npm升級到5及以上的版本,保證鉤子的一致性。
在包釋出之前和之後,我們可以利用prepublish和postpublish這個兩個鉤子做一些相關的工作。
在開發中,我們都是使用ES6的語法來進行開發的,所以在釋出的時候會涉及到程式碼的編譯。一般的開源專案,比如redux、antd,都會提供最少三種的檔案格式
1、經過壓縮的dist檔案,一般放在dist資料夾中,可以用script進行直接引用
2、符合commonjs規範的檔案,一般放在lib資料夾中
3、符合ES6模組規範的檔案,一般放在es資料夾中
4、符合umd通過規範的檔案,在瀏覽器和node中都可以使用
所以具體的流程為:
prepublish,對包進行打包編譯
publish,只發布編譯後的檔案
postpublish,刪除編譯生成的檔案
"scripts": {
"es": "tools run es",
"lib": "tools run commonjs",
"dist:umd": "tools run dist:umd",
"dist:cjs": "tools run dist:cjs",
"dist:es": "tools run dist:es",
"dist:min": "tools run dist:min",
"compile": "npm run es && npm run lib",
"dist": "npm run dist:umd && npm run dist:cjs && npm run dist:es && npm run dist:min",
"prepublish": "npm run compile && npm run dist",
"postpublish": "rm -rf es && rm -rf lib && rm -rf dist",
}複製程式碼
npm tag
使用npm publish釋出包的時候,會有一個--tag的選項,如果不提供的話,會預設設定為latest,並且在使用npm install某個包的時候,預設也會安裝latest這個tag的包。
但是在進行包的迭代的時候,可能會需要釋出不同的版本來做新功能的測試,這時候就需要結合SemVer和--tag來進行相應的處理。
npm publish --tag <tagname>
使用上面的命令,可以釋出對應的dist-tag。
如果我們想進行下一個大版本的迭代,並用next的dist-tag來表示
npm publist --tag next
如果使用者想安裝這個tag下的包,可以使用下面的命令
npm install package@next
可以通過dist-tag來檢視某個包的dist-tag
npm dist-tag ls redux
latest: 4.0.0
next: 4.0.0-rc.1
當預發版本穩定以後,可以使用npm dist-tag add beta latest
把預發版本設定為穩定版本。
最終的package#scripts
{
"lint": "eslint --fix src/",
"lint:style": "stylelint --fix 'src/**/*.less'",
"test": "cross-env BABEL_ENV=test jest --colors --config .jest.js",
"pre-commit": "lint-staged",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"preversion": "npm run changelog && git add CHANGELOG.md",
"postversion": "git push && git push --tags",
"x": "npm version major -m 'Upgrade version to %s '",
"y": "npm version minor -m 'Upgrade version to %s '",
"z": "npm version patch -m 'Upgrade version to %s '",
"es": "tools run es",
"lib": "tools run commonjs",
"dist:umd": "tools run dist:umd",
"dist:cjs": "tools run dist:cjs",
"dist:es": "tools run dist:es",
"dist:min": "tools run dist:min",
"compile": "npm run es && npm run lib",
"dist": "npm run dist:umd && npm run dist:cjs && npm run dist:es && npm run dist:min",
"prepublish": "npm run compile && npm run dist"
"postpublish": "rm -rf es && rm -rf lib && rm -rf dist",
}複製程式碼
Others
files & .npmignore & .gitignore
有三個方法可以控制npm釋出的包中包含哪些檔案
package.json#files
files欄位是一個陣列,用來表示可以包含哪些檔案,格式和.gitignore的寫法一樣
.npmignore
這個檔案用來表示哪些檔案將被忽略,格式和.gitignore的寫法一樣
.gitignore
也可以用來表示要忽略哪些檔案
這三個的優先順序是files > .npmignore > .gitignore
files包含的檔案,就算出現在.npmignore和.gitignore,也不會被忽略。如果既沒有files欄位,也沒有.npmignore檔案,那麼npm會讀取.gitignore檔案,忽略裡面的檔案。
main & module & sideEffect
package.json#mai
n 和 package.json#module
這兩個欄位是用來指定npm包的入口檔案,但是兩者有一定的不同。
npm在一開始的時候,是node的包管理平臺,所有的包都是基於CommonJS 規範規範的,main這個欄位是npm自帶的,一般表示符合CommonJS規範的檔案入口。
rollup實現了基於ES模組靜態分析,對程式碼進行Tree Shaking,它通過識別package.json中的module欄位,將它當成是符合ES模組規範的檔案入口。webpack之後也進行跟進,也能識別module欄位,並且在webpack的預設配置中,module的優先順序要高於main,因此符合ES模組規範的程式碼能進行Tree Shaking,減少專案最終打包出來的程式碼。
因為一般的專案在配置babel的時候,為了提高構建速度,都會忽略node_modules裡面的檔案,所以module入口的檔案最好是符合ESmodule規範的ES5的程式碼,webpack最終會把ESmodule轉換為它自己的commonjs規範的程式碼。
package.json#sideEffect
這個欄位是webpack4中新增的一個特性,用來表示npm包的程式碼是否具有副作用。ES6的程式碼在經過babel編譯為ES5的程式碼後,就算是符合ES6的模組規範,也會出現UglifyJs無法Tree Shaking的問題。webpack4通過sideEffect這個欄位,使UglifyJs強行進行Tree Shaking。
sideEffect可以設定為Boolean或者陣列
當為false時,表明這個包是沒有副作用的,可以進行按需引用
如果為陣列時,陣列的每一項表示的是有副作用的檔案
在元件庫開發的時候,如果有樣式檔案,需要把樣式檔案的路徑放到sideEffect的陣列中,因為UglifyJs只能識別js檔案,如果不設定的話,最後打包的時候會把樣式檔案忽略掉。
{ "sideEffects": ["components/**/*.less"] }複製程式碼
npm register
npm全域性安裝後,它的register是 registry.npmjs.org ,如果你使用淘寶的映象重寫了register,那麼可能會在登陸和釋出的時候出錯。
npm config list @cfe:registry = "mirrors.npm.private.caocaokeji.cn/repository/…"
registry = "registry.npm.taobao.org/"
可以使用下面的命令進行登陸和釋出
npm login --registry registry.npmjs.org
npm publish --registry registry.npmjs.org
或者在開發npm包的時候,將registry換成npm的官方地址,開發完以後再換回淘寶的映象
npm config set set registry www.npmjs.com/
npm config set registry registry.npm.taobao.org
npm link
在開發包的時候,會遇到除錯問題,希望能夠一邊開發一邊除錯,不用頻繁的去釋出版本。
使用npm link可以達到這個效果,它會在在全域性的node_modules目錄中生成一個符號連結,指向模組的本地目錄。
假設你要開發一個包,叫tool,需要在本地的專案work-center中去使用 在命令列中進入tool的目錄,執行npm link這個命令,就會生成一個符號連結。
進入專案work-center的目錄,執行npm link tool,就可以使用這個包了。
tool的所有改動都會對映到安裝的專案中,但是帶來的問題就是一處改動多處影響。
在除錯結束後,執行npm unlink tool來刪除符號連結。
oh-my-zsh
在Mac上使用oh-my-zsh可以提高命令列的開發效率,具體的配置可以參考這篇文章mac下oh-my-zsh的配置
安裝了oh-my-zsh以後,可以簡化git的命令列操作,提高鍵盤的壽命,常用命令如下
zsh-git快捷鍵
gst - git status
gl - git pull
gp - git push
ga - git add
gcmsg - git commit -m
gco - git checkout
gcm - git checkout master
monorepo & lerna
monorepo 是單程式碼倉庫,與之對應的是multirepo,多程式碼倉庫。
monorepo是把所有的module都放在一個程式碼倉庫中,進行統一管理;multirepo是把module拆分開來,單獨去管理。multirepo的問題是issue、changelog和版本號不好管理;monorepo的問題是單個倉庫程式碼量比較大。現在一些主流的開發專案,比如babel、react、vue、vue-cli,都是使用monorepo的方式來管理程式碼倉庫的;rollup和antd是使用multirepo。
lerna是babel官方開源的monorepo管理工具。