背景
日常開發(fā)中,一個項目的代碼往往不只在一個項目倉庫下,一些功能獨(dú)立、復(fù)用性強(qiáng)的組件或者工具函數(shù)等,可抽離成單獨(dú)的通用組件,通過依賴包的形式安裝進(jìn)來,其他同學(xué)也可以很方便地在其他項目中調(diào)用。比如業(yè)務(wù)項目叫my-project,抽離的組件庫@cute/hello發(fā)布到npm上,項目中通過npm install @cute/hello --save,就可以直接引用組件庫了。
調(diào)用組件庫是一件很快樂的事情,只需要調(diào)調(diào)API就能實(shí)現(xiàn)。但是如果組件庫有bug,你想在組件庫下調(diào)試完畢后,在真實(shí)有問題的項目環(huán)境去開發(fā)調(diào)試,卻無從下手。有人想到可以先npm publish發(fā)布一個測試的beta包,再在項目中npm install,但這樣一來一回頻繁調(diào)試的時候就浪費(fèi)了很多時間,況且第三方組件庫不允許你去操作發(fā)布測試的。怎么能做到組件庫開發(fā)和調(diào)試時像內(nèi)嵌在項目的代碼一樣流暢,無額外感知成本呢?那么這就涉及到組件庫的本地調(diào)試了。

下面會先介紹三種常見組件庫本地調(diào)試方法,最后會總結(jié)一種自動化調(diào)試方案:使用nodemon+yalc實(shí)現(xiàn)自動化調(diào)試本地組件庫。想直接看自動化調(diào)試的可以跳到最后面。
1、 常用的npm link方式
2、 修改項目package.json依賴路徑
3、 復(fù)制構(gòu)建的組件庫產(chǎn)物到項目庫的node_modules下
4、 自動化調(diào)試方案:nodemon+yalc
說明:文中所講的組件庫、library、依賴包、開源庫等詞匯在此均指同一概念,即抽象出來的通用組件。
以往的調(diào)試方式
大致來看,會有幾種常見的調(diào)試方式,但是都有其局限性。
1. 常用的npm link方式
通過npm link的方式建立組件庫和項目之間的聯(lián)系,這也是較常用的方式。每當(dāng)組件庫代碼變更后,通過在組件庫發(fā)布代碼所在目錄執(zhí)行以下
npm run build // 執(zhí)行構(gòu)建
cd dist // 進(jìn)入構(gòu)建后結(jié)果目錄
npm link // 將 library 鏈接到全局
然后到項目所在目錄重新綁定并重啟服務(wù)。
npm link @cute/hello
npm run start // 對應(yīng)到具體執(zhí)行腳本

npm link后輸出和項目綁定link示例如下,通過執(zhí)行發(fā)現(xiàn),組件庫npm link后會在發(fā)布代碼目錄下也生成一個node_modules會用來存放組件庫自身依賴的dependencies。my-project執(zhí)行npm link @cute/hello后會把組件庫發(fā)布目錄下的代碼都同步到項目的node_modules下了。
? cute-hello git:(xxx) cd dist
? dist git:(xxx) npm link
npm notice created a lockfile as package-lock.json. You should commit this file.
added 4 packages from 6 contributors and audited 4 packages in 4.75s
found 0 vulnerabilities
/usr/local/lib/node_modules/@cute/hello -> /Users/user/cute-hello/dist
? my-project git:(xxx) npm link @cute/hello
/Users/user/my-project/node_modules/@cute/hello -> /usr/local/lib/node_modules/@cute/hello -> /Users/user/cute-hello/dist
my-project 與本地組件庫 npm link 后,它的 node_modules 中的 @cute/hello 代碼是本地組件庫發(fā)布目錄 link 后的代碼。
每次組件庫代碼更改后,在組件庫進(jìn)行 npm run build 操作后,項目執(zhí)行 npm link 后,它會去 link 的地址去找資源文件(也就是組件庫),所以它的資源文件不在項目下,webpack不會對其做預(yù)編譯,所以如果組件庫本身還依賴其他的依賴庫,需要每次構(gòu)建后在組件庫執(zhí)行npm link,它才會自動生成組件庫自身依賴并注入 node_modules 中 ,這樣項目庫才能找到組件庫的依賴,不然會導(dǎo)致實(shí)際構(gòu)建或者運(yùn)行時報錯,項目庫會找不到組件庫自身的依賴。

