439 Views
November 22, 23
スライド概要
kansai.ts #4 での発表資料です。
https://kansaits.connpass.com/event/299545/
Web Application Engineer
TypeScript コンパイラの 内側に片足入れる ryounasso
自己紹介 サイボウズ株式会社 kintone 新機能開発チーム SNS アカウント : @ryounasso TypeScript を触る機会 - 個人開発 (Next.js, React) - 業務 (React) 人生初 LT で緊張してます...
このスライドで目指すところ 1. ざっくりと処理の流れがわかる 2. それぞれの処理で、メインで動く関数とファイルがわかる TypeScript へのコントリビュートの足がかりに...!! > Good First Issue Issues · microsoft/TypeScript · GitHub 25件ほどあった
TypeScript はそのまま動かない TypeScript はブラウザや Node.js など JavaScript の実行環境で動作させるために JavaScript にコンパイルする必要がある TypeScript のコンパイラのソースは src/compiler フォルダ以下にある > https://github.com/Microsoft/TypeScript/tree/main/src/compiler 主要なファイルは以下の通り - scanner.ts - parser.ts - binder.ts - checker.ts - emitter.ts
大まかな流れ ソース ファイル 字句解析 構文解析 識別子の 結合 型 チェック トランス パイル AST の作成 出力
AST とは 字句解析 構文解析 識別子の 結合 型 チェック AST の作成 - Abstract Syntax Tree (抽象構文木) のこと コードをパースして、ソースコードを意味的に表す木構造のこと 端的にいうとコードを表すオブジェクト トランス パイル
字句解析 (scanner.ts) ● ● ● ● ● 字句解析 構文解析 識別子の 結合 型 チェック トランス パイル AST の作成 字句解析を行う ソースコードをトークンに分割する。 scan がメインの関数 Scanner は createScanner で生成される。 scan を実行すると、スキャン中の位置や現在のトークンの詳細などを更新す る ● Parser によって内部的に制御される ● parser.ts でシングルトンの scanner が生成される (生成コスト削減)
字句解析 (scanner.ts) 字句解析 構文解析 識別子の 結合 型 チェック AST の作成 // Sample usage initializeState(` var foo = 123; `.trim()); // Start the scanning var token = scanner.scan(); while (token != ts.SyntaxKind.EndOfFileToken) { let currentToken = ts.formatSyntaxKind(token); let tokenStart = scanner.getStartPos(); token = scanner.scan(); let tokenEnd = scanner.getStartPos(); console.log(currentToken, tokenStart, tokenEnd); } VarKeyword 0 3 Identifier 3 7 FirstAssignment 7 9 FirstLiteralToken 9 13 SemicolonToken 13 14 トランス パイル
構文解析 (parser.ts) 字句解析 構文解析 AST の作成 ● 構文解析を行う ● createSourceFile がメインの関数 ● createParser で生成される シングルトンとして実装されている 主にやっていることは、 1. ソースコードをトークンに分割 (scanner) 2. 文法規則に従って、AST を作成する 識別子の 結合 型 チェック トランス パイル
構文解析 (parser.ts) var sourceCode = ` var foo = 123; `.trim(); 字句解析 構文解析 識別子の 結合 型 チェック トランス パイル AST の作成 var sourceFile = ts.createSourceFile('foo.ts', sourceCode, ts.ScriptTarget.ES5, true); ----/** createSourceFileの中で **/ result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop, jsDocParsingMode); SourceFile 0 14 -------- SyntaxList 0 14 -------- VariableStatement 0 14 printAllChildren(sourceFile); ------------ VariableDeclarationList 0 13 ---------------- VarKeyword 0 3 ---------------- SyntaxList 3 13 -------------------- VariableDeclaration 3 13 ------------------------ Identifier 3 7 ------------------------ FirstAssignment 7 9 ------------------------ FirstLiteralToken 9 13 ------------ SemicolonToken 13 14 ---- EndOfFileToken 14 14
識別子の結合 (bind.ts) 字句解析 構文解析 識別子の 結合 型 チェック トランス パイル AST の作成 ● AST を読み取り、変数・関数などの宣言や定義を発見し、 代入先となる識別子と代入される値をバインドする ● bindSourceFile がメインの関数 ● createBinder で生成される 主にやっていることは 1. AST の巡回 2. シンボルの作成 ソースコードの識別子 (変数や関数名など)に関する情報を持つオブジェクト 3. スコープの解決 4. 型情報の収集
識別子の結合 (bind.ts)
字句解析
構文解析
識別子の
結合
AST の作成
const msg: string = "Hello, world";
Global Scope
msg declared line 0
flags: BlockSopedVariable
welcome(msg);
welcome declared line 3
flags: Function
function welcome(str: string) {
console.log(str)
}
welcome Function Scope
parent Global Scope
str declared line 3
flags: BlockScopedVariable
型
チェック
トランス
パイル
型チェック (checker.ts) 字句解析 構文解析 識別子の 結合 型 チェック トランス パイル AST の作成 tsc の中枢 コードの量がすごい...! (Github 上では多すぎて表示されなかった...) バインディングされた情報を元に、変数の型推論、型の整合性のチェック、 型のアサーションの確認などを行う。 checkSourceFile がメインの関数
型チェック (checker.ts) 字句解析 構文解析 AST の作成 const msg: string = "Hello, world"; (簡略化した AST) SourceFile └─ VariableStatement ├─ constKeyword └─ DeclarationList ├─ msg ├─ ColonToken ├─ StringKeyword ├─ EqualsToken └─ StringLiteral └─ "Hello World" 識別子の 結合 型 チェック トランス パイル
型チェック (checker.ts) 字句解析 構文解析 AST の作成 const msg: string = "Hello, world"; (簡略化した AST) SourceFile └─ VariableStatement ├─ constKeyword └─ DeclarationList ├─ msg ├─ ColonToken ├─ StringKeyword ├─ EqualsToken └─ StringLiteral └─ "Hello World" 識別子の 結合 型 チェック トランス パイル
トランスパイル (emitter.ts) 字句解析 構文解析 識別子の 結合 型 チェック AST の作成 最後に、TypeScript コードを JavaScript コードにトランスパイルする emitFiles がメインの関数 JavaScript はバージョンによって使える文法が大きく変わる。 トランスパイルのために、transformer を使用 src/compiler/transformers フォルダ以下に、 それぞれES版に対するトランスフォーマー等が存在している 流れは以下の通り 1. getTransformers で適用する transformer の配列を取得 2. emitFiles で JavaScript コードにトランスパイル トランス パイル
トランスパイル (emitter.ts) 字句解析 構文解析 識別子の 結合 型 チェック AST の作成 JavaScript では型情報が不要になる (transformers) const msg: string = "Hello, world"; const msg: string = "Hello, world"; SourceFile SourceFile └─ VariableStatement └─ VariableStatement ├─ constKeyword ├─ constKeyword └─ DeclarationList └─ DeclarationList ├─ msg ├─ msg ├─ ColonToken ├─ EqualsToken ├─ StringKeyword └─ StringLiteral ├─ EqualsToken └─ StringLiteral └─ "Hello World" └─ "Hello World" トランス パイル
まとめ 字句解析 scanner.ts scan ソース ファイル 出力 構文解析 parser.ts createSourceFile トランスパイル emitter.ts emitFiles 識別子の結合 bind.ts bindSourceFile 型チェック checker.ts checkSourceFile
参考記事 - TypeScriptコンパイラの内側 - TypeScript Deep Dive 日本語版 (gitbook.io) - https://www.youtube.com/watch?v=X8k_4tZ16qU&list=PLYUbsZda9oHuEiIdekbAzNO0-pUM5Iqj&index=5 - https://qiita.com/eloy/items/9d0d8227121a1dcbdf71 - https://github.com/microsoft/TypeScript