無腦發佈 npm
比如老王我,用npm init
新建一個包,改把改把,然後來個npm publish
,so easy ✌️!
Too young too naive, baby 👶!
請容我講述一些發佈過程中踩過的坑。
首先,算了也可以之後有空再說,我們需要通讀npm
的配置文檔。
通用性👷
指定發佈文件
利用package.json
中files
字段精簡發佈體積。
{
"files": ["dist", "lib", "module"]
}
若不指定files
,每次發佈會把所有不以.
開頭的文件都發布出去,導致發佈體積過大(node_modules
默認也不會被髮布)。
README.md
作爲主文檔,加不加都會發布,package.json
也是。
指定源代碼
{
"source": "src/index.ts",
"repository": {
"type": "git",
"url": "https://github.com/yourname/yourproject.git"
}
}
通常來說我是不在npm
發佈中包括源代碼的,因此都沒有加過source
字段,只是用repository
來告知一下git
倉庫地址即可。
如果倉庫是內部倉庫或私人倉庫並不對外,則source
字段就有用了,將源代碼發佈後可讓人幫忙debug
找問題。
注意如果有source
,則files
也要加上souce
對應的文件或文件夾。
發佈sourcemap
一般來說我們發佈的都是經過編譯的代碼,爲了給使用者方便調試,只要不是源碼,都要有對應的sourcemap
文件,例如發佈了一個dist/index.js
則也需要一個dist/index.js.map
文件與之配套。
指定安裝源
如果你從來不用私有源,可跳過該項。
利用.npmrc
指定安裝源,用於當前項目與你的全局配置區分開。
否則當前項目很可能指定的內部npm
源,導致外部用戶無法利用lock
文件安裝。
例如
registry=https://registry.npmjs.org/
精確指定dependencies
、devDependencies
、peerDependencies
dependencies
要儘量少,只有在運行時確實用到才放進去。
依賴的版本號要清晰指明,如"react": "16.x || 17.x"
否則,如果指定了"react": "17.0.0"
,則在使用了react
16的項目中,會引入兩份react
,造成一些莫名其妙的問題。
這種情況,react
應放到peerDependencies
中。
指定發佈目標
如果你從來不在私有源發佈,可跳過該項。
在package.json
中指定發佈地址,在當前包與全局配置不一致時非常必要。
{
"publishConfig": {
"registry": "https://registry.npmjs.org"
}
}
sideEffects
對應配置:
{ "sideEffects": false }
作用:在打包時進行treeshake
可根據是否使用而優化相關的代碼。
如果sideEffects
爲true
,則一旦引入,不管是否調用都不能被treeshake
掉。
專用性🥷
類型配套
無論針對哪個環境,目前自帶類型已經是既成事實的標配。
記得生成類型的.d.ts
文件,並在package.json
中指定。
{
"types": "type/index.d.ts",
"typings": "type/index.d.ts"
}
我一般會用一個專用的tsconfig.declaration.json
來專門生成類型:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"emitDeclarationOnly": true,
"declaration": true,
"outDir": "types"
}
}
作爲後端庫
package.json
中指定main
字段。
編譯結果需要在nodejs
環境中運行,輸出commonjs
格式模塊。
爲了兼容最新與將來,同時也要輸出esmodule
格式模塊。
相關配置:
{
"main": "lib/index.js",
"module": "module/index.js",
"jsnext:main": "module/index.js"
}
module
與jsnext:main
都是指esmodule
格式,只是爲了兼容某些特殊環境的別名。可能還有其他別名單我暫時就見過這倆。
其中module
中的文件推薦使用特定的後綴名,例如.esm.js
或.mjs
,但在一些工程相關工具中是否會有未知爲題,不好說。
未來已來,現在大部分前端工程工具都會優先使用module
指定的文件,單如果沒有指定module
,也會爲了兼容去加載main
。
作爲前端庫
前端庫其實要求比後端庫更高,爲啥?
因爲現代前端開發環境要求支持所有後端環境,並延伸出前端環境的額外支持。也就是說後端庫要求一般是前端庫要求的子集。
需要擴展的是純前端環境的運行格式,老格式amd
已經被淘汰可以不用考慮,現在基本都被umd
格式統一。
{
"main": "lib/index.js",
"module": "module/index.js",
"unpkg": "dist/index.js",
"umd:main": "dist/index.js",
"jsdelivr": "dist/index.umd.production.min.js"
}
其中unpkg
,umd:main
,jsdelivr
都是爲了更廣泛兼容的指向瀏覽器環境運行的同一個目標別名。
通常來說commonjs
,esmodule
,umd
都不會將其依賴的其他包包括進去,只是在運行時才加載。
還有一種情況,可能只有我自己用到過,就是發佈包中有些東西與外部環境有衝突,因此除了這些通用模式之外我又加了一個independent
(取名叫standalong
也比較合適)格式,將這個包的所有依賴都封裝進去,可以不依賴外部環境獨立使用。
例如mobx-value
的獨立運行文件。
注意瀏覽器環境輸出的都是優化後的.production.min
格式,也必須同時輸出.development
後綴的開發模式,爲了方便使用者調試方便。
因爲最大的使用者,往往就是我們自己,不要連自己都糊弄了事~
作爲命令行工具
多配置兼容
命令行工具一般需要很多參數,例如tsc
,當參數過多時沒人願意每次都輸入長長的參數,因此需要配置文件的支持。
那麼選哪種配置格式呢?
此時cosmiconfig隆重登場!以一句名言形容,小孩子才做選擇,成年人全都要!
兼容各種配置,各種位置,詳情參見其api
。
還有一點,如果需要讀取一些周邊的json
配置,不要用原生的JSON.parse
,很多json
是帶註釋的或者編寫不規範,用json5
讀取兼容好。
還有一個精簡版:lilconfig,功能差不多,我下次打算試試。
配置文件類型校驗
剛入門typescript
時,我嘗試用typescript
作爲配置文件,然後在運行時利用類型機制達到校驗配置的目的。
但這樣會丟失很多靈活性,限制死了配置文件的來源與格式,並由於庫的typescript
環境與應用所在的typescript
環境不一致,也導致了很多工程問題(對我說的就是ts-gear
)。
後來發現通過註釋文檔的方式,js
文件中也同樣可以校驗類型,而且js
文件對運行時更友好。
例如webpack.config.js
這樣配置
/**
* @type {import('webpack').Configuration}
* */
const config = {...}
export default config
配置文件運行時校驗
我們的程序要讀配置,但配置是使用者提供的,誰知道用戶會寫些什麼,即使有上面那步提到的類型校驗把關,也會有很多邊界問題類型根本管不了。
因此,運行時配置數據校驗就是必備環節。
不光是校驗不通過時終止運行,還必須給出一個合理且精準的錯誤提示。
推薦一個協議、兩個校驗工具與一個漂亮的格式化提示工具。
協議是json schema
,校驗工具爲joi
或ajv
,提示輸出工具爲chalk
。
指定可運行文件
在package.json
中指定bin
:
{
"bin": "bin/run.js"
}
對於大部分js腳本,都要在運行文件頭部指定運行環境。
#! /usr/bin/env node
然後別忘了在發佈前添加可執行屬性,務必整合在自動化發佈腳本中。
chmod +x bin/run.js
可調用api
例如babel
,我們不光能使用@babel/cli
在命令行使用,也可以在自己的程序裏import babel from 'babel'
來調用其api
。
一個命令行工具通常也是一個第三方庫,方便集成到調用者自身的腳本與環境中。
其他特定環境
例如針對react-native
,這個我就見過,沒實際用過。
{
"react-native": "dist/index.esm.js"
}
最後不論什麼格式,都記得輸出配套sourcemap
的.map
文件。
健壯性🏋
指定運行環境:engine與os
尤其對於命令行工具,這倆點很重要,不然很容易就換個人換個電腦就莫名報錯。
{
"engine": "node>=14",
"os": ["linux", "darwin"]
}
有否配套測試用例
- 有可運行的配套測試用例。
- 在
README.md
上有可見的測試覆蓋率統計,讓人可以放心使用。
測試用例放在哪?
最初我習慣按照jest
推薦的模式,將所有測試用例放在__tests__
文件夾內。
最近兩年看了好多別的語言的單測用例,我現在更傾向於將測試文件與源文件放在一起。因爲測試用例,就是源代碼的一部分!
比如以下這種目錄結構
src/setter.ts
src/setter.test.ts
測試運行時機
npm prepublishOnly
的鉤子一定要加上運行測試用例。
有餘力的情況,可以再配置個額外的流水線,github
上有好多免費的配套流水線,自己折騰折騰。
代碼校驗配套
項目必須有一個較好的文檔規則校驗流程,大多數情況我使用eslint
,然後配上airbnb
與prettier
的校驗規則。
校驗有兩個重要作用,一個是真的能解決很多隱性bug,另一個是代碼漂亮,之後看你項目源碼的人也會覺得舒服,關鍵是面試時也能拿的出手。
如果有面試者給我看自己的開源作品,如果代碼風格都不行,立即就判定不行,也不用再看什麼邏輯能力了,招進來也是挖坑。
好的代碼風格必須依賴校驗工具,最好把校驗流程也集成到發佈的鉤子上。
推廣性🤹
文檔
使用.markdownlint
配置規範自己的markdown
文檔,否則很容易寫飛了。
要不人家一看文檔,項目質量很容易就露餡了不是🤭
配套展示用例
- 一個方法是在項目中自帶一個可運行的樣例,讓人
clone
之後運行指定命令即可查看樣例。 - 更好一些,部署一個可以在線查看的例子,並在主文檔上附上直達鏈接。
- 更進一步,項目增大之後,需要說明的地方越來越多,一個
README
已經太長。使用docusaurus
等類似的工具部署一個獨立的文檔站點。
有否自動化版本管理
Why?因爲版本號與兼容性是強相關的,具體參考semver
規範。
- 使用
husky
/yorkie
等規範提交日誌。 - 使用
standard-version
等自動生成CHANGELOG
並根據規則自動提升版本號。
最後留個作業
- 你有什麼
npm
發佈時的關鍵經驗這裏沒提到的,幫我補充下🤝 - 當我們再一次運行
npm publish
,腦編譯一下,想想這期間都發生了些什麼,還少些什麼?
作者:京東零售 王凡
內容來源:京東雲開發者社區