調(diào)試完畢后解除link操作,項目重新 npm install 依賴
npm uninstall -g @cute/hello // 解除組件全局link
整個調(diào)試流程如下圖。

操作成本:
- 每次組件庫修改需要build構(gòu)建
- 組件庫有自身依賴時構(gòu)建后需執(zhí)行npm link和項目重啟服務(wù)
- 結(jié)束調(diào)試后解除link,安裝正確的生產(chǎn)依賴包
- 組件庫和項目庫都有調(diào)試成本
適用場景:
- 在一些命令行工具庫里面尤其好用,完全零調(diào)試成本,全局的命令行工具甚至不需要在項目庫安裝依賴包就能調(diào)試;
- 組件庫自身沒有依賴的簡單情況下
2. 修改項目package.json依賴路徑
修改 package.json 中組件庫為本地組件庫路徑,需要在項目庫重新執(zhí)行 npm install 安裝依賴。但是項目庫中安裝依賴,實(shí)際上是把依賴安裝到組件庫下了,項目下并沒有把組件庫自身依賴安裝到項目下。
// package.json
"dependencies": {
"@cute/hello": "file:/Users/user/cute-hello/dist",
}
可以看到 package-lock.json 中組件自動引用本地相對路徑
// package-lock.json
"@cute/hello": {
"version": "file:../cute-hello/dist",
"requires": {
...
},
}
每次組件庫代碼更改后,在組件庫進(jìn)行npm run build構(gòu)建,這里同樣會面臨像npm link那樣的尷尬處境,項目會去組件庫找組件的自身依賴,如果沒有找到則會啟動錯誤,所以組件庫每次構(gòu)建完你都需要在項目重新執(zhí)行npm install安裝組件庫的自身依賴。
調(diào)試完畢提交代碼及上線前需要手動把package.json更新回正確的依賴包地址。
整個調(diào)試流程如下圖。

操作成本:
每次組件庫修改需要build構(gòu)建
項目重新執(zhí)行npm install
結(jié)束調(diào)試后重新安裝正確的生產(chǎn)依賴包
上線前需要手動修改package.json的正確依賴包地址
適用場景:
組件庫自身沒有依賴的簡單情況下
3. 復(fù)制組件庫產(chǎn)物到項目庫node_modules下
在組件庫構(gòu)建后,將dist下的構(gòu)建產(chǎn)物復(fù)制到項目 node_modules 的@cute/hello目錄下,組件庫下執(zhí)行命令:
cp -r dist /Users/user/my-project/node_modules/@cute/hello
每次組件庫代碼更改后,都需要在組件庫進(jìn)行npm run build操作,而且項目的node_modules下組件庫復(fù)制完可能不會觸發(fā)熱更新,需要刪掉項目下組件庫再執(zhí)行cp命令,那么命令改成先刪除再復(fù)制,這樣就可觸發(fā)項目的熱更新了。
rm -rf /Users/user/my-project/node_modules/cute/hello && cp -r dist /Users/user/my-project/node_modules/@cute/hello
整個調(diào)試流程如下圖。

