tsc性能優(yōu)化Project References使用詳解
什么是 Project References
在了解一個東西是什么的時候,直接看其官方定義是最直觀的
TypeScript: Documentation中對于Project References的介紹如下:
Project references are a new feature in TypeScript 3.0 that allow you to structure your TypeScript programs into smaller pieces.
這是TypeScript 3.0新增的特性,這個特性有啥用呢?
將我們的項目分成多個小的片段,也就是允許我們將項目進行分包模塊化
這樣一來我們在執(zhí)行tsc對項目進行構建的時候,無論是出于代碼轉譯成js的目的還是說出于單純的類型檢查(將compilerOptions.noEmit置為true)的目的
都可以嚴格按照自己的需要對想要被tsc處理的那部分代碼進行處理,而不是每次都對整個項目進行處理,這是我認為Project References的最大好處
就以一個前后端代碼在同一個倉庫里維護的項目為例,如果沒有Project References,那么我執(zhí)行tsc時,會對前后端模塊都進行類型檢查和轉譯
但實際上如果我只修改了前端部分的代碼,理所應當讓tsc只處理前端模塊,后端模塊的構建產物不需要進行重新構建,有了Project References,我們就能實現(xiàn)到這個需求,從而優(yōu)化項目的構建或類型檢查性能
相信大家對Project References有一個大概的認識了,接下來我們就開始實際動手體驗一下Project References加深我們對它的理解吧!
示例項目結構
. ├── package.json ├── pnpm-lock.yaml ├── src │ ├── __test__ // 單元測試 │ │ ├── client.test.ts // 前端代碼測試 │ │ ├── index.ts // 簡陋的單元測試 API │ │ └── server.test.ts // 后端代碼測試 │ ├── client // 前端模塊 │ │ └── index.ts │ ├── server // 后端模塊 │ │ └── index.ts │ └── shared // 共享模塊 -- 包含通用的工具函數(shù) │ └── index.ts └── tsconfig.json // TypeScript 配置
這是一個很常見的項目目錄結構,有前端代碼,有后端代碼,也有通用工具函數(shù)代碼以及前后端的單元測試代碼
它們的依賴關系如下:
- client 依賴 shared
- server 依賴 shared
__test__依賴 client 和 server- shared 無依賴
不使用 Project References 帶來的問題
現(xiàn)在整個項目只有一個tsconfig.json位于項目根目錄下,其內容如下:
{
"compilerOptions": {
"target": "ES5",
"module": "CommonJS",
"strict": true,
"outDir": "./dist"
}
}
如果我們執(zhí)行tsc,它會將各個模塊的代碼都打包到項目根目錄的dist目錄下
dist
├── __test__
│ ├── client.test.js
│ ├── index.js
│ └── server.test.js
├── client
│ └── index.js
├── server
│ └── index.js
└── shared
└── index.js
這有一個很明顯的問題,正如前面所說,當我們只修改一個模塊,比如只修改了前端模塊的代碼,那么理應只需要再構建前端模塊的產物即可,但是無論改動范圍如何,都是會將整個項目都構建一次,這在項目規(guī)模變得越來越大的時候會帶來極大的性能問題,構建時長會變得特別長
或許你會想著在每個模塊里創(chuàng)建一個tsconfig.json,然后通過tsc -p指定每個模塊的目錄去單獨對它們進行構建,沒錯,這是一種解決方案
但是這會帶來下面兩個問題:
- 如果需要全量構建項目,你得需要運行三次
tsc,對每個模塊分別構建,而tsc的啟動時間開銷是比較大的,在這個小規(guī)模項目里甚至啟動開銷的時間比實際構建的時間更長,現(xiàn)在還只是運行三次tsc,如果項目模塊很多,有幾十上百個呢?那光是啟動tsc幾十上百次都已經(jīng)會花一些時間了 tsc -w不能一次監(jiān)控多個tsconfig.json,只能是對各個模塊都啟動一次tsc -w
Project References的出現(xiàn),就是為了解決上述問題的
tsconfig.json 的 references 配置項
Project References就是tsconfig.json里的references配置項,其結構是一個包含若干對象的數(shù)組,對象的結構如下:
{
"references": [{ "path": "path/to/referenced-project" }]
}
核心就是一個path屬性,該屬性指向被引用的項目模塊路徑,該路徑下需要包含tsconfig.json,如果該模塊不是用tsconfig.json命名的話,你也可以指定具體的文件名,比如:
{
"references": [{ "path": "path/to/referenced-project/tsconfig.web.json" }]
}
當指定了references選項后,會發(fā)生如下改變:
- 在主模塊中導入被引用的模塊時,會加載它的類型聲明文件,也就是
.d.ts后綴的文件 - 使用
tsc --build或tsc -b構建主模塊時,會自動構建被引用的模塊
這樣一來能夠帶來三個好處:
- 提升類型檢查和構建的速度
- 減少
IDE的運行內存占用 - 更容易對項目結構進行劃分
tsconfig.json 的 composite 配置項
光是在主模塊中指定references配置項還不夠,還需要在被引用的項目對應的tsconfig.json中開啟composite配置項
composite配置項又是干嘛的呢? -- 它可以幫助tsc快速確定如何尋找被引用項目的輸出產物
當被引用的項目開啟composite配置項后,會有如下改變和要求:
當未指定rootDir時,默認值不再是The longest common path of all non-declaration input files,而是包含了tsconfig.json的目錄
Tips: 關于The longest common path of all non-declaration input files的意思可以到tsconfig.json 文章中關于 rootDir 的介紹中查閱
必須開啟include或者files配置項將要參與構建的文件聲明進來
必須開啟declaration配置項(因為前面介紹references的時候說了,會加載被引入模塊的類型聲明文件,因此被引用模塊自然得開啟declaration配置項生成自己的類型聲明文件供主模塊加載)
使用 Project References 改造示例項目
根據(jù)目前我們對Project References的認識,現(xiàn)在可以開始改造一下我們的項目了,首先是根目錄下的tsconfig.json配置,它起到一個類似于項目入口的作用,因此這里面只負責添加references聲明項目中需要被構建的模塊,以及通過exclude將不需要參與構建的模塊排除(比如src/__test__中的測試代碼)
/tsconfig.json
{
"references": [
{ "path": "src/client" },
{ "path": "src/server" },
{ "path": "src/shared" }
],
"exclude": ["**/__test__"]
}
然后是各個子模塊的tsconfig.json配置,這里我們假設構建目標為es5的代碼,所以對于client、server以及shared來說是存在公共配置的,所以我們可以抽離出一個公共配置,然后在子模塊中通過extends配置項公用一個配置
/tsconfig.base.json
{
"compilerOptions": {
"target": "ES5",
"module": "CommonJS",
"strict": true
}
}
src/client/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/client",
"composite": true,
"declaration": true
},
// 依賴哪個模塊則引用哪個模塊
"references": [{ "path": "../shared" }]
}
src/server/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/server",
"composite": true,
"declaration": true
},
// 依賴哪個模塊則引用哪個模塊
"references": [{ "path": "../shared" }]
}
src/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/shared",
"composite": true,
"declaration": true
}
}
全量構建
現(xiàn)在我們在項目根目錄下運行tsc --build --verbose,就會根據(jù)references配置去尋找各個子模塊,并對它們進行構建,可以理解為對項目的全量構建
--build 參數(shù)表示讓tsc以build模式進行構建和類型檢查,也就是會使用references配置項,如果不開啟的話是不會使用references配置項的,這點可以從官方文檔中得證:

