8102年末如何開發和維護一個npm項目

開發流程

初始化

首先在npm官網進行註冊登陸javascript

運行npm init,能夠經過命令行進行一些初始化的設置,若是想快速進行設置,能夠運行npm init -y,會在項目的根目錄生成一個package.json的文件,具體包含哪些配置能夠參考官方文檔,下面介紹一些經常使用的配置。vue

  • name:npm包的名稱java

  • version:包的版本號node

  • description:對包的功能進行描述react

  • main:包的入口文件,默認是index.jswebpack

  • repository:代碼的託管信息,通常是github地址git

  • keywords:關鍵字信息,便於包的搜索github

  • author:做者web

  • license:開源協議,通常是MIT和ISCvue-cli

  • 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.0.0- rc.一、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只用於兩種狀況

    1. 不兼容變更,若是是上個版本不兼容的改動,用BREAKING CHANGE做爲開頭

    2. 關閉 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。

持續迭代

在通常的迭代過程當中,步驟多是

  1. 修改完本地代碼之後,提交此次的修改,運行git add . && git commit && git push

  2. 修改package.json中的version字段,實現版本號的自增

  3. 運行git add . && git commit && git push

  4. 給這個版本打一個tag,git tag <package.version> && git push --tags

  5. 發佈到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

經常使用的是majorminorpatch,分別對應規範中的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,都會提供最少三種的文件格式

一、通過壓縮的dist文件,通常放在dist文件夾中,能夠用script進行直接引用

二、符合commonjs規範的文件,通常放在lib文件夾中

三、符合ES6模塊規範的文件,通常放在es文件夾中

四、符合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#main 和 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管理工具。

相關文章
相關標籤/搜索