操作成本:
每次組件庫修改需要build構(gòu)建
每次需要在組件庫執(zhí)行cp命令
結(jié)束調(diào)試后重新安裝正確的生產(chǎn)依賴包
只需要在組件庫下執(zhí)行一些操作命令,項目庫無額外操作成本
適用場景:
- 基本都能適用
自動化調(diào)試流程:nodemon+yalc
自動化調(diào)試流程依賴【文件監(jiān)控開源庫 nodemon】和【本地依賴管理開源庫 yalc】,大致思路是用nodemon監(jiān)聽文件修改,然后自動執(zhí)行組件庫打包,再使用yalc進(jìn)行組件庫發(fā)布。
nodemon 是一個用來監(jiān)視應(yīng)用程序中文件更改并自動重啟服務(wù)的開源庫,僅用于開發(fā)環(huán)境(推薦),他不影響項目代碼,可以理解為是把node替換成nodemon執(zhí)行命令。
yalc 是一個類似于本地化 npm 的解決方案,它在本地環(huán)境中創(chuàng)建了一個共享的 library 存儲庫( ~/.yalc ),當(dāng)你需要本地依賴時可以快速從存儲庫中拉取資源進(jìn)行使用。它與 npm link 不同就是 npm link 中組件庫和項目引用的依賴是映射到一個地方的,當(dāng)項目庫啟動時會在依賴下去尋找其他依賴,如果找不到相關(guān)依賴,應(yīng)用就會異常;如果組件庫的編譯規(guī)則和項目的編譯規(guī)則不匹配,也同樣會出問題。而 yalc 就是相當(dāng)于將文件復(fù)制進(jìn)依賴目錄從而能正常運(yùn)行。

更多詳情可以看以下鏈接
● yalc: https://github.com/whitecolor/yalc
● nodemon: https://github.com/remy/nodemon
自動化流程
自動化流程通過使用nodemon監(jiān)聽文件修改后,自動執(zhí)行組件庫打包,再使用 yalc push 進(jìn)行組件庫發(fā)布,項目只要 yalc add [package] 后,后續(xù)就能直接熱更新下預(yù)覽變更了。具體實(shí)現(xiàn)流程下面會介紹。

首先,全局安裝nodemon和yalc:
npm install -g yalc // 全局安裝
npm install -D nodemon // 組件庫安裝
// 或
yarn global add yalc
yarn global add nodemon -D
組件庫單獨(dú)執(zhí)行 yalc publish 時會把當(dāng)前組件庫下相關(guān)文件存儲到本地共享的全局存儲中,除了不會發(fā)布到真實(shí)遠(yuǎn)端倉庫外,都和npm真實(shí)發(fā)包無異。更好用的是 yalc publish --push 或者簡稱 yalc push ,它會將你的依賴發(fā)布到本地存儲庫(更新狀態(tài)),并將所有更改傳播到現(xiàn)有通過 yalc 安裝的依賴中,下面會用到。
dist git:(xxx) ? yalc push
@cute/hello@2.0.0-beta.2 published in store.
在組件庫的 package.json 中添加 nodemon 腳本命令,這里可以新建一個 nodemon.json 的配置文件去配置詳細(xì)的信息(具體配置可以去官網(wǎng)查看)。nodemon 啟動監(jiān)聽,當(dāng)監(jiān)聽文件變化時自動 build 構(gòu)建,并將庫 yalc push 發(fā)布到緩存中。
如果當(dāng)前構(gòu)建后,package.json 中 private: true 時,直接 yalc push 會報錯:Will not publish package with private: true use --private flag to force publishing. 需要使用 yalc push --private 強(qiáng)制發(fā)布。
{
"scripts": {
"build": "node scripts/dist.js",
"build:watch": "nodemon --config 'nodemon.json' -x 'npm run build && cd dist && yalc push --private'"
},
}
// nodemon.json
{
"restartable": "rs",
"ignore": [
".git",
"node_modules/**/node_modules",
"package.json",
"nodemon.json",
"dist",
],
"verbose": true,
"execMap": {
"js": "node --harmony"
},
"events": {
"restart": "osascript -e 'display notification \"App restarted due to:\n'$FILENAME'\" with title \"nodemon\"'"
},
"watch": ["src"],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json,css,scss,vue"
}
上面組件庫的準(zhǔn)備工作完成后,項目下只要執(zhí)行 yalc add @cute/hello ,在項目根目錄下就會自動生成一個 yalc.lock 文件和 .yalc 目錄,.yalc 目錄類似 node_modules 那樣,它存放著從存儲庫中拉取下來的依賴,yalc.lock 記錄當(dāng)前被替換的組件庫版本信息,這些信息用來后面還原依賴包用。同時 my-projecy 中的 node_modules 的組件庫也被替換成 yalc 緩存中的組件庫了。
my-project git:(xxx) yalc add @cute/hello
Package @cute/hello@2.0.0-beta.2 added ==> /Users/user/my-project/node_modules/@cute/hello
package.json的組件庫地址自動注入了 file:.yalc/@cute/hello 。
"dependencies": {
"@cute/hello": "file:.yalc/@cute/hello",
}
項目根目錄下自動生成了 yalc.lock 文件和 .yalc 目錄:
# yalc.lock
{
"version": "v1",
"packages": {
"@cute/hello": {
"signature": "519aa4086b04e080b8d9731188dd162d",
"file": true,
"replaced": "@cute/hello@2.0.0-beta.1"
}
}
}