--verbose 參數(shù)則是會將構建過程中的輸出顯示在控制臺中,不開啟該參數(shù)的話則不會顯示輸出(除非構建過程中報錯)
運行后/dist目錄結構如下
dist
├── client
│ ├── index.d.ts
│ ├── index.js
│ └── tsconfig.tsbuildinfo
├── server
│ ├── index.d.ts
│ ├── index.js
│ └── tsconfig.tsbuildinfo
└── shared
├── index.d.ts
├── index.js
└── tsconfig.tsbuildinfo
可以看到,所有子模塊都被構建進來了,各模塊的產物中有一個tsconfig.tsbuildinfo文件,這個文件起到一個類似緩存的作用,對于后續(xù)進行增量構建有著重要作用
前面看到的官方文檔中對tsc --build的作用的介紹中的第二點Detect if they are up-to-date主要就是依靠這個緩存文件去識別的
開啟--verbose參數(shù)后,可以看到控制臺輸出如下:
[4:50:26 PM] Projects in this build:
* src/shared/tsconfig.json
* src/client/tsconfig.json
* src/server/tsconfig.json
* tsconfig.json
[4:50:26 PM] Project 'src/shared/tsconfig.json' is out of date because output file 'dist/shared/tsconfig.tsbuildinfo' does not exist
[4:50:26 PM] Building project '/home/plasticine/demo/ts-reference-demo/src/shared/tsconfig.json'...
[4:50:28 PM] Project 'src/client/tsconfig.json' is out of date because output file 'dist/client/tsconfig.tsbuildinfo' does not exist
[4:50:28 PM] Building project '/home/plasticine/demo/ts-reference-demo/src/client/tsconfig.json'...
[4:50:28 PM] Project 'src/server/tsconfig.json' is out of date because output file 'dist/server/tsconfig.tsbuildinfo' does not exist
[4:50:28 PM] Building project '/home/plasticine/demo/ts-reference-demo/src/server/tsconfig.json'...
[4:50:29 PM] Project 'tsconfig.json' is out of date because output 'src/shared/index.js' is older than input 'src/client'
[4:50:29 PM] Building project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...
[4:50:29 PM] Updating unchanged output timestamps of project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...
xxx out of date xxx意思就是這個模塊沒被構建過,因此會開始對其進行構建
由于我們是首次構建,所以三個模塊都是沒被構建過的,所以三個模塊都被檢測為out of date
當我們再次運行tsc --build --verbose時,輸出如下:
4:54:35 PM - Projects in this build:
* src/shared/tsconfig.json
* src/client/tsconfig.json
* src/server/tsconfig.json
* tsconfig.json
4:54:35 PM - Project 'src/shared/tsconfig.json' is up to date because newest input 'src/shared/index.ts' is older than output 'dist/shared/tsconfig.tsbuildinfo'
4:54:35 PM - Project 'src/client/tsconfig.json' is up to date because newest input 'src/client/index.ts' is older than output 'dist/client/tsconfig.tsbuildinfo'
4:54:35 PM - Project 'src/server/tsconfig.json' is up to date because newest input 'src/server/index.ts' is older than output 'dist/server/tsconfig.tsbuildinfo'
4:54:35 PM - Project 'tsconfig.json' is up to date because newest input 'dist/server/index.d.ts' is older than output 'src/client/index.js'
可以看到,所有模塊都被檢測為up to date,從而避免了重復構建
增量構建
如果現(xiàn)在我們修改了client模塊的代碼,再運行tsc --build --verbose會怎樣呢?估計你也能猜到了,只有client模塊會被構建,而其他模塊則會跳過
4:56:44 PM - Projects in this build:
* src/shared/tsconfig.json
* src/client/tsconfig.json
* src/server/tsconfig.json
* tsconfig.json
4:56:44 PM - Project 'src/shared/tsconfig.json' is up to date because newest input 'src/shared/index.ts' is older than output 'dist/shared/tsconfig.tsbuildinfo'
4:56:44 PM - Project 'src/client/tsconfig.json' is out of date because output 'dist/client/tsconfig.tsbuildinfo' is older than input 'src/client/index.ts'
4:56:44 PM - Building project '/home/plasticine/demo/ts-reference-demo/src/client/tsconfig.json'...
4:56:45 PM - Project 'src/server/tsconfig.json' is up to date because newest input 'src/server/index.ts' is older than output 'dist/server/tsconfig.tsbuildinfo'
4:56:45 PM - Project 'tsconfig.json' is out of date because output file 'src/client/index.js' does not exist
4:56:45 PM - Building project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...
4:56:45 PM - Updating unchanged output timestamps of project '/home/plasticine/demo/ts-reference-demo/tsconfig.json'...
相信現(xiàn)在你能體會到Project References的好處了吧,能夠很大程度上優(yōu)化我們的構建速度!
不過實際開發(fā)中,tsc更多的是用來進行類型檢查,至于compile的工作,則更多地是交給如Babel、swc、esbuild等工具去完成,這也是官方文檔中有提到過的

