滋 TS 源碼(二)- 開始編譯

 藍字「合格前端」關注咱們哦!前端

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.jsonstopOnEntry 設爲 true 的預期表現,

接着 lib/tscrequire ../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 中的詞法和語法分析過程。

下面,用一張圖來總結本文涉及到的全部文件和方法,
createProgramcreateSourceFile 是兩個關鍵環節。


參考

TypeScript v3.7.3

❤️ 看完兩件事

若是你以爲這篇內容對你挺有益,我想邀請你幫我兩個小忙:

  1. 點個「在看」,讓更多的人也能看到這篇內容

  2. 關注公衆號「全棧大佬的修煉之路」,每週學習一個新技術,公衆號後臺回覆「學習資料」免費送給你精心準備的全棧進階資料。

本文分享自微信公衆號 - 全棧大佬的修煉之路(gh_7795af32a259)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索