此時,就可以快樂地進(jìn)行組件庫和項目庫之間的調(diào)試了,不需要額外的操作成本,所改代碼即所視。
調(diào)試完畢后,要提交代碼部署到公測環(huán)境時,執(zhí)行 yalc remove --all(如果只引了一個依賴包也可直接 yalc remove [package],yalc remove --all 會將當(dāng)前所有從 yalc 存儲中拉取的都還原),即可一鍵還原安裝包現(xiàn)場,上面說到的package.json會自動還原了依賴包版本、.yalc、yalc/lock都自動去掉了,還原了開發(fā)調(diào)試前的現(xiàn)場,但是node_modules中的對應(yīng)的組件庫也去掉了,所以完成調(diào)試后本地只需要重新npm install安裝正常版本即可后續(xù)開發(fā)。
? my-project git:(xxx) ? yalc remove --all
Removing installation of @cute/hello in /Users/user/my-project
總結(jié)
總結(jié)一下,自動化調(diào)試流程就是在組件庫使用nodemon監(jiān)聽文件變更后,自動執(zhí)行 build 構(gòu)建和 yalc push,項目中通過 yalc add [package] 從本地存儲中拉取依賴包進(jìn)行調(diào)試,調(diào)試完成后 yalc remove --all 一鍵恢復(fù)調(diào)試前現(xiàn)場。
其實(shí)以往的調(diào)試方式都可以加 nodemon 命令去監(jiān)聽達(dá)到自動化調(diào)試的目的,但是卻有一些局限性(如下表),縱觀來看 yalc 的方式更加通用且邏輯清晰,組件庫和項目庫不需要花太多時間去理會中間的執(zhí)行過程,只需要像 npm 安裝依賴一樣,在項目中通過 yalc 引入多個組件庫進(jìn)行調(diào)試,調(diào)試完畢后 yalc remove --all 即可清除所有調(diào)試庫。
| 調(diào)試方式 \ 對比 | nodemon改造 | 局限 |
|---|---|---|
| npm link | 組件庫nodemon監(jiān)聽文件變化->build構(gòu)建->npm link | 項目需要重啟服務(wù);編譯規(guī)則不匹配,也可能會出問題 |
| cp復(fù)制構(gòu)建產(chǎn)物 | 組件庫nodemon監(jiān)聽文件變化->build構(gòu)建->scripts中添加cp命令 | 如果有多個項目在用組件庫,需要寫多套cp命令調(diào)試 |
| 修改package.json依賴庫地址 | 組件庫nodemon監(jiān)聽文件變化->build構(gòu)建->自動進(jìn)入項目庫執(zhí)行npm install | 項目庫每次install很繁瑣,調(diào)試完成后項目的依賴地址需要手動恢復(fù),這樣一點(diǎn)也不cool |
更多閱讀
● npm link:https://docs.npmjs.com/cli/v8/commands/npm-link
● yalc: https://github.com/whitecolor/yalc
● nodemon: https://github.com/remy/nodemon