tsc性能優(yōu)化Project References使用詳解
什么是 Project References
在了解一個(gè)東西是什么的時(shí)候,直接看其官方定義是最直觀的
TypeScript: Documentation中對(duì)于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
新增的特性,這個(gè)特性有啥用呢?
將我們的項(xiàng)目分成多個(gè)小的片段,也就是允許我們將項(xiàng)目進(jìn)行分包模塊化
這樣一來我們?cè)趫?zhí)行tsc
對(duì)項(xiàng)目進(jìn)行構(gòu)建的時(shí)候,無論是出于代碼轉(zhuǎn)譯成js
的目的還是說出于單純的類型檢查(將compilerOptions.noEmit
置為true
)的目的
都可以嚴(yán)格按照自己的需要對(duì)想要被tsc
處理的那部分代碼進(jìn)行處理,而不是每次都對(duì)整個(gè)項(xiàng)目進(jìn)行處理,這是我認(rèn)為Project References
的最大好處
就以一個(gè)前后端代碼在同一個(gè)倉(cāng)庫(kù)里維護(hù)的項(xiàng)目為例,如果沒有Project References
,那么我執(zhí)行tsc
時(shí),會(huì)對(duì)前后端模塊都進(jìn)行類型檢查和轉(zhuǎn)譯
但實(shí)際上如果我只修改了前端部分的代碼,理所應(yīng)當(dāng)讓tsc
只處理前端模塊,后端模塊的構(gòu)建產(chǎn)物不需要進(jìn)行重新構(gòu)建,有了Project References
,我們就能實(shí)現(xiàn)到這個(gè)需求,從而優(yōu)化項(xiàng)目的構(gòu)建或類型檢查性能
相信大家對(duì)Project References
有一個(gè)大概的認(rèn)識(shí)了,接下來我們就開始實(shí)際動(dòng)手體驗(yàn)一下Project References
加深我們對(duì)它的理解吧!
示例項(xiàng)目結(jié)構(gòu)
. ├── package.json ├── pnpm-lock.yaml ├── src │ ├── __test__ // 單元測(cè)試 │ │ ├── client.test.ts // 前端代碼測(cè)試 │ │ ├── index.ts // 簡(jiǎn)陋的單元測(cè)試 API │ │ └── server.test.ts // 后端代碼測(cè)試 │ ├── client // 前端模塊 │ │ └── index.ts │ ├── server // 后端模塊 │ │ └── index.ts │ └── shared // 共享模塊 -- 包含通用的工具函數(shù) │ └── index.ts └── tsconfig.json // TypeScript 配置
這是一個(gè)很常見的項(xiàng)目目錄結(jié)構(gòu),有前端代碼,有后端代碼,也有通用工具函數(shù)代碼以及前后端的單元測(cè)試代碼
它們的依賴關(guān)系如下:
- client 依賴 shared
- server 依賴 shared
__test__
依賴 client 和 server- shared 無依賴
不使用 Project References 帶來的問題
現(xiàn)在整個(gè)項(xiàng)目只有一個(gè)tsconfig.json
位于項(xiàng)目根目錄下,其內(nèi)容如下:
{ "compilerOptions": { "target": "ES5", "module": "CommonJS", "strict": true, "outDir": "./dist" } }
如果我們執(zhí)行tsc
,它會(huì)將各個(gè)模塊的代碼都打包到項(xiàng)目根目錄的dist
目錄下
dist ├── __test__ │ ├── client.test.js │ ├── index.js │ └── server.test.js ├── client │ └── index.js ├── server │ └── index.js └── shared └── index.js
這有一個(gè)很明顯的問題,正如前面所說,當(dāng)我們只修改一個(gè)模塊,比如只修改了前端模塊的代碼,那么理應(yīng)只需要再構(gòu)建前端模塊的產(chǎn)物即可,但是無論改動(dòng)范圍如何,都是會(huì)將整個(gè)項(xiàng)目都構(gòu)建一次,這在項(xiàng)目規(guī)模變得越來越大的時(shí)候會(huì)帶來極大的性能問題,構(gòu)建時(shí)長(zhǎng)會(huì)變得特別長(zhǎng)
或許你會(huì)想著在每個(gè)模塊里創(chuàng)建一個(gè)tsconfig.json
,然后通過tsc -p
指定每個(gè)模塊的目錄去單獨(dú)對(duì)它們進(jìn)行構(gòu)建,沒錯(cuò),這是一種解決方案
但是這會(huì)帶來下面兩個(gè)問題:
- 如果需要全量構(gòu)建項(xiàng)目,你得需要運(yùn)行三次
tsc
,對(duì)每個(gè)模塊分別構(gòu)建,而tsc
的啟動(dòng)時(shí)間開銷是比較大的,在這個(gè)小規(guī)模項(xiàng)目里甚至啟動(dòng)開銷的時(shí)間比實(shí)際構(gòu)建的時(shí)間更長(zhǎng),現(xiàn)在還只是運(yùn)行三次tsc
,如果項(xiàng)目模塊很多,有幾十上百個(gè)呢?那光是啟動(dòng)tsc
幾十上百次都已經(jīng)會(huì)花一些時(shí)間了 tsc -w
不能一次監(jiān)控多個(gè)tsconfig.json
,只能是對(duì)各個(gè)模塊都啟動(dòng)一次tsc -w
Project References
的出現(xiàn),就是為了解決上述問題的
tsconfig.json 的 references 配置項(xiàng)
Project References
就是tsconfig.json
里的references
配置項(xiàng),其結(jié)構(gòu)是一個(gè)包含若干對(duì)象的數(shù)組,對(duì)象的結(jié)構(gòu)如下:
{ "references": [{ "path": "path/to/referenced-project" }] }
核心就是一個(gè)path
屬性,該屬性指向被引用的項(xiàng)目模塊路徑,該路徑下需要包含tsconfig.json
,如果該模塊不是用tsconfig.json
命名的話,你也可以指定具體的文件名,比如:
{ "references": [{ "path": "path/to/referenced-project/tsconfig.web.json" }] }
當(dāng)指定了references
選項(xiàng)后,會(huì)發(fā)生如下改變:
- 在主模塊中導(dǎo)入被引用的模塊時(shí),會(huì)加載它的類型聲明文件,也就是
.d.ts
后綴的文件 - 使用
tsc --build
或tsc -b
構(gòu)建主模塊時(shí),會(huì)自動(dòng)構(gòu)建被引用的模塊
這樣一來能夠帶來三個(gè)好處:
- 提升類型檢查和構(gòu)建的速度
- 減少
IDE
的運(yùn)行內(nèi)存占用 - 更容易對(duì)項(xiàng)目結(jié)構(gòu)進(jìn)行劃分
tsconfig.json 的 composite 配置項(xiàng)
光是在主模塊中指定references
配置項(xiàng)還不夠,還需要在被引用的項(xiàng)目對(duì)應(yīng)的tsconfig.json
中開啟composite
配置項(xiàng)
composite
配置項(xiàng)又是干嘛的呢? -- 它可以幫助tsc
快速確定如何尋找被引用項(xiàng)目的輸出產(chǎn)物
當(dāng)被引用的項(xiàng)目開啟composite
配置項(xiàng)后,會(huì)有如下改變和要求:
當(dāng)未指定rootDir
時(shí),默認(rèn)值不再是The longest common path of all non-declaration input files
,而是包含了tsconfig.json
的目錄
Tips: 關(guān)于The longest common path of all non-declaration input files
的意思可以到tsconfig.json 文章中關(guān)于 rootDir 的介紹中查閱
必須開啟include
或者files
配置項(xiàng)將要參與構(gòu)建的文件聲明進(jìn)來
必須開啟declaration
配置項(xiàng)(因?yàn)榍懊娼榻Breferences
的時(shí)候說了,會(huì)加載被引入模塊的類型聲明文件,因此被引用模塊自然得開啟declaration
配置項(xiàng)生成自己的類型聲明文件供主模塊加載)
使用 Project References 改造示例項(xiàng)目
根據(jù)目前我們對(duì)Project References
的認(rèn)識(shí),現(xiàn)在可以開始改造一下我們的項(xiàng)目了,首先是根目錄下的tsconfig.json
配置,它起到一個(gè)類似于項(xiàng)目入口的作用,因此這里面只負(fù)責(zé)添加references
聲明項(xiàng)目中需要被構(gòu)建的模塊,以及通過exclude
將不需要參與構(gòu)建的模塊排除(比如src/__test__
中的測(cè)試代碼)
/tsconfig.json
{ "references": [ { "path": "src/client" }, { "path": "src/server" }, { "path": "src/shared" } ], "exclude": ["**/__test__"] }
然后是各個(gè)子模塊的tsconfig.json
配置,這里我們假設(shè)構(gòu)建目標(biāo)為es5
的代碼,所以對(duì)于client
、server
以及shared
來說是存在公共配置的,所以我們可以抽離出一個(gè)公共配置,然后在子模塊中通過extends
配置項(xiàng)公用一個(gè)配置
/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 }, // 依賴哪個(gè)模塊則引用哪個(gè)模塊 "references": [{ "path": "../shared" }] }
src/server/tsconfig.json
{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../dist/server", "composite": true, "declaration": true }, // 依賴哪個(gè)模塊則引用哪個(gè)模塊 "references": [{ "path": "../shared" }] }
src/shared/tsconfig.json
{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../dist/shared", "composite": true, "declaration": true } }
全量構(gòu)建
現(xiàn)在我們?cè)陧?xiàng)目根目錄下運(yùn)行tsc --build --verbose
,就會(huì)根據(jù)references
配置去尋找各個(gè)子模塊,并對(duì)它們進(jìn)行構(gòu)建,可以理解為對(duì)項(xiàng)目的全量構(gòu)建
--build
參數(shù)表示讓tsc
以build
模式進(jìn)行構(gòu)建和類型檢查,也就是會(huì)使用references
配置項(xiàng),如果不開啟的話是不會(huì)使用references
配置項(xiàng)的,這點(diǎn)可以從官方文檔中得證:
--verbose
參數(shù)則是會(huì)將構(gòu)建過程中的輸出顯示在控制臺(tái)中,不開啟該參數(shù)的話則不會(huì)顯示輸出(除非構(gòu)建過程中報(bào)錯(cuò))
運(yùn)行后/dist
目錄結(jié)構(gòu)如下
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
可以看到,所有子模塊都被構(gòu)建進(jìn)來了,各模塊的產(chǎn)物中有一個(gè)tsconfig.tsbuildinfo
文件,這個(gè)文件起到一個(gè)類似緩存的作用,對(duì)于后續(xù)進(jìn)行增量構(gòu)建有著重要作用
前面看到的官方文檔中對(duì)tsc --build
的作用的介紹中的第二點(diǎn)Detect if they are up-to-date
主要就是依靠這個(gè)緩存文件去識(shí)別的
開啟--verbose
參數(shù)后,可以看到控制臺(tái)輸出如下:
[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
意思就是這個(gè)模塊沒被構(gòu)建過,因此會(huì)開始對(duì)其進(jìn)行構(gòu)建
由于我們是首次構(gòu)建,所以三個(gè)模塊都是沒被構(gòu)建過的,所以三個(gè)模塊都被檢測(cè)為out of date
當(dāng)我們?cè)俅芜\(yùn)行tsc --build --verbose
時(shí),輸出如下:
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'
可以看到,所有模塊都被檢測(cè)為up to date
,從而避免了重復(fù)構(gòu)建
增量構(gòu)建
如果現(xiàn)在我們修改了client
模塊的代碼,再運(yùn)行tsc --build --verbose
會(huì)怎樣呢?估計(jì)你也能猜到了,只有client
模塊會(huì)被構(gòu)建,而其他模塊則會(huì)跳過
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)在你能體會(huì)到Project References
的好處了吧,能夠很大程度上優(yōu)化我們的構(gòu)建速度!
不過實(shí)際開發(fā)中,tsc
更多的是用來進(jìn)行類型檢查,至于compile
的工作,則更多地是交給如Babel
、swc
、esbuild
等工具去完成,這也是官方文檔中有提到過的
這也是為什么你在vite
創(chuàng)建的項(xiàng)目中能夠看到默認(rèn)的build
命令配置為tsc && vite build
,正是將類型檢查的工作交給tsc
,而構(gòu)建工作則交給vite
底層依賴的rollup
去完成
對(duì)__test__測(cè)試代碼的處理
我們的改造貌似已經(jīng)完成了,但其實(shí)還忽略了一個(gè)src/__test__
,它也可以被視為一個(gè)模塊,它作為主模塊,依賴了client
和server
,因此也可以給它加上tsconfig.json
配置,并且對(duì)于測(cè)試代碼,我們一般不希望將它們構(gòu)建成js
,只希望tsc
負(fù)責(zé)類型檢查的工作,因此我們需要進(jìn)行如下配置:
src/__test__/tsconfig.json
{ "compilerOptions": { "noEmit": true }, "references": [{ "path": "../client" }, { "path": "../server" }] }
noEmit
的作用剛剛在官方文檔中也看到了,不會(huì)把產(chǎn)物文件輸出,如果我們只需要類型檢查能力的話很適合開啟該配置項(xiàng)
現(xiàn)在我們?nèi)绻枰獙?duì)__test__
中的代碼進(jìn)行類型檢查的話,只需要執(zhí)行:
# 忽略 references 配置項(xiàng) tsc --project src/__test__ # 啟用 references 配置項(xiàng) tsc --build src/__test__
如果是使用--project
參數(shù)的話,tsconfig.json
中可以忽略references
配置項(xiàng),因?yàn)榧幢闩渲昧艘膊粫?huì)被使用,這在依賴產(chǎn)物未構(gòu)建出來時(shí)能起作用
而如果使用--build
參數(shù),并且client
和server
未構(gòu)建出來時(shí),會(huì)先構(gòu)建它們,再對(duì)測(cè)試代碼進(jìn)行類型檢查,可以根據(jù)個(gè)人需求場(chǎng)景來決定使用--project
還是--build
總結(jié)
本篇文章介紹了Project References
是什么,并通過一個(gè)簡(jiǎn)單的示例項(xiàng)目,并結(jié)合TypeScript Documentation
官方文檔邊實(shí)戰(zhàn)邊解釋
總的來說,其使用起來就是:
- 主模塊(
tsc --build
作用的模塊視為主模塊)中通過references
配置項(xiàng)聲明依賴的模塊 - 被引用模塊中開啟
composite
和declaration
配置項(xiàng)以支持被引用 - 通過
tsc --build 主模塊
才可以啟用references
配置項(xiàng),這在官方文檔中被稱為Build Mode
,如果直接tsc 主模塊
的話,是不會(huì)啟用references
配置項(xiàng)的,也就導(dǎo)致依然會(huì)對(duì)項(xiàng)目中的所有ts
文件進(jìn)行編譯(如果沒配置include
或files
配置項(xiàng)的話)
希望通過本篇文章,能夠讓你對(duì)Project References
有一個(gè)全面了解,也希望能夠?qū)⑵溆迷谀愕捻?xiàng)目中,提升類型檢查或構(gòu)建(使用 tsc 進(jìn)行構(gòu)建的話)的速度,更多關(guān)于tsc性能Project References的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js離開或刷新頁面檢測(cè)(且兼容FF,IE,Chrome)
這篇文章主要介紹了js離開或刷新頁面檢測(cè)(且兼容FF,IE,Chrome)。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-03-03紅黑樹的插入詳解及Javascript實(shí)現(xiàn)方法示例
這篇文章主要給大家介紹了關(guān)于紅黑樹的插入的相關(guān)資料,以及Javascript實(shí)現(xiàn)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起看看吧。2018-03-03用javascript做一個(gè)小游戲平臺(tái) (二) 游戲選擇器
昨天晚上“設(shè)計(jì)”了n久,那些代碼都還沒有運(yùn)行起來,有點(diǎn)心急、有點(diǎn)郁悶。2010-01-01javascript 最常用的10個(gè)自定義函數(shù)[推薦]
如果不使用類庫(kù)或者沒有自己的類庫(kù),儲(chǔ)備一些常用函數(shù)總是有好處的。2009-12-12js當(dāng)前頁面登錄注冊(cè)框,固定div,底層陰影的實(shí)例代碼
下面小編就為大家?guī)硪黄猨s當(dāng)前頁面登錄注冊(cè)框,固定div,底層陰影的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10JS函數(shù)內(nèi)部屬性之a(chǎn)rguments和this實(shí)例解析
在函數(shù)內(nèi)部,有兩個(gè)特殊的對(duì)象:arguments和this。這篇文章主要介紹了函數(shù)內(nèi)部屬性之a(chǎn)rguments和this ,需要的朋友可以參考下2018-10-10基于javascript實(shí)現(xiàn)圖片懶加載
這篇文章主要介紹了javascript實(shí)現(xiàn)圖片懶加載的方法及思路,有時(shí)我們需要用懶加載,也就是延遲加載圖片的方式,來提高網(wǎng)站的親和力,需要的朋友可以參考下2016-01-01