发射器

TypeScript 编译器提供了两个发射器:

  • emitter.ts:可能是你最感兴趣的发射器,它是 TS -> JavaScript 的发射器
  • declarationEmitter.ts:这个发射器用于为 TypeScript 源文件(.ts 创建声明文件(.d.ts
    本节我们介绍 emitter.ts

Promgram 对发射器的使用

Program 提供了一个 emit 函数。该函数主要将功能委托给 emitter.ts中的 emitFiles 函数。下面是调用栈:

  1. Program.emit ->
  2. `emitWorker` (在 program.ts 中的 createProgram ->
  3. `emitFiles` emitter.ts 中的函数)

emitWorker(通过 emitFiles 参数)给发射器提供一个 EmitResolverEmitResolver 由程序的 TypeChecker 提供,基本上它是一个来自 createChecker 的本地函数的子集。

发射器函数

emitFiles

定义在 emitter.ts 中,下面是该函数的签名:

  1. // targetSourceFile 当用户想发射项目中的某个文件时指定,保存时编译(compileOnSave)功能使用此参数
  2. export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile?: SourceFile): EmitResult {

EmitHostCompilerHost 的简化版(运行时,很多用例实际上都是 CompilerHost

emitFiles 中的最有趣的调用栈如下所示:

  1. emitFiles ->
  2. emitFile(jsFilePath, targetSourceFile) ->
  3. emitJavaScript(jsFilePath, targetSourceFile);

emitJavaScript

该函数有良好的注释,我们下面给出它:

  1. function emitJavaScript(jsFilePath: string, root?: SourceFile) {
  2. let writer = createTextWriter(newLine);
  3. let write = writer.write;
  4. let writeTextOfNode = writer.writeTextOfNode;
  5. let writeLine = writer.writeLine;
  6. let increaseIndent = writer.increaseIndent;
  7. let decreaseIndent = writer.decreaseIndent;
  8. let currentSourceFile: SourceFile;
  9. // 导出器函数的名称,如果文件是个系统外部模块的话
  10. // System.register([...], function (<exporter>) {...})
  11. // System 模块中的导出像这样:
  12. // export var x; ... x = 1
  13. // =>
  14. // var x;... exporter("x", x = 1)
  15. let exportFunctionForFile: string;
  16. let generatedNameSet: Map<string> = {};
  17. let nodeToGeneratedName: string[] = [];
  18. let computedPropertyNamesToGeneratedNames: string[];
  19. let extendsEmitted = false;
  20. let decorateEmitted = false;
  21. let paramEmitted = false;
  22. let awaiterEmitted = false;
  23. let tempFlags = 0;
  24. let tempVariables: Identifier[];
  25. let tempParameters: Identifier[];
  26. let externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[];
  27. let exportSpecifiers: Map<ExportSpecifier[]>;
  28. let exportEquals: ExportAssignment;
  29. let hasExportStars: boolean;
  30. /** 将发射输出写入磁盘 */
  31. let writeEmittedFiles = writeJavaScriptFile;
  32. let detachedCommentsInfo: { nodePos: number; detachedCommentEndPos: number }[];
  33. let writeComment = writeCommentRange;
  34. /** 发射一个节点 */
  35. let emit = emitNodeWithoutSourceMap;
  36. /** 在发射节点前调用 */
  37. let emitStart = function(node: Node) {};
  38. /** 发射结点完成后调用 */
  39. let emitEnd = function(node: Node) {};
  40. /** 从 startPos 位置开始,为指定的 token 发射文本。默认写入的文本由 tokenKind 提供,
  41. * 但是如果提供了可选的 emitFn 回调,将使用该回调来代替默认方式发射文本。
  42. * @param tokenKind 要搜索并发射的 token 的类别
  43. * @param startPos 源码中搜索 token 的起始位置
  44. * @param emitFn 如果给出,会被调用来进行文本的发射。
  45. */
  46. let emitToken = emitTokenText;
  47. /** 该函数因为节点,会在发射的代码中于函数或类中启用词法作用域前调用
  48. * @param scopeDeclaration 启动词法作用域的节点
  49. * @param scopeName 可选的作用域的名称,默认从节点声明中推导
  50. */
  51. let scopeEmitStart = function(scopeDeclaration: Node, scopeName?: string) {};
  52. /** 出了作用域后调用 */
  53. let scopeEmitEnd = function() {};
  54. /** 会被编码的 Sourcemap 数据 */
  55. let sourceMapData: SourceMapData;
  56. if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
  57. initializeEmitterWithSourceMaps();
  58. }
  59. if (root) {
  60. // 不要直接调用 emit,那样不会设置 currentSourceFile
  61. emitSourceFile(root);
  62. } else {
  63. forEach(host.getSourceFiles(), sourceFile => {
  64. if (!isExternalModuleOrDeclarationFile(sourceFile)) {
  65. emitSourceFile(sourceFile);
  66. }
  67. });
  68. }
  69. writeLine();
  70. writeEmittedFiles(writer.getText(), /*writeByteOrderMark*/ compilerOptions.emitBOM);
  71. return;
  72. /// 一批本地函数
  73. }

它主要设置了一批本地变量和函数(这些函数构成 emitter.ts大部分内容),接着交给本地函数 emitSourceFile 发射文本。emitSourceFile 函数设置 currentSourceFile 然后交给本地函数 emit 去处理。

  1. function emitSourceFile(sourceFile: SourceFile): void {
  2. currentSourceFile = sourceFile;
  3. exportFunctionForFile = undefined;
  4. emit(sourceFile);
  5. }

emit 函数处理 注释实际 JavaScript 的发射。实际 JavaScript 的发射是 emitJavaScriptWorker 函数的工作。

emitJavaScriptWorker

完整的函数:

  1. function emitJavaScriptWorker(node: Node) {
  2. // 检查节点是否可以忽略 ScriptTarget 发射
  3. switch (node.kind) {
  4. case SyntaxKind.Identifier:
  5. return emitIdentifier(<Identifier>node);
  6. case SyntaxKind.Parameter:
  7. return emitParameter(<ParameterDeclaration>node);
  8. case SyntaxKind.MethodDeclaration:
  9. case SyntaxKind.MethodSignature:
  10. return emitMethod(<MethodDeclaration>node);
  11. case SyntaxKind.GetAccessor:
  12. case SyntaxKind.SetAccessor:
  13. return emitAccessor(<AccessorDeclaration>node);
  14. case SyntaxKind.ThisKeyword:
  15. return emitThis(node);
  16. case SyntaxKind.SuperKeyword:
  17. return emitSuper(node);
  18. case SyntaxKind.NullKeyword:
  19. return write('null');
  20. case SyntaxKind.TrueKeyword:
  21. return write('true');
  22. case SyntaxKind.FalseKeyword:
  23. return write('false');
  24. case SyntaxKind.NumericLiteral:
  25. case SyntaxKind.StringLiteral:
  26. case SyntaxKind.RegularExpressionLiteral:
  27. case SyntaxKind.NoSubstitutionTemplateLiteral:
  28. case SyntaxKind.TemplateHead:
  29. case SyntaxKind.TemplateMiddle:
  30. case SyntaxKind.TemplateTail:
  31. return emitLiteral(<LiteralExpression>node);
  32. case SyntaxKind.TemplateExpression:
  33. return emitTemplateExpression(<TemplateExpression>node);
  34. case SyntaxKind.TemplateSpan:
  35. return emitTemplateSpan(<TemplateSpan>node);
  36. case SyntaxKind.JsxElement:
  37. case SyntaxKind.JsxSelfClosingElement:
  38. return emitJsxElement(<JsxElement | JsxSelfClosingElement>node);
  39. case SyntaxKind.JsxText:
  40. return emitJsxText(<JsxText>node);
  41. case SyntaxKind.JsxExpression:
  42. return emitJsxExpression(<JsxExpression>node);
  43. case SyntaxKind.QualifiedName:
  44. return emitQualifiedName(<QualifiedName>node);
  45. case SyntaxKind.ObjectBindingPattern:
  46. return emitObjectBindingPattern(<BindingPattern>node);
  47. case SyntaxKind.ArrayBindingPattern:
  48. return emitArrayBindingPattern(<BindingPattern>node);
  49. case SyntaxKind.BindingElement:
  50. return emitBindingElement(<BindingElement>node);
  51. case SyntaxKind.ArrayLiteralExpression:
  52. return emitArrayLiteral(<ArrayLiteralExpression>node);
  53. case SyntaxKind.ObjectLiteralExpression:
  54. return emitObjectLiteral(<ObjectLiteralExpression>node);
  55. case SyntaxKind.PropertyAssignment:
  56. return emitPropertyAssignment(<PropertyDeclaration>node);
  57. case SyntaxKind.ShorthandPropertyAssignment:
  58. return emitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);
  59. case SyntaxKind.ComputedPropertyName:
  60. return emitComputedPropertyName(<ComputedPropertyName>node);
  61. case SyntaxKind.PropertyAccessExpression:
  62. return emitPropertyAccess(<PropertyAccessExpression>node);
  63. case SyntaxKind.ElementAccessExpression:
  64. return emitIndexedAccess(<ElementAccessExpression>node);
  65. case SyntaxKind.CallExpression:
  66. return emitCallExpression(<CallExpression>node);
  67. case SyntaxKind.NewExpression:
  68. return emitNewExpression(<NewExpression>node);
  69. case SyntaxKind.TaggedTemplateExpression:
  70. return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
  71. case SyntaxKind.TypeAssertionExpression:
  72. return emit((<TypeAssertion>node).expression);
  73. case SyntaxKind.AsExpression:
  74. return emit((<AsExpression>node).expression);
  75. case SyntaxKind.ParenthesizedExpression:
  76. return emitParenExpression(<ParenthesizedExpression>node);
  77. case SyntaxKind.FunctionDeclaration:
  78. case SyntaxKind.FunctionExpression:
  79. case SyntaxKind.ArrowFunction:
  80. return emitFunctionDeclaration(<FunctionLikeDeclaration>node);
  81. case SyntaxKind.DeleteExpression:
  82. return emitDeleteExpression(<DeleteExpression>node);
  83. case SyntaxKind.TypeOfExpression:
  84. return emitTypeOfExpression(<TypeOfExpression>node);
  85. case SyntaxKind.VoidExpression:
  86. return emitVoidExpression(<VoidExpression>node);
  87. case SyntaxKind.AwaitExpression:
  88. return emitAwaitExpression(<AwaitExpression>node);
  89. case SyntaxKind.PrefixUnaryExpression:
  90. return emitPrefixUnaryExpression(<PrefixUnaryExpression>node);
  91. case SyntaxKind.PostfixUnaryExpression:
  92. return emitPostfixUnaryExpression(<PostfixUnaryExpression>node);
  93. case SyntaxKind.BinaryExpression:
  94. return emitBinaryExpression(<BinaryExpression>node);
  95. case SyntaxKind.ConditionalExpression:
  96. return emitConditionalExpression(<ConditionalExpression>node);
  97. case SyntaxKind.SpreadElementExpression:
  98. return emitSpreadElementExpression(<SpreadElementExpression>node);
  99. case SyntaxKind.YieldExpression:
  100. return emitYieldExpression(<YieldExpression>node);
  101. case SyntaxKind.OmittedExpression:
  102. return;
  103. case SyntaxKind.Block:
  104. case SyntaxKind.ModuleBlock:
  105. return emitBlock(<Block>node);
  106. case SyntaxKind.VariableStatement:
  107. return emitVariableStatement(<VariableStatement>node);
  108. case SyntaxKind.EmptyStatement:
  109. return write(';');
  110. case SyntaxKind.ExpressionStatement:
  111. return emitExpressionStatement(<ExpressionStatement>node);
  112. case SyntaxKind.IfStatement:
  113. return emitIfStatement(<IfStatement>node);
  114. case SyntaxKind.DoStatement:
  115. return emitDoStatement(<DoStatement>node);
  116. case SyntaxKind.WhileStatement:
  117. return emitWhileStatement(<WhileStatement>node);
  118. case SyntaxKind.ForStatement:
  119. return emitForStatement(<ForStatement>node);
  120. case SyntaxKind.ForOfStatement:
  121. case SyntaxKind.ForInStatement:
  122. return emitForInOrForOfStatement(<ForInStatement>node);
  123. case SyntaxKind.ContinueStatement:
  124. case SyntaxKind.BreakStatement:
  125. return emitBreakOrContinueStatement(<BreakOrContinueStatement>node);
  126. case SyntaxKind.ReturnStatement:
  127. return emitReturnStatement(<ReturnStatement>node);
  128. case SyntaxKind.WithStatement:
  129. return emitWithStatement(<WithStatement>node);
  130. case SyntaxKind.SwitchStatement:
  131. return emitSwitchStatement(<SwitchStatement>node);
  132. case SyntaxKind.CaseClause:
  133. case SyntaxKind.DefaultClause:
  134. return emitCaseOrDefaultClause(<CaseOrDefaultClause>node);
  135. case SyntaxKind.LabeledStatement:
  136. return emitLabelledStatement(<LabeledStatement>node);
  137. case SyntaxKind.ThrowStatement:
  138. return emitThrowStatement(<ThrowStatement>node);
  139. case SyntaxKind.TryStatement:
  140. return emitTryStatement(<TryStatement>node);
  141. case SyntaxKind.CatchClause:
  142. return emitCatchClause(<CatchClause>node);
  143. case SyntaxKind.DebuggerStatement:
  144. return emitDebuggerStatement(node);
  145. case SyntaxKind.VariableDeclaration:
  146. return emitVariableDeclaration(<VariableDeclaration>node);
  147. case SyntaxKind.ClassExpression:
  148. return emitClassExpression(<ClassExpression>node);
  149. case SyntaxKind.ClassDeclaration:
  150. return emitClassDeclaration(<ClassDeclaration>node);
  151. case SyntaxKind.InterfaceDeclaration:
  152. return emitInterfaceDeclaration(<InterfaceDeclaration>node);
  153. case SyntaxKind.EnumDeclaration:
  154. return emitEnumDeclaration(<EnumDeclaration>node);
  155. case SyntaxKind.EnumMember:
  156. return emitEnumMember(<EnumMember>node);
  157. case SyntaxKind.ModuleDeclaration:
  158. return emitModuleDeclaration(<ModuleDeclaration>node);
  159. case SyntaxKind.ImportDeclaration:
  160. return emitImportDeclaration(<ImportDeclaration>node);
  161. case SyntaxKind.ImportEqualsDeclaration:
  162. return emitImportEqualsDeclaration(<ImportEqualsDeclaration>node);
  163. case SyntaxKind.ExportDeclaration:
  164. return emitExportDeclaration(<ExportDeclaration>node);
  165. case SyntaxKind.ExportAssignment:
  166. return emitExportAssignment(<ExportAssignment>node);
  167. case SyntaxKind.SourceFile:
  168. return emitSourceFileNode(<SourceFile>node);
  169. }
  170. }

通过简单地调用相应的 emitXXX 函数来完成递归,例如 emitFunctionDeclaration

  1. function emitFunctionDeclaration(node: FunctionLikeDeclaration) {
  2. if (nodeIsMissing(node.body)) {
  3. return emitOnlyPinnedOrTripleSlashComments(node);
  4. }
  5. if (node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.MethodSignature) {
  6. // 会把注释当做方法声明的一部分去发射。
  7. emitLeadingComments(node);
  8. }
  9. // 目标为 es6 之前时,使用 function 关键字来发射类函数(functions-like)声明,包括箭头函数
  10. // 目标为 es6 时,可以发射原生的 ES6 箭头函数,并使用宽箭头代替 function 关键字.
  11. if (!shouldEmitAsArrowFunction(node)) {
  12. if (isES6ExportedDeclaration(node)) {
  13. write('export ');
  14. if (node.flags & NodeFlags.Default) {
  15. write('default ');
  16. }
  17. }
  18. write('function');
  19. if (languageVersion >= ScriptTarget.ES6 && node.asteriskToken) {
  20. write('*');
  21. }
  22. write(' ');
  23. }
  24. if (shouldEmitFunctionName(node)) {
  25. emitDeclarationName(node);
  26. }
  27. emitSignatureAndBody(node);
  28. if (
  29. languageVersion < ScriptTarget.ES6 &&
  30. node.kind === SyntaxKind.FunctionDeclaration &&
  31. node.parent === currentSourceFile &&
  32. node.name
  33. ) {
  34. emitExportMemberAssignments((<FunctionDeclaration>node).name);
  35. }
  36. if (node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.MethodSignature) {
  37. emitTrailingComments(node);
  38. }
  39. }

发射器源映射(SourceMaps)

如前所述 emitter.ts 的大部分是本地函数 emitJavaScript(我们之前展示过该函数的初始化例程)。它主要是设置一批本地变量并交给 emitSourceFile 处理。下面我们再看一遍这个函数,这次我们重点关注 SourceMap 的部分:

  1. function emitJavaScript(jsFilePath: string, root?: SourceFile) {
  2. // 无关代码 ........... 已移除
  3. let writeComment = writeCommentRange;
  4. /** 将发射的输出写到磁盘上 */
  5. let writeEmittedFiles = writeJavaScriptFile;
  6. /** 发射一个节点 */
  7. let emit = emitNodeWithoutSourceMap;
  8. /** 节点发射前调用 */
  9. let emitStart = function (node: Node) { };
  10. /** 节点发射完成后调用 */
  11. let emitEnd = function (node: Node) { };
  12. /** 从 startPos 位置开始,为指定的 token 发射文本。默认写入的文本由 tokenKind 提供,
  13. * 但是如果提供了可选的 emitFn 回调,将使用该回调来代替默认方式发射文本。
  14. * @param tokenKind 要搜索并发射的 token 的类别
  15. * @param startPos 源码中搜索 token 的起始位置
  16. * @param emitFn 如果给出,会被调用来进行文本的发射。*/
  17. let emitToken = emitTokenText;
  18. /** 该函数因为节点,会在发射的代码中于函数或类中启用词法作用域前调用
  19. * @param scopeDeclaration 启动词法作用域的节点
  20. * @param scopeName 可选的作用域的名称,默认从节点声明中推导
  21. */
  22. let scopeEmitStart = function(scopeDeclaration: Node, scopeName?: string) { };
  23. /** 出了作用域后调用 */
  24. let scopeEmitEnd = function() { };
  25. /** 会被编码的 Sourcemap 数据 */
  26. let sourceMapData: SourceMapData;
  27. if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
  28. initializeEmitterWithSourceMaps();
  29. }
  30. if (root) {
  31. // 不要直接调用 emit,那样不会设置 currentSourceFile
  32. emitSourceFile(root);
  33. }
  34. else {
  35. forEach(host.getSourceFiles(), sourceFile => {
  36. if (!isExternalModuleOrDeclarationFile(sourceFile)) {
  37. emitSourceFile(sourceFile);
  38. }
  39. });
  40. }
  41. writeLine();
  42. writeEmittedFiles(writer.getText(), /*writeByteOrderMark*/ compilerOptions.emitBOM);
  43. return;

重要的函数调用是 initializeEmitterWithSourceMaps,该函数是 emitJavaScript 的本地函数,它覆盖了部分已定义的本地函数。覆盖的函数可以在 initalizeEmitterWithSourceMap 的底部找到:

  1. // `initializeEmitterWithSourceMaps` 函数的最后部分
  2. writeEmittedFiles = writeJavaScriptAndSourceMapFile;
  3. emit = emitNodeWithSourceMap;
  4. emitStart = recordEmitNodeStartSpan;
  5. emitEnd = recordEmitNodeEndSpan;
  6. emitToken = writeTextWithSpanRecord;
  7. scopeEmitStart = recordScopeNameOfNode;
  8. scopeEmitEnd = recordScopeNameEnd;
  9. writeComment = writeCommentRangeWithMap;

就是说大部分的发射器代码不关心 SourceMap,它们以相同的方式使用这些(带或不带 SourceMap 的)本地函数。

原文: https://jkchao.github.io/typescript-book-chinese/compiler/emitter.html