大家好,我是若川。這是 源碼共讀 第三期活動,
紀年小姐姐
的第三次投稿。紀年小姐姐
學習完優化了自己的項目發布流程,而且回答了leader
對她的提問,來看看她的思考和實踐。
第三期是 Vue 3.2 發布了,那尤雨溪是怎么發布 Vue.js 的?。不知不覺,源碼共讀已經進行了快一個月,有些小伙伴表示對面試和工作很有幫助,學完立馬能用。如果你也感興趣可以加我微信
ruochuan12
參加。
1. 學習目標和資源準備
這一期閱讀的是 Vue3 源碼中的 script/release.js 代碼,也就是 Vue.js 的發布流程。在上一期源碼閱讀中從 .github/contributing.md[1] 了解到 Vue.js 采用的是 monorepo 的方式進行代碼的管理。
monorepo 是管理項目代碼的一個方式,指在一個項目倉庫 (repo) 中管理多個模塊/包 (package),不同于常見的每個 package 都建一個 repo。
剛好我最近搭建組件庫也是使用 monorepo 的方式去管理包。monorepo 有個缺點,因為每個包都維護著自己的 dependencies,那么在 install 的時候會導致 node_modules 的體積非常大。目前最常見的 monorepo 解決方案是使用 lerna 和 yarn 的 workspaces 特性去處理倉庫的依賴,我搭建的組件庫也是使用了 lerna 和 yarn。但 Vue3 的包管理沒有使用 lerna,它是怎么管理依賴包的版本號呢?讓我們跟著源碼一探究竟。
Lerna[2] 是一個管理工具,用于管理包含多個軟件包(package)的 JavaScript 項目,針對使用 git 和 npm 管理多軟件包代碼倉庫的工作流程進行優化。
學習目標:
1)學習 release.js 源碼,輸出記錄文檔。
資源準備:
Vue3 源碼地址:https://github.com/vuejs/vue-next
2. Yarn Workspace
//?vue-next/package.json?(多余的代碼已省略)
{"private":?true,"version":?"3.2.2","workspaces":?["packages/*"],"scripts":?{"release":?"node?scripts/release.js"}
}
Yarn 從 1.0 版開始支持 Workspace (工作區),Workspace 可以更好的統一管理有多個項目的倉庫。
管理依賴關系便捷:每個項目使用獨立的 package.json 管理依賴,可以使用 yarn 命令一次性安裝或者升級所有依賴,無需在每個目錄下分別安裝依賴
降低磁盤空間占用:可以使多個項目共享同一個 node_modules 目錄
3. release.js 文件解讀
先手動跑一遍 yarn run release --dry
,控制臺會輸出以下信息(多余信息已省略),從控制臺日志看出來,發布 Vue.js 會經歷以下幾個步驟:
//?確認發布版本號
??Select?release?type?...
>?patch?(3.2.3)minor?(3.3.0)major?(4.0.0)custom
//?執行測試用例
Running?tests...
//?更新依賴版本
Updating?cross?dependencies...
//?打包編譯所有包
Building?all?packages...
//?生成?changelog
conventional-changelog?-p?angular?-i?CHANGELOG.md?-s
//?提交代碼
Committing?changes...
//?發布包
Publishing?packages...
//?推送代碼到?GitHub
Pushing?to?GitHub...
初步了解發布流程后,來看看 release.js 源碼做了什么,先看入口函數 main()
main 函數
代碼太多就不貼代碼了,記錄一下思路和思考
確認要發布的版本:
如果從命令行獲取到了版本號,先驗證版本號規范,再次確認版本號
如果命令行沒有輸入版本號,會讓用戶選擇一個版本發布
確認版本號使用了一個庫叫 semver,它的作用是用于版本校驗比較。
//?目的是獲取命令行參數(也就是允許用戶自定義輸入版本號,比如?yarn?release?v3.5.0)
const?args?=?require('minimist')(process.argv.slice(2))
let?targetVersion?=?args._[0]
執行測試用例
const?execa?=?require('execa')
const?run?=?(bin,?args,?opts?=?{})?=>?execa(bin,?args,?{?stdio:?'inherit',?...opts?})
const?bin?=?name?=>?path.resolve(__dirname,?'../node_modules/.bin/'?+?name)if?(!skipTests?&&?!isDryRun)?{//?bin("jest")?先獲取?node_modules/.bin/jest?的目錄,run?的本質就是執行命令行//?這行代碼的意思就相當于在命令終端,項目根目錄運行 ./node_modules/.bin/jest 命令。await?run(bin('jest'),?['--clearCache'])await?run('yarn',?['test',?'--bail'])
}?else?{console.log(`(skipped)`)
}
更新依賴版本
//?1)獲取?packages?目錄下的所有包
const?packages?=?fs.readdirSync(path.resolve(__dirname,?'../packages')).filter(p?=>?!p.endsWith('.ts')?&&?!p.startsWith('.'))
//?1)獲取包的根目錄路徑
const?getPkgRoot?=?pkg?=>?path.resolve(__dirname,?'../packages/'?+?pkg)
//?2)更新根目錄和?packages?目錄下每個包的?package.json?的版本號
function?updateVersions(version)?{}
//?3)實現更新?package.json?版本號的,以及更新依賴包的版本號
function?updatePackage(pkgRoot,?version)?{}
//?4)實現更新與?vue?相關依賴包的版本號
function?updateDeps(pkg,?depType,?version)?{}
打包編譯所有包
這部分涉及另外一個文件 script/build.js,這個文件主要是將各個包打包在對應的目錄下,比如打包一個依賴就運行一次yarn build
,如果有多個包,就異步循環調用打包命令。核心代碼如下:
/***?迭代打包*?@param?{*}?maxConcurrency?最大并發*?@param?{*}?source?目錄*?@param?{*}?iteratorFn?構建函數(核心就是運行?build?命令)*?@returns*/
async?function?runParallel(maxConcurrency,?source,?iteratorFn)?{const?ret?=?[];const?executing?=?[];for?(const?item?of?source)?{const?p?=?Promise.resolve().then(()?=>?iteratorFn(item,?source));ret.push(p);if?(maxConcurrency?<=?source.length)?{const?e?=?p.then(()?=>?executing.splice(executing.indexOf(e),?1));executing.push(e);if?(executing.length?>=?maxConcurrency)?{await?Promise.race(executing);}}}return?Promise.all(ret);
}
生成 CHANGELOG 文件
主要運行的是這行命令:conventional-changelog -p angular -i CHANGELOG.md -s
提交代碼
先執行 git diff 命令,檢查文件是否有修改,如果有,執行 git add 和 git commit 命令
發布包
最后執行的命令是,yarn publish,發布新版本和打 Tag
推送到 GitHub
主要運行的命令:
打 tag:git tag ${version}
推送 tag:git push origin refs/tags/${version}
提交代碼到遠程倉庫:git push
至此,release 發布流程已經分析完了。