這也是為什么你在vite創(chuàng)建的項目中能夠看到默認的build命令配置為tsc && vite build,正是將類型檢查的工作交給tsc,而構建工作則交給vite底層依賴的rollup去完成
對__test__測試代碼的處理
我們的改造貌似已經(jīng)完成了,但其實還忽略了一個src/__test__,它也可以被視為一個模塊,它作為主模塊,依賴了client和server,因此也可以給它加上tsconfig.json配置,并且對于測試代碼,我們一般不希望將它們構建成js,只希望tsc負責類型檢查的工作,因此我們需要進行如下配置:
src/__test__/tsconfig.json
{
"compilerOptions": {
"noEmit": true
},
"references": [{ "path": "../client" }, { "path": "../server" }]
}
noEmit的作用剛剛在官方文檔中也看到了,不會把產物文件輸出,如果我們只需要類型檢查能力的話很適合開啟該配置項
現(xiàn)在我們如果需要對__test__中的代碼進行類型檢查的話,只需要執(zhí)行:
# 忽略 references 配置項 tsc --project src/__test__ # 啟用 references 配置項 tsc --build src/__test__
如果是使用--project參數(shù)的話,tsconfig.json中可以忽略references配置項,因為即便配置了也不會被使用,這在依賴產物未構建出來時能起作用
而如果使用--build參數(shù),并且client和server未構建出來時,會先構建它們,再對測試代碼進行類型檢查,可以根據(jù)個人需求場景來決定使用--project還是--build
總結
本篇文章介紹了Project References是什么,并通過一個簡單的示例項目,并結合TypeScript Documentation官方文檔邊實戰(zhàn)邊解釋
總的來說,其使用起來就是:
- 主模塊(
tsc --build作用的模塊視為主模塊)中通過references配置項聲明依賴的模塊 - 被引用模塊中開啟
composite和declaration配置項以支持被引用 - 通過
tsc --build 主模塊才可以啟用references配置項,這在官方文檔中被稱為Build Mode,如果直接tsc 主模塊的話,是不會啟用references配置項的,也就導致依然會對項目中的所有ts文件進行編譯(如果沒配置include或files配置項的話)
希望通過本篇文章,能夠讓你對Project References有一個全面了解,也希望能夠將其用在你的項目中,提升類型檢查或構建(使用 tsc 進行構建的話)的速度,更多關于tsc性能Project References的資料請關注腳本之家其它相關文章!
相關文章
javascript+html5實現(xiàn)仿flash滾動播放圖片的方法
這篇文章主要介紹了javascript+html5實現(xiàn)仿flash滾動播放圖片的方法,可實現(xiàn)類似Flash滾動播放的效果,非常具有實用價值,需要的朋友可以參考下2015-04-04
JavaScript中Set和Map數(shù)據(jù)結構使用場景詳解
這篇文章主要為大家介紹了JavaScript中Set和Map數(shù)據(jù)結構使用場景詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06

