发射器
TypeScript 编译器提供了两个发射器:
emitter.ts
:可能是你最感兴趣的发射器,它是 TS -> JavaScript 的发射器declarationEmitter.ts
:这个发射器用于为 TypeScript 源文件(.ts
) 创建声明文件(.d.ts
)
本节我们介绍 emitter.ts
Promgram 对发射器的使用
Program 提供了一个 emit
函数。该函数主要将功能委托给 emitter.ts
中的 emitFiles
函数。下面是调用栈:
Program.emit ->
`emitWorker` (在 program.ts 中的 createProgram) ->
`emitFiles` (emitter.ts 中的函数)
2
3
emitWorker
(通过 emitFiles
参数)给发射器提供一个 EmitResolver
。 EmitResolver
由程序的 TypeChecker 提供,基本上它是一个来自 createChecker
的本地函数的子集。
发射器函数
emitFiles
定义在 emitter.ts
中,下面是该函数的签名:
// targetSourceFile 当用户想发射项目中的某个文件时指定,保存时编译(compileOnSave)功能使用此参数
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile?: SourceFile): EmitResult {
2
EmitHost
是 CompilerHost
的简化版(运行时,很多用例实际上都是 CompilerHost
)
emitFiles
中的最有趣的调用栈如下所示:
emitFiles ->
emitFile(jsFilePath, targetSourceFile) ->
emitJavaScript(jsFilePath, targetSourceFile);
2
3
emitJavaScript
该函数有良好的注释,我们下面给出它:
function emitJavaScript(jsFilePath: string, root?: SourceFile) {
let writer = createTextWriter(newLine);
let write = writer.write;
let writeTextOfNode = writer.writeTextOfNode;
let writeLine = writer.writeLine;
let increaseIndent = writer.increaseIndent;
let decreaseIndent = writer.decreaseIndent;
let currentSourceFile: SourceFile;
// 导出器函数的名称,如果文件是个系统外部模块的话
// System.register([...], function (<exporter>) {...})
// System 模块中的导出像这样:
// export var x; ... x = 1
// =>
// var x;... exporter("x", x = 1)
let exportFunctionForFile: string;
let generatedNameSet: Map<string> = {};
let nodeToGeneratedName: string[] = [];
let computedPropertyNamesToGeneratedNames: string[];
let extendsEmitted = false;
let decorateEmitted = false;
let paramEmitted = false;
let awaiterEmitted = false;
let tempFlags = 0;
let tempVariables: Identifier[];
let tempParameters: Identifier[];
let externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[];
let exportSpecifiers: Map<ExportSpecifier[]>;
let exportEquals: ExportAssignment;
let hasExportStars: boolean;
/** 将发射输出写入磁盘 */
let writeEmittedFiles = writeJavaScriptFile;
let detachedCommentsInfo: { nodePos: number; detachedCommentEndPos: number }[];
let writeComment = writeCommentRange;
/** 发射一个节点 */
let emit = emitNodeWithoutSourceMap;
/** 在发射节点前调用 */
let emitStart = function(node: Node) {};
/** 发射结点完成后调用 */
let emitEnd = function(node: Node) {};
/** 从 startPos 位置开始,为指定的 token 发射文本。默认写入的文本由 tokenKind 提供,
* 但是如果提供了可选的 emitFn 回调,将使用该回调来代替默认方式发射文本。
* @param tokenKind 要搜索并发射的 token 的类别
* @param startPos 源码中搜索 token 的起始位置
* @param emitFn 如果给出,会被调用来进行文本的发射。
*/
let emitToken = emitTokenText;
/** 该函数由于节点的缘故,在被发射的代码中的函数或类中,会在启用词法作用域前被调用
* @param scopeDeclaration 启动词法作用域的节点
* @param scopeName 可选的作用域的名称,默认从节点声明中推导
*/
let scopeEmitStart = function(scopeDeclaration: Node, scopeName?: string) {};
/** 出了作用域后调用 */
let scopeEmitEnd = function() {};
/** 会被编码的 Sourcemap 数据 */
let sourceMapData: SourceMapData;
if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
initializeEmitterWithSourceMaps();
}
if (root) {
// 不要直接调用 emit,那样不会设置 currentSourceFile
emitSourceFile(root);
} else {
forEach(host.getSourceFiles(), sourceFile => {
if (!isExternalModuleOrDeclarationFile(sourceFile)) {
emitSourceFile(sourceFile);
}
});
}
writeLine();
writeEmittedFiles(writer.getText(), /*writeByteOrderMark*/ compilerOptions.emitBOM);
return;
/// 一批本地函数
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
它主要设置了一批本地变量和函数(这些函数构成 emitter.ts
的大部分内容),接着交给本地函数 emitSourceFile
发射文本。emitSourceFile
函数设置 currentSourceFile
然后交给本地函数 emit
去处理。
function emitSourceFile(sourceFile: SourceFile): void {
currentSourceFile = sourceFile;
exportFunctionForFile = undefined;
emit(sourceFile);
}
2
3
4
5
emit
函数处理 注释 和 实际 JavaScript 的发射。实际 JavaScript 的发射是 emitJavaScriptWorker 函数的工作。
emitJavaScriptWorker
完整的函数:
function emitJavaScriptWorker(node: Node) {
// 检查节点是否可以忽略 ScriptTarget 发射
switch (node.kind) {
case SyntaxKind.Identifier:
return emitIdentifier(<Identifier>node);
case SyntaxKind.Parameter:
return emitParameter(<ParameterDeclaration>node);
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return emitMethod(<MethodDeclaration>node);
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return emitAccessor(<AccessorDeclaration>node);
case SyntaxKind.ThisKeyword:
return emitThis(node);
case SyntaxKind.SuperKeyword:
return emitSuper(node);
case SyntaxKind.NullKeyword:
return write('null');
case SyntaxKind.TrueKeyword:
return write('true');
case SyntaxKind.FalseKeyword:
return write('false');
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.RegularExpressionLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
case SyntaxKind.TemplateMiddle:
case SyntaxKind.TemplateTail:
return emitLiteral(<LiteralExpression>node);
case SyntaxKind.TemplateExpression:
return emitTemplateExpression(<TemplateExpression>node);
case SyntaxKind.TemplateSpan:
return emitTemplateSpan(<TemplateSpan>node);
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
return emitJsxElement(<JsxElement | JsxSelfClosingElement>node);
case SyntaxKind.JsxText:
return emitJsxText(<JsxText>node);
case SyntaxKind.JsxExpression:
return emitJsxExpression(<JsxExpression>node);
case SyntaxKind.QualifiedName:
return emitQualifiedName(<QualifiedName>node);
case SyntaxKind.ObjectBindingPattern:
return emitObjectBindingPattern(<BindingPattern>node);
case SyntaxKind.ArrayBindingPattern:
return emitArrayBindingPattern(<BindingPattern>node);
case SyntaxKind.BindingElement:
return emitBindingElement(<BindingElement>node);
case SyntaxKind.ArrayLiteralExpression:
return emitArrayLiteral(<ArrayLiteralExpression>node);
case SyntaxKind.ObjectLiteralExpression:
return emitObjectLiteral(<ObjectLiteralExpression>node);
case SyntaxKind.PropertyAssignment:
return emitPropertyAssignment(<PropertyDeclaration>node);
case SyntaxKind.ShorthandPropertyAssignment:
return emitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);
case SyntaxKind.ComputedPropertyName:
return emitComputedPropertyName(<ComputedPropertyName>node);
case SyntaxKind.PropertyAccessExpression:
return emitPropertyAccess(<PropertyAccessExpression>node);
case SyntaxKind.ElementAccessExpression:
return emitIndexedAccess(<ElementAccessExpression>node);
case SyntaxKind.CallExpression:
return emitCallExpression(<CallExpression>node);
case SyntaxKind.NewExpression:
return emitNewExpression(<NewExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.TypeAssertionExpression:
return emit((<TypeAssertion>node).expression);
case SyntaxKind.AsExpression:
return emit((<AsExpression>node).expression);
case SyntaxKind.ParenthesizedExpression:
return emitParenExpression(<ParenthesizedExpression>node);
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
return emitFunctionDeclaration(<FunctionLikeDeclaration>node);
case SyntaxKind.DeleteExpression:
return emitDeleteExpression(<DeleteExpression>node);
case SyntaxKind.TypeOfExpression:
return emitTypeOfExpression(<TypeOfExpression>node);
case SyntaxKind.VoidExpression:
return emitVoidExpression(<VoidExpression>node);
case SyntaxKind.AwaitExpression:
return emitAwaitExpression(<AwaitExpression>node);
case SyntaxKind.PrefixUnaryExpression:
return emitPrefixUnaryExpression(<PrefixUnaryExpression>node);
case SyntaxKind.PostfixUnaryExpression:
return emitPostfixUnaryExpression(<PostfixUnaryExpression>node);
case SyntaxKind.BinaryExpression:
return emitBinaryExpression(<BinaryExpression>node);
case SyntaxKind.ConditionalExpression:
return emitConditionalExpression(<ConditionalExpression>node);
case SyntaxKind.SpreadElementExpression:
return emitSpreadElementExpression(<SpreadElementExpression>node);
case SyntaxKind.YieldExpression:
return emitYieldExpression(<YieldExpression>node);
case SyntaxKind.OmittedExpression:
return;
case SyntaxKind.Block:
case SyntaxKind.ModuleBlock:
return emitBlock(<Block>node);
case SyntaxKind.VariableStatement:
return emitVariableStatement(<VariableStatement>node);
case SyntaxKind.EmptyStatement:
return write(';');
case SyntaxKind.ExpressionStatement:
return emitExpressionStatement(<ExpressionStatement>node);
case SyntaxKind.IfStatement:
return emitIfStatement(<IfStatement>node);
case SyntaxKind.DoStatement:
return emitDoStatement(<DoStatement>node);
case SyntaxKind.WhileStatement:
return emitWhileStatement(<WhileStatement>node);
case SyntaxKind.ForStatement:
return emitForStatement(<ForStatement>node);
case SyntaxKind.ForOfStatement:
case SyntaxKind.ForInStatement:
return emitForInOrForOfStatement(<ForInStatement>node);
case SyntaxKind.ContinueStatement:
case SyntaxKind.BreakStatement:
return emitBreakOrContinueStatement(<BreakOrContinueStatement>node);
case SyntaxKind.ReturnStatement:
return emitReturnStatement(<ReturnStatement>node);
case SyntaxKind.WithStatement:
return emitWithStatement(<WithStatement>node);
case SyntaxKind.SwitchStatement:
return emitSwitchStatement(<SwitchStatement>node);
case SyntaxKind.CaseClause:
case SyntaxKind.DefaultClause:
return emitCaseOrDefaultClause(<CaseOrDefaultClause>node);
case SyntaxKind.LabeledStatement:
return emitLabelledStatement(<LabeledStatement>node);
case SyntaxKind.ThrowStatement:
return emitThrowStatement(<ThrowStatement>node);
case SyntaxKind.TryStatement:
return emitTryStatement(<TryStatement>node);
case SyntaxKind.CatchClause:
return emitCatchClause(<CatchClause>node);
case SyntaxKind.DebuggerStatement:
return emitDebuggerStatement(node);
case SyntaxKind.VariableDeclaration:
return emitVariableDeclaration(<VariableDeclaration>node);
case SyntaxKind.ClassExpression:
return emitClassExpression(<ClassExpression>node);
case SyntaxKind.ClassDeclaration:
return emitClassDeclaration(<ClassDeclaration>node);
case SyntaxKind.InterfaceDeclaration:
return emitInterfaceDeclaration(<InterfaceDeclaration>node);
case SyntaxKind.EnumDeclaration:
return emitEnumDeclaration(<EnumDeclaration>node);
case SyntaxKind.EnumMember:
return emitEnumMember(<EnumMember>node);
case SyntaxKind.ModuleDeclaration:
return emitModuleDeclaration(<ModuleDeclaration>node);
case SyntaxKind.ImportDeclaration:
return emitImportDeclaration(<ImportDeclaration>node);
case SyntaxKind.ImportEqualsDeclaration:
return emitImportEqualsDeclaration(<ImportEqualsDeclaration>node);
case SyntaxKind.ExportDeclaration:
return emitExportDeclaration(<ExportDeclaration>node);
case SyntaxKind.ExportAssignment:
return emitExportAssignment(<ExportAssignment>node);
case SyntaxKind.SourceFile:
return emitSourceFileNode(<SourceFile>node);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
通过简单地调用相应的 emitXXX
函数来完成递归,例如 emitFunctionDeclaration
function emitFunctionDeclaration(node: FunctionLikeDeclaration) {
if (nodeIsMissing(node.body)) {
return emitOnlyPinnedOrTripleSlashComments(node);
}
if (node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.MethodSignature) {
// 会把注释当做方法声明的一部分去发射。
emitLeadingComments(node);
}
// 目标为 es6 之前时,使用 function 关键字来发射类函数(functions-like)声明,包括箭头函数
// 目标为 es6 时,可以发射原生的 ES6 箭头函数,并使用宽箭头代替 function 关键字.
if (!shouldEmitAsArrowFunction(node)) {
if (isES6ExportedDeclaration(node)) {
write('export ');
if (node.flags & NodeFlags.Default) {
write('default ');
}
}
write('function');
if (languageVersion >= ScriptTarget.ES6 && node.asteriskToken) {
write('*');
}
write(' ');
}
if (shouldEmitFunctionName(node)) {
emitDeclarationName(node);
}
emitSignatureAndBody(node);
if (
languageVersion < ScriptTarget.ES6 &&
node.kind === SyntaxKind.FunctionDeclaration &&
node.parent === currentSourceFile &&
node.name
) {
emitExportMemberAssignments((<FunctionDeclaration>node).name);
}
if (node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.MethodSignature) {
emitTrailingComments(node);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
发射器源映射(SourceMaps)
如前所述 emitter.ts
中的大部分代码是函数 emitJavaScript
(我们之前展示过该函数的初始化例程)。 它主要是设置一批本地变量并交给 emitSourceFile
处理。下面我们再看一遍这个函数,这次我们重点关注 SourceMap
的部分:
function emitJavaScript(jsFilePath: string, root?: SourceFile) {
// 无关代码 ........... 已移除
let writeComment = writeCommentRange;
/** 将发射的输出写到磁盘上 */
let writeEmittedFiles = writeJavaScriptFile;
/** 发射一个节点 */
let emit = emitNodeWithoutSourceMap;
/** 节点发射前调用 */
let emitStart = function (node: Node) { };
/** 节点发射完成后调用 */
let emitEnd = function (node: Node) { };
/** 从 startPos 位置开始,为指定的 token 发射文本。默认写入的文本由 tokenKind 提供,
* 但是如果提供了可选的 emitFn 回调,将使用该回调来代替默认方式发射文本。
* @param tokenKind 要搜索并发射的 token 的类别
* @param startPos 源码中搜索 token 的起始位置
* @param emitFn 如果给出,会被调用来进行文本的发射。*/
let emitToken = emitTokenText;
/** 该函数因为节点,会在发射的代码中于函数或类中启用词法作用域前调用
* @param scopeDeclaration 启动词法作用域的节点
* @param scopeName 可选的作用域的名称,而不是从节点声明中推导
*/
let scopeEmitStart = function(scopeDeclaration: Node, scopeName?: string) { };
/** 出了作用域后调用 */
let scopeEmitEnd = function() { };
/** 会被编码的 Sourcemap 数据 */
let sourceMapData: SourceMapData;
if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
initializeEmitterWithSourceMaps();
}
if (root) {
// 不要直接调用 emit,那样不会设置 currentSourceFile
emitSourceFile(root);
}
else {
forEach(host.getSourceFiles(), sourceFile => {
if (!isExternalModuleOrDeclarationFile(sourceFile)) {
emitSourceFile(sourceFile);
}
});
}
writeLine();
writeEmittedFiles(writer.getText(), /*writeByteOrderMark*/ compilerOptions.emitBOM);
return;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
重要的函数调用: initializeEmitterWithSourceMaps
,该函数是 emitJavaScript
的本地函数,它覆盖了部分已定义的本地函数。 覆盖的函数可以在 initalizeEmitterWithSourceMap
的底部找到:
// `initializeEmitterWithSourceMaps` 函数的最后部分
writeEmittedFiles = writeJavaScriptAndSourceMapFile;
emit = emitNodeWithSourceMap;
emitStart = recordEmitNodeStartSpan;
emitEnd = recordEmitNodeEndSpan;
emitToken = writeTextWithSpanRecord;
scopeEmitStart = recordScopeNameOfNode;
scopeEmitEnd = recordScopeNameEnd;
writeComment = writeCommentRangeWithMap;
2
3
4
5
6
7
8
9
10
就是说大部分的发射器代码不关心 SourceMap
,它们以相同的方式使用这些(带或不带 SourceMap 的)本地函数。