4. 感想
回答一下開篇的問題,Vue 是如何管理版本號呢?閱讀完源碼我們會分現,在發版的時候會統一更新所有包的 package.json 的版本號。對比我在搭建組件庫過程中使用的 lerna,其實 lerna 是把 release 這一套流程封裝成了一個包,它里面處理發包的流程跟 Vue Release 流程基本是一致的。
這次的源碼解讀解答了我的一些疑惑。在我搭建組件庫的過程中,我一開始了解到的是一個組件一個目錄,單包推送到 npm 私庫。這樣做的缺點很明顯,需要在每個目錄安裝一遍依賴,單獨處理版本號。后來了解到了 yarn workspace,知道它可以處理依賴安裝的問題,但版本號的處理還是沒有解決方案。于是我去尋找業內比較流行的解決辦法,發現大部分是使用了 lerna。
于是我向我的 TL 溝通詢問,可否采用 yarn + lerna 的方式來搭建組件庫。我記得特別清楚他反問我,問我 lerna 解決了什么問題,我支支吾吾回答了官網上的介紹,因為我當時對 lerna 的了解僅停留在官網以及它的常用命令,實際上我不知道它解決了什么問題。TL 見我答不上來,回復了我一句【如無必要,勿增實體】。
通過這次的源碼閱讀,我可以回答 TL 反問我的那個問題了,lerna 解決的是發包流程中版本號處理,自動生成 CHANGELOG 文件,提交代碼,發布包,推送到倉庫這幾個問題,它把這幾個流程封裝成命令供用戶使用。它不是搭建組件庫非必要引入的工具,雖然引用了 lerna 會增加了新的復雜度,但在不了解發包流程的前期使用 lerna 可以使組件庫開發者更專注于組件開發的工作上,而不需要過度關注如何發包。
5. 實踐
經過一番思考,我認為引入 lerna 確實給系統增加了一些復雜度,因為它要求開發人員額外學習 lerna 的一些知識和命令,增加了學習成本以及系統復雜度。我覺得可以參考 Vue 的 release.js,寫一個適用于項目的構建發版腳本用來發包,降低系統復雜度。
邏輯代碼基本與 Vue3 的 release.js 和 build.js 一致,去掉了一些沒必要的代碼,比如單元測試和一些環境判斷。還修改了一下 rollup.config.js 的配置,感覺用起來確實比 lerna 好用一些。最終效果如下:

參考資料
[1]
.github/contributing.md: https://github.com/vuejs/vue-next/blob/master/.github/contributing.md
[2]Lerna: https://www.lernajs.cn/
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
我讀源碼的經歷
初學者也能看懂的 Vue3 源碼中那些實用的基礎工具函數
老姚淺談:怎么學JavaScript?
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~