pnpm+workspace+changesets構(gòu)建你的monorepo工程
什么是monorepo?以及和multirepo的區(qū)別是什么?
關(guān)于這些問題,在之前的一篇介紹lerna的文章中已經(jīng)詳細(xì)介紹過,感興趣的同學(xué)可以再回顧下。
簡(jiǎn)而言之,monorepo就是把多個(gè)工程放到一個(gè)git倉(cāng)庫(kù)中進(jìn)行管理,因此他們可以共享同一套構(gòu)建流程、代碼規(guī)范也可以做到統(tǒng)一,特別是如果存在模塊間的相互引用的情況,查看代碼、修改bug、調(diào)試等會(huì)更加方便。
什么是pnpm?pnpm是新一代的包管理工具,號(hào)稱是最先進(jìn)的包管理器。按照官網(wǎng)說法,可以實(shí)現(xiàn)節(jié)約磁盤空間并提升安裝速度和創(chuàng)建非扁平化的node_modules文件夾兩大目標(biāo),具體原理可以參考pnpm官網(wǎng)。
pnpm提出了workspace的概念,內(nèi)置了對(duì)monorepo的支持,那么為什么要用pnpm取代之前的lerna呢?
這里我總結(jié)了以下幾點(diǎn)原因:
lerna已經(jīng)不再維護(hù),后續(xù)有任何問題社區(qū)無法及時(shí)響應(yīng)
pnpm裝包效率更高,并且可以節(jié)約更多磁盤空間
pnpm本身就預(yù)置了對(duì)monorepo的支持,不需要再額外第三方包的支持
onemorething,就是好奇心了?
如何使用pnpm來搭建menorepo工程安裝pnpm$?npm?install?-g?pnpm??v7版本的pnpm安裝使用需要node版本至少大于v14.19.0,所以在安裝之前首先需要檢查下node版本。
工程初始化為了便于后續(xù)的演示,先在工程根目錄下新建packages目錄,并且在packages目錄下創(chuàng)建pkg1和pkg2兩個(gè)工程,分別進(jìn)到pkg1和pkg2兩個(gè)目錄下,執(zhí)行npminit命令,初始化兩個(gè)工程,package.json中的name字段分別叫做@qftjs/menorepo1和@qftjs/monorepo2(PS:@qftjs是提前在npm上創(chuàng)建好的組織,沒有的話需要提前創(chuàng)建)。
為了防止根目錄被發(fā)布出去,需要設(shè)置工程工程個(gè)目錄下package.json配置文件的private字段為true。
為了實(shí)現(xiàn)一個(gè)完整的例子,這里我使用了father-build對(duì)模塊進(jìn)行打包,father-build是基于rollup進(jìn)行的一層封裝,使用起來更加便捷。
在pkg1和pkg2的src目錄下個(gè)創(chuàng)建一個(gè)index.ts文件:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;分別在pkg1和pkg2下新增.fatherrc.ts和tsconfig.ts配置文件。
//?.fatherrc.tsexport?default?{??target:?'node',??cjs:?{?type:?'babel',?lazy:?true?},??disableTypeCheck:?false,};//?tsconfig.ts{??"include":?["src",?"types",?"test"],??"compilerOptions":?{????"target":?"es5",????"module":?"esnext",????"lib":?["dom",?"esnext"],????"importHelpers":?true,????"declaration":?true,????"sourceMap":?true,????"rootDir":?"./",????"strict":?true,????"noImplicitAny":?true,????"strictNullChecks":?true,????"strictFunctionTypes":?true,????"strictPropertyInitialization":?true,????"noImplicitThis":?true,????"alwaysStrict":?true,????"noUnusedLocals":?true,????"noUnusedParameters":?true,????"noImplicitReturns":?true,????"noFallthroughCasesInSwitch":?true,????"moduleResolution":?"node",????"baseUrl":?"./",????"paths":?{??????"*":?["src/*",?"node_modules/*"]????},????"jsx":?"react",????"esModuleInterop":?true??}}全局安裝father-build:
$?pnpm?i?-Dw?father-build最后在pkg1和pkg2下的package.json文件中增加一條script:
{??"scripts":?{????"build":?"father-build"??}}這樣在pkg1或者pkg2下執(zhí)行build命令就會(huì)將各子包的ts代碼打包成js代碼輸出至lib目錄下。
要想啟動(dòng)pnpm的workspace功能,需要工程根目錄下存在pnpm-workspace.yaml配置文件,并且在pnpm-workspace.yaml中指定工作空間的目錄。比如這里我們所有的子包都是放在packages目錄下,因此修改pnpm-workspace.yaml內(nèi)容如下:
packages:??-?'packages/*'初始化完畢后的工程目錄結(jié)構(gòu)如下:
.├──?README.md├──?package.json├──?packages│???├──?pkg1│???│???├──?package.json│???│???├──?src│???│???│???└──?index.ts│???│???└──?tsconfig.json│???└──?pkg2│???????├──?package.json│???????├──?src│???????│???└──?index.ts│???????└──?tsconfig.json├──?pnpm-workspace.yaml└──?tsconfig.root.json安裝依賴包使用pnpm安裝依賴包一般分以下幾種情況:
全局的公共依賴包,比如打包涉及到的rollup、typescript等
pnpm提供了-w,--workspace-root參數(shù),可以將依賴包安裝到工程的根目錄下,作為所有?package的公共依賴。
比如:
$?pnpm?install?react?-w如果是一個(gè)開發(fā)依賴的話,可以加上-D參數(shù),表示這是一個(gè)開發(fā)依賴,會(huì)裝到pacakage.json中的devDependencies中,比如:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;0給某個(gè)package單獨(dú)安裝指定依賴
pnpm提供了--filter參數(shù),可以用來對(duì)特定的package進(jìn)行某些操作。
因此,如果想給pkg1安裝一個(gè)依賴包,比如axios,可以進(jìn)行如下操作:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;1需要注意的是,--filter參數(shù)跟著的是package下的package.json的name字段,并不是目錄名。
關(guān)于--filter操作其實(shí)還是很豐富的,比如執(zhí)行pkg1下的scripts腳本:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;2filter后面除了可以指定具體的包名,還可以跟著匹配規(guī)則來指定對(duì)匹配上規(guī)則的包進(jìn)行操作,比如:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;3此命令會(huì)執(zhí)行所有package下的build命令。具體的用法可以參考filter文檔。
模塊之間的相互依賴
最后一種就是我們?cè)陂_發(fā)時(shí)經(jīng)常遇到的場(chǎng)景,比如pkg1中將pkg2作為依賴進(jìn)行安裝。
基于pnpm提供的workspace:協(xié)議,可以方便的在packages內(nèi)部進(jìn)行互相引用。比如在pkg1中引用pkg2:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;4此時(shí)我們查看pkg1的package.json,可以看到dependencies字段中多了對(duì)@qftjs/monorepo2的引用,以workspace:開頭,后面跟著具體的版本號(hào)。
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;5在設(shè)置依賴版本的時(shí)候推薦用workspace:*,這樣就可以保持依賴的版本是工作空間里最新版本,不需要每次手動(dòng)更新依賴版本。
當(dāng)pnpmpublish的時(shí)候,會(huì)自動(dòng)將package.json中的workspace修正為對(duì)應(yīng)的版本號(hào)。
只允許pnpm當(dāng)在項(xiàng)目中使用pnpm時(shí),如果不希望用戶使用yarn或者npm安裝依賴,可以將下面的這個(gè)preinstall腳本添加到工程根目錄下的package.json中:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;6preinstall腳本會(huì)在install之前執(zhí)行,現(xiàn)在,只要有人運(yùn)行npminstall或yarninstall,就會(huì)調(diào)用only-allow去限制只允許使用pnpm安裝依賴。
Release工作流在workspace中對(duì)包版本管理是一個(gè)非常復(fù)雜的工作,遺憾的是pnpm沒有提供內(nèi)置的解決方案,一部分開源項(xiàng)目在自己的項(xiàng)目中自己實(shí)現(xiàn)了一套包版本的管理機(jī)制,比如Vue3、Vite等。
pnpm推薦了兩個(gè)開源的版本控制工具:
changesets
rush
這里我采用了changesets來做依賴包的管理。選用changesets的主要原因還是文檔更加清晰一些,個(gè)人感覺上手比較容易。
按照changesets文檔介紹的,changesets主要是做了兩件事:
Changesetsholdtwokeybitsofinformation:aversiontype(followingsemver),andchangeinformationtobeaddedtoachangelog.
簡(jiǎn)而言之就是管理包的version和生成changelog。
配置changesets安裝
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;7初始化
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;8執(zhí)行完初始化命令后,會(huì)在工程的根目錄下生成.changeset目錄,其中的config.json作為默認(rèn)的changeset的配置文件。
修改配置文件如下:
//?pkg1/src/index.tsimport?pkg2?from?'@qftjs/monorepo2';function?fun2()?{??pkg2();??console.log('I?am?package?1');}export?default?fun2;9說明如下:
changelog:changelog生成方式
commit:不要讓changeset在publish的時(shí)候幫我們做gitadd
linked:配置哪些包要共享版本
access:公私有安全設(shè)定,內(nèi)網(wǎng)建議restricted,開源使用public
baseBranch:項(xiàng)目主分支
updateInternalDependencies:確保某包依賴的包發(fā)生upgrade,該包也要發(fā)生versionupgrade的衡量單位(量級(jí))
ignore:不需要變動(dòng)version的包
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH:在每次version變動(dòng)時(shí)一定無理由patch抬升依賴他的那些包的版本,防止陷入major優(yōu)先的未更新問題
如何使用changesets一個(gè)包一般分如下幾個(gè)步驟:
為了便于統(tǒng)一管理所有包的發(fā)布過程,在工程根目錄下的pacakge.json的scripts中增加如下幾條腳本:
編譯階段,生成構(gòu)建產(chǎn)物
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;0清理構(gòu)建產(chǎn)物和node_modules
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;1執(zhí)行changeset,開始交互式填寫變更集,這個(gè)命令會(huì)將你的包全部列出來,然后選擇你要更改發(fā)布的包
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;2執(zhí)行changesetversion,修改發(fā)布包的版本
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;3這里需要注意的是,版本的選擇一共有三種類型,分別是patch、minor和major,嚴(yán)格遵循semver規(guī)范。
這里還有個(gè)細(xì)節(jié),如果我不想直接發(fā)release版本,而是想先發(fā)一個(gè)帶tag的prerelease版本呢(比如beta或者rc版本)?
這里提供了兩種方式:
手工調(diào)整
這種方法最簡(jiǎn)單粗暴,但是比較容易犯錯(cuò)。
首先需要修改包的版本號(hào):
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;4然后運(yùn)行:
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;5注意發(fā)包的時(shí)候不要忘記加上--tag參數(shù)。
通過changeset提供的Prereleases模式
利用官方提供的Prereleases模式,通過preenter<tag>命令進(jìn)入先進(jìn)入pre模式。
常見的tag如下所示:
名稱功能alpha是內(nèi)部測(cè)試版,一般不向外部發(fā)布,會(huì)有很多Bug,一般只有測(cè)試人員使用beta也是測(cè)試版,這個(gè)階段的版本會(huì)一直加入新的功能。在Alpha版之后推出rcReleaseCandidate)系統(tǒng)平臺(tái)上就是發(fā)行候選版本。RC版不會(huì)再加入新的功能了,主要著重于除錯(cuò)//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;6之后在此模式下的changesetpublish均將默認(rèn)走beta環(huán)境,下面在此模式下任意的進(jìn)行你的開發(fā),舉一個(gè)例子如下:
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;7完成版本發(fā)布之后,退出Prereleases模式:
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;8構(gòu)建產(chǎn)物后發(fā)版本
//?pkg2/src/index.tsfunction?fun2()?{??console.log('I?am?package?2');}export?default?fun2;9規(guī)范代碼提交代碼提交規(guī)范對(duì)于團(tuán)隊(duì)或者公司來說是非常重要的,養(yǎng)成良好的代碼提交規(guī)范可以方便回溯,有助于對(duì)本次提交進(jìn)行review,如果單純的只是要求團(tuán)隊(duì)成員遵循某些代碼提交規(guī)范,是很難形成強(qiáng)制約束的,現(xiàn)在我們就嘗試通過工具來約束代碼提交規(guī)范。
使用commitizen規(guī)范commit提交格式commitizen的作用主要是為了生成標(biāo)準(zhǔn)化的commitmessage,符合Angular規(guī)范。
一個(gè)標(biāo)準(zhǔn)化的commitmessage應(yīng)該包含三個(gè)部分:Header、Body和Footer,其中的Header是必須的,Body和Footer可以選填。
//?.fatherrc.tsexport?default?{??target:?'node',??cjs:?{?type:?'babel',?lazy:?true?},??disableTypeCheck:?false,};0Header部分由三個(gè)字段組成:type(必需)、scope(可選)、subject(必需)
Typetype必須是下面的其中之一:
feat:增加新功能
fix:修復(fù)bug
docs:只改動(dòng)了文檔相關(guān)的內(nèi)容
style:不影響代碼含義的改動(dòng),例如去掉空格、改變縮進(jìn)、增刪分號(hào)
refactor:代碼重構(gòu)時(shí)使用,既不是新增功能也不是代碼的bud修復(fù)
perf:提高性能的修改
test:添加或修改測(cè)試代碼
build:構(gòu)建工具或者外部依賴包的修改,比如更新依賴包的版本
ci:持續(xù)集成的配置文件或者腳本的修改
chore:雜項(xiàng),其他不需要修改源代碼或不需要修改測(cè)試代碼的修改
revert:撤銷某次提交
scope
用于說明本次提交的影響范圍。scope依據(jù)項(xiàng)目而定,例如在業(yè)務(wù)項(xiàng)目中可以依據(jù)菜單或者功能模塊劃分,如果是組件庫(kù)開發(fā),則可以依據(jù)組件劃分。
subject
主題包含對(duì)更改的簡(jiǎn)潔描述:
注意三點(diǎn):
使用祈使語(yǔ)氣,現(xiàn)在時(shí),比如使用"change"而不是"changed"或者”changes“
第一個(gè)字母不要大寫
末尾不要以.結(jié)尾
Body
主要包含對(duì)主題的進(jìn)一步描述,同樣的,應(yīng)該使用祈使語(yǔ)氣,包含本次修改的動(dòng)機(jī)并將其與之前的行為進(jìn)行對(duì)比。
Footer
包含此次提交有關(guān)重大更改的信息,引用此次提交關(guān)閉的issue地址,如果代碼的提交是不兼容變更或關(guān)閉缺陷,則Footer必需,否則可以省略。
使用方法:
commitizen和cz-conventional-changelog如果需要在項(xiàng)目中使用commitizen生成符合AngularJS規(guī)范的提交說明,還需要安裝cz-conventional-changelog適配器。
//?.fatherrc.tsexport?default?{??target:?'node',??cjs:?{?type:?'babel',?lazy:?true?},??disableTypeCheck:?false,};1工程根目錄下的package.json中增加一條腳本:
//?.fatherrc.tsexport?default?{??target:?'node',??cjs:?{?type:?'babel',?lazy:?true?},??disableTypeCheck:?false,};2接下來就可以使用$pnpmcommit來代替$gitcommit進(jìn)行代碼提交了,看到下面的效果就表示已經(jīng)安裝成功了。
commitlint&&husky前面我們提到,通過commitizen&&c
vscode如何安裝nodejs(vscode如何安裝python第三方庫(kù))
npminstall 在項(xiàng)目目錄下node_modules可見安裝好的第三方包 ExpressApp |–node_modules (3)運(yùn)行項(xiàng)目 npmstart 輸出如下: ExpressApp@0.0.0startd:\\Nodejs_Workspace\\ExpressApp node.\/bin\/www 注:npmstart指令會(huì)自動(dòng)執(zhí)行node.\/bin\/www 在瀏覽器中輸入,可訪問Express歡迎頁(yè)面 二、使用VSCode開發(fā)Nodejs 1、VSCode打...
vscode搭建vue開發(fā)環(huán)境?
onWindowChange:窗口失去焦點(diǎn)時(shí)(編輯器窗口的切換,桌面窗口的切換)自動(dòng)保存; 安裝ESLint vscode中安裝ESLint插件 然后配置vscode的setting.json File-Preference..."${workspaceRoot}" ], "pattern":{ "regexp":"^!(\\\\w+):(\\\\S+)?:(\\\\d+),(\\\\d+)(?:~(?:(\\\\d+),)?(\\\\d+))?:(.*)$", "...
手機(jī)怎么編輯網(wǎng)頁(yè)
npm install -server -g 這里我們?nèi)职惭b-server,這個(gè)時(shí)候啟動(dòng)-server服務(wù),默認(rèn)訪問的跟目錄是public,我們需要修改成本地編寫頁(yè)面的跟目錄,我的目錄:\/Users\/barry-yang\/Documents\/webworkspace cd \/Users\/barry-yang\/Documents\/webworkspace 然后終端輸入-server 手機(jī)訪問 手機(jī)地址輸入電腦的ip加端口號(hào)...
TypeScript4.5發(fā)布:新的擴(kuò)展名、新語(yǔ)法、新的工具類型...
npm:這一協(xié)議實(shí)際上是npm提供的,類似的還有file:以及workspace:(yarn2workspace、pnpmworkspace),你所安裝的實(shí)際上也是@types\/web這個(gè)包。 對(duì)于解析邏輯發(fā)生的變更,詳見compiler\/program.ts。新的內(nèi)置工具類型AwaitedTheAwaitedTypeandPromiseImprovements TS4.5引入了新的工具類型Awaited,表示一個(gè)Promise的resolve值類型,社區(qū)...
運(yùn)行npm start遇到這個(gè)如下錯(cuò)誤提示怎么破,win7下
E:\\workSpace\\nodejs\\helloworld>npm start > helloworld@0.0.0 start E:\\workSpace\\nodejs\\helloworld > node .\/bin\/www GET \/ 304 564.566 ms - - GET \/stylesheets\/style.css 304 2.819 ms - - 來到項(xiàng)目目錄下,然后我是用npm start。就啟動(dòng)服務(wù)了。你試一下用npm start ...
相關(guān)評(píng)說:
海南區(qū)三角: ______ matlab 的路徑要設(shè)置成需要轉(zhuǎn)換文件所在的位置;也應(yīng)該用 mcc -m ****.m -a arrayviewfunc.m workspacefunc.m;如果還有問題,將arrayviewfunc.m 和workspacefunc.m也復(fù)制存放在需要轉(zhuǎn)換文件的文件夾內(nèi);arrayviewfunc.m 和workspacefunc.m可在matlab 安裝包中搜索到,祝你操作順利;
海南區(qū)三角: ______ 把兩個(gè)break都改成return break是退出循環(huán) return是退出函數(shù) 補(bǔ)充:你的a沒有定義 試一下這樣子調(diào)用你的函數(shù):triarea(3,4,5)
海南區(qū)三角: ______ m文件如果是函數(shù)function開頭,比如function out=fun(in) 直接在workspace中調(diào)用out=fun(in) 若m文件是腳本,文件名為funfun 直接在workspace中輸入funfun就行了
海南區(qū)三角: ______ 對(duì)\符號(hào)進(jìn)行\(zhòng)\轉(zhuǎn)義跳引 path="d:\\01_workspace\\test"
海南區(qū)三角: ______ MATLAB在程序運(yùn)行過程中,將為每個(gè)函數(shù)分配它自己的變量空間,在函數(shù)退出之后,該變量空間也就立即被注銷.為將函數(shù)輸出到workspace,采用 assignin(WS,'name',V) 其中,WS為工作空間名稱,'name'為變量名,V是為該變量指派的值.例如 assignin('base','a',5); % 為該變量指派新的值 則在workspace中輸出了a這個(gè)變量,其值為5
海南區(qū)三角: ______ 有,其實(shí)就是當(dāng)前文件夾 import os os.getcwd() 得到的路徑就相當(dāng)于matlab的workspace 建議使用spyder,界面和matlab很相似,可以查看workspace,可以查看變量,有命令行,有腳本編輯器,而且整合的Ipython也很方便
海南區(qū)三角: ______ 工作空間其實(shí)并不是C語(yǔ)言的概念 而是編程工具(IDE)的概念, 而且并不是每個(gè)IDE都有 工作空間的英文名是workspace 其實(shí)翻譯成工作區(qū)更好一些.在IDE中, 最基本的管理單位是文件, 多個(gè)文件混合組織后,稱為項(xiàng)目(project) 多個(gè)項(xiàng)目組織后 稱為工作空間(workspace) workspace和project 都是用來管理代碼, 使操作更方便的.