戳藍字「合格前端」關注咱們哦!前端
0. 回顧
上一篇,咱們把 VSCode 調試環境準備好了,啓動調試後,進到了 lib/tsc
中,
斷點停在了第一行。web
本文咱們開始往下調試了,深刻到 tsc
編譯的整個過程當中。
不過在此以前,咱們需先對 VSCode 調試面板有一些瞭解,調試面板提供了多種調試功能。json
(1)F5 - Continue:執行到下一個斷點數組
(2)F10 - Step Over:執行下一行,不跳入函數內部微信
(3)F11 - Step Into:代碼執行到下一步,跳入函數內部app
(4)⇧ F11 - Step Out:執行完當前函數,回到調用方編輯器
(5)⇧ ⌘ F5 - Restart:從新啓動調試函數
(6)⇧ F5 - Stop:中止調試學習
熟悉了這些調試功能以後,咱們才能在 TypeScript 源碼中隨意馳騁。flex
1. lib/tsc require 沒法進入斷點的問題
上一篇提到,斷點停在了 lib/tsc
第一行,
這是調試配置 .vscode/launch.json
中 stopOnEntry
設爲 true
的預期表現,
接着 lib/tsc
會 require
../built/local/tsc.js
文件,
而後,VSCode 會根據 ../built/local/tsc.js.map
反查對應的 .ts
源碼,跳轉到 src/tsc/tsc.ts 中。
ts.executeCommandLine(
...
);
因此,咱們應在 src/tsc/tsc.ts#L2 打個斷點,以下所示,
然而我發現,在 VSCode 按 F5
繼續執行,卻沒法從 lib/tsc
直接跳轉到 src/tsc/tsc.ts 中。
咱們得按如下步驟操做才行。
(1)lib/tsc
Step Over 到 require
那一行
(2)lib/tsc
Step Into 跳轉到 require
中
(3)代碼會跳轉到 src/compiler/core.ts#L1,而後再按 F5
(4)這樣才能來到 src/tsc/tsc.ts#L2 的斷點處
咱們看到 ts.sys.args
的值,正是 tsc
待編譯的文件 debug/index.ts
。
2. 解析 & 寫文件
好了,終於能夠正常的調試 .ts
源碼了,咱們 Step Into 進入 ts.executeCommandLine
的具體實現中。
它位於 src/tsc/executeCommandLine.ts#L353,
export function executeCommandLine(
...
): void {
...
if (...) {
...
}
else {
executeCommandLineWorker(system, cb, commandLine);
}
}
可見,executeCommandLine
接着會調用 executeCommandLineWorker
,src/tsc/executeCommandLine.ts#L170,
function executeCommandLineWorker(
...
) {
...
if (configFileName) {
...
}
else {
...
if (isWatchSet(commandLineOptions)) {
...
}
else if (isIncrementalCompilation(commandLineOptions)) {
...
}
else {
performCompilation(
sys,
reportDiagnostic,
cb,
{ ...commandLine, options: commandLineOptions }
);
}
}
}
而後調用 performCompilation
,src/tsc/executeCommandLine.ts#L493,
function performCompilation(
...
) {
...
const program = createProgram(programOptions);
const exitStatus = emitFilesAndReportErrorsAndGetExitStatus(
...
);
...
}
這裏,performCompilation
會執行兩個關鍵操做,
(1)createProgram
解析源代碼
(2)emitFilesAndReportErrorsAndGetExitStatus
將編譯結果寫文件
本文以後的幾篇文章,咱們先詳細的介紹源碼的解析過程,
而後再回過頭來介紹 emitFilesAndReportErrorsAndGetExitStatus
。
3. 調用 parser 解析單個文件
首先,createProgram
位於 src/compiler/program.ts#L713,這個函數有 2665
行,
有不少輔助函數,它實際是在 src/compiler/program.ts#L988 這裏就返回了。
處理待編譯的源文件 debug/index.ts
,發生在 src/compiler/program.ts#L873,forEach
中,
export function createProgram(...): Program {
...
if (structuralIsReused !== StructureIsReused.Completely) {
...
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false));
...
}
...
const program: Program = {
...
};
...
return program;
...
}
它逐個處理了 TypeScript 待編譯的文件,咱們當前示例項目中,只有一個文件,
能夠看到,rootNames
數組的惟一元素正是 debug/index.ts
。
隨後,forEach
會調用 processRootFile
,src/compiler/program.ts#L2041,
function processRootFile(...) {
processSourceFile(...);
}
processRootFile
調用 processSourceFile
,src/compiler/program.ts#L2231,
function processSourceFile(...): void {
getSourceFileFromReferenceWorker(
...
);
}
processSourceFile
調用 getSourceFileFromReferenceWorker
,src/compiler/program.ts#L2186,
function getSourceFileFromReferenceWorker(
...
if (hasExtension(fileName)) {
...
const sourceFile = getSourceFile(fileName);
...
return sourceFile;
}
else {
...
}
}
getSourceFileFromReferenceWorker
調用 getSourceFile
,
它是 getSourceFileFromReferenceWorker
傳入的一個參數,
具體實現位於 src/compiler/program.ts#L2234,
function processSourceFile(...): void {
getSourceFileFromReferenceWorker(
...
fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, refFile, packageId), // TODO: GH#18217
...
);
}
接着會調用 findSourceFile
,src/compiler/program.ts#L2273,
function findSourceFile(...): SourceFile | undefined {
...
const file = host.getSourceFile(
...
);
...
return file;
}
而後調用 host.getSourceFile
,src/compiler/program.ts#L78,
先讀文件,而後又調用了 createSourceFile
,
function getSourceFile(...): SourceFile | undefined {
...
try {
...
text = compilerHost.readFile(fileName);
...
}
catch (e) {
...
}
return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined;
}
createSourceFile
位於 src/compiler/parser.ts#L515,咱們終於看到 parser 的影子了。
總結
以上咱們分析了從命令行 lib/tsc
開始,到調用 parser 解析每一個文件的過程,
邏輯上來看,編譯過程當中,TypeScript 會建立一個 Program
對象,
這個 Program
對象中,會包含多個 SourceFile
。
每個 SourceFile
都是單獨讀文件,而後進行解析的。
下文咱們要深刻研究 SourceFile
究竟是怎樣解析的,這會涉及 Compiler 中的詞法和語法分析過程。
下面,用一張圖來總結本文涉及到的全部文件和方法,createProgram
和 createSourceFile
是兩個關鍵環節。
![](http://static.javashuo.com/static/loading.gif)
參考
TypeScript v3.7.3
❤️ 看完兩件事
若是你以爲這篇內容對你挺有益,我想邀請你幫我兩個小忙:
點個「在看」,讓更多的人也能看到這篇內容
關注公衆號「全棧大佬的修煉之路」,每週學習一個新技術,公衆號後臺回覆「學習資料」免費送給你精心準備的全棧進階資料。
本文分享自微信公衆號 - 全棧大佬的修煉之路(gh_7795af32a259)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。