typeScript-emitter 发射器

158 阅读7分钟

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

  1. emitter.ts:可能是你最感兴趣的发射器,它是 TS -> JavaScript 的发射器
  2. declarationEmitter.ts:这个发射器用于为 TypeScript 源文件(.ts) 创建声明文件(.d.ts)

Program 对发射器的使用

Program 提供了一个 emit 函数。该函数主要将功能委托给 emitter.ts 中的 emitFiles 函数。

Program.emit => emitWorker (在 program.ts 中的 createProgram => emitFilesemitter.ts 中的函数)

program.ts/emitWorker

function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnly?: boolean | EmitOnly, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult {
  if (!forceDtsEmit) {
    const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken);
    if (result) return result;
  }

  // 在下面的“emitTime”跟踪代码之外创建发射解析器。这样,与之相关的任何成本(如类型检查)都与类型检查计数器相关联。
  // 如果指定了-out选项,则不应将源文件传递给getEmitResolver。
  // 这是因为在-out场景中,需要发出所有文件,因此需要对所有文件进行类型检查。指定所有文件都需要进行类型检查的方法是不将文件传递给getEmitReve

  const emitResolver = getTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken);

  performance.mark("beforeEmit");

  const emitResult = emitFiles(
    emitResolver,
    getEmitHost(writeFileCallback),
    sourceFile,
    getTransformers(options, customTransformers, emitOnly),
    emitOnly,
    /*onlyBuildInfo*/ false,
    forceDtsEmit
  );

  performance.mark("afterEmit");
  performance.measure("Emit", "beforeEmit", "afterEmit");
  return emitResult;
}

emitWorker(通过 emitFiles 参数)给发射器提供一个 EmitResolvergetTypeChecker 就是 checker.ts 中的 createTypeChecker,它返回一个 TypeChecker接口的对象,里面包含了 getEmitResolver 函数。

emitFiles

/** @internal */
// 当用户只希望在整个项目中发出一个文件时,targetSourceFile。这在compileOnSave特性中使用
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, {
  scriptTransformers,
  declarationTransformers
}: EmitTransformers, emitOnly?: boolean | EmitOnly, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult {

  var compilerOptions = host.getCompilerOptions();
  var sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined;
  var emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined;
  var emitterDiagnostics = createDiagnosticCollection();
  var newLine = getNewLineCharacter(compilerOptions);
  var writer = createTextWriter(newLine);
  var {
    enter,
    exit
  } = performance.createTimer("printTime", "beforePrint", "afterPrint");
  var bundleBuildInfo: BundleBuildInfo | undefined;
  var emitSkipped = false;
  /* eslint-enable no-var */

  // Emit each output file
  enter();
  forEachEmittedFile(
    host,
    emitSourceFileOrBundle,
    getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit),
    forceDtsEmit,
    onlyBuildInfo,
    !targetSourceFile
  );
  exit();


  return {
    emitSkipped,
    diagnostics: emitterDiagnostics.getDiagnostics(),
    emittedFiles: emittedFilesList,
    sourceMaps: sourceMapDataList,
  };
}

这段代码的流程就是进入的时候通过 enter 函数的中的 mark 做个标记,发射文件,退出。核心就是 forEachEmittedFile

forEachEmittedFile

遍历源文件,输出具有emit的源文件,如果 sourceFilesOrTargetSourceFile 是数组,就遍历数组,如果不是数组,就调用 getSourceFilesToEmit 来获取要发射的源文件列表。


export function forEachEmittedFile<T>(
  host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T,
  sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile,
  forceDtsEmit = false,
  onlyBuildInfo?: boolean,
  includeBuildInfo?: boolean) {
  const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit);
  const options = host.getCompilerOptions();
  if (outFile(options)) {
    const prepends = host.getPrependNodes();
    if (sourceFiles.length || prepends.length) {
      const bundle = factory.createBundle(sourceFiles, prepends);
      const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle);
      if (result) {
        return result;
      }
    }
  } else {
    if (!onlyBuildInfo) {
      for (const sourceFile of sourceFiles) {
        const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile);
        if (result) {
          return result;
        }
      }
    }
    if (includeBuildInfo) {
      const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options);
      if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined);
    }
  }
}

可以看到最终都是通过 action 函数返回了一个 result, 这个 result 就是 emitSourceFileOrBundle 的返回值,也就是 emitResult

emitSourceFileOrBundle

function emitSourceFileOrBundle({
                                  jsFilePath,
                                  sourceMapFilePath,
                                  declarationFilePath,
                                  declarationMapPath,
                                  buildInfoPath
                                }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) {

  // ...
  tracing?.push(tracing.Phase.Emit, "emitJsFileOrBundle", { jsFilePath });
  emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo);
  tracing?.pop();

  tracing?.push(tracing.Phase.Emit, "emitDeclarationFileOrBundle", { declarationFilePath });
  emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo);
  tracing?.pop();

  tracing?.push(tracing.Phase.Emit, "emitBuildInfo", { buildInfoPath });
  emitBuildInfo(bundleBuildInfo, buildInfoPath);
  tracing?.pop();

  // ...
}

这一段代码就是发射 js 文件,声明文件,以及构建信息。即:emitJsFileOrBundle => emitDeclarationFileOrBundle => emitBuildInfo

emitJsFileOrBundle

function emitJsFileOrBundle(
  sourceFileOrBundle: SourceFile | Bundle | undefined,
  jsFilePath: string | undefined,
  sourceMapFilePath: string | undefined,
  relativeToBuildInfo: (path: string) => string) {
  if (!sourceFileOrBundle || emitOnly || !jsFilePath) {
    return;
  }

  // 如果无法写入js文件和源映射文件,请确保不要写入它们中的任何一个
  if (host.isEmitBlocked(jsFilePath) || compilerOptions.noEmit) {
    emitSkipped = true;
    return;
  }
  // 转换源文件
  const transform = transformNodes(resolver, host, factory, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false);

  const printerOptions: PrinterOptions = {
    removeComments: compilerOptions.removeComments,
    newLine: compilerOptions.newLine,
    noEmitHelpers: compilerOptions.noEmitHelpers,
    module: compilerOptions.module,
    target: compilerOptions.target,
    sourceMap: compilerOptions.sourceMap,
    inlineSourceMap: compilerOptions.inlineSourceMap,
    inlineSources: compilerOptions.inlineSources,
    extendedDiagnostics: compilerOptions.extendedDiagnostics,
    writeBundleFileInfo: !!bundleBuildInfo,
    relativeToBuildInfo
  };

  // Create a printer to print the nodes
  const printer = createPrinter(printerOptions, {
    // resolver hooks
    hasGlobalName: resolver.hasGlobalName,

    // transform hooks
    onEmitNode: transform.emitNodeWithNotification,
    isEmitNotificationEnabled: transform.isEmitNotificationEnabled,
    substituteNode: transform.substituteNode,
  });

  Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform");
  printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform, printer, compilerOptions);

  // Clean up emit nodes on parse tree
  transform.dispose();
  if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo;
}

发射 js 文件的核心就是 printSourceFileOrBundle 函数,它有接受一个 printer 的参数,这个 printer 是通过 createPrinter 创建的。

createPrinter

export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer {
  var {
    hasGlobalName,
    onEmitNode = noEmitNotification,
    isEmitNotificationEnabled,
    substituteNode = noEmitSubstitution,
    onBeforeEmitNode,
    onAfterEmitNode,
    onBeforeEmitNodeArray,
    onAfterEmitNodeArray,
    onBeforeEmitToken,
    onAfterEmitToken
  } = handlers;
  // ...
  var emitBinaryExpression = createEmitBinaryExpression();
  /* eslint-enable no-var */

  reset();
  return {
    // public API
    printNode,
    printList,
    printFile,
    printBundle,

    // internal API
    writeNode,
    writeList,
    writeFile,
    writeBundle,
    bundleFileInfo
  };
}

既然是创建,它自然会生成一些api,这些 api 就是我们在printSourceFileOrBundle中使用的。

我们接着在看一下var emitBinaryExpression = createEmitBinaryExpression();

createEmitBinaryExpression

 function createEmitBinaryExpression() {
  interface WorkArea {
    stackIndex: number;
    preserveSourceNewlinesStack: (boolean | undefined)[];
    containerPosStack: number[];
    containerEndStack: number[];
    declarationListContainerEndStack: number[];
    shouldEmitCommentsStack: boolean[];
    shouldEmitSourceMapsStack: boolean[];
  }

  return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined);

  function onEnter(node: BinaryExpression, state: WorkArea | undefined) {
  }

  function onLeft(next: Expression, _workArea: WorkArea, parent: BinaryExpression) {
    return maybeEmitExpression(next, parent, "left");
  }

  function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) {

  }

  function onRight(next: Expression, _workArea: WorkArea, parent: BinaryExpression) {
    return maybeEmitExpression(next, parent, "right");
  }

  function onExit(node: BinaryExpression, state: WorkArea) {

  }

  function maybeEmitExpression(next: Expression, parent: BinaryExpression, side: "left" | "right") {
    const parenthesizerRule = side === "left" ?
      parenthesizer.getParenthesizeLeftSideOfBinaryForOperator(parent.operatorToken.kind) :
      parenthesizer.getParenthesizeRightSideOfBinaryForOperator(parent.operatorToken.kind);

    let pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next);
    if (pipelinePhase === pipelineEmitWithSubstitution) {
      Debug.assertIsDefined(lastSubstitution);
      next = parenthesizerRule(cast(lastSubstitution, isExpression));
      pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, EmitHint.Expression, next);
      lastSubstitution = undefined;
    }

    if (pipelinePhase === pipelineEmitWithComments ||
      pipelinePhase === pipelineEmitWithSourceMaps ||
      pipelinePhase === pipelineEmitWithHint) {
      if (isBinaryExpression(next)) {
        return next;
      }
    }

    currentParenthesizerRule = parenthesizerRule;
    pipelinePhase(EmitHint.Expression, next);
  }
}

createBinaryExpressionTrampoline

函数签名如下:

/**
 * 创建一个状态机,该状态机使用堆遍历“BinaryExpression”以减少大型树上的调用堆栈深度。
 * @param onEnter 输入“BinaryExpression”时计算的回调。返回新的用户定义状态,以便在行走时与节点关联。
 * @param onLeft 在“BinaryExpression”的左侧遍历时计算的回调。返回“BinaryExpression”以继续行走,或返回“void”以前进到右侧。
 * @param onRight 在“BinaryExpression”的右侧遍历时计算的回调。返回“BinaryExpression”以继续遍历,或返回“void”以前进到节点的末尾。
 * @param onExit 退出“BinaryExpression”时计算的回调。返回的结果将被折叠到父对象的状态,或者如果位于顶部框架,则从助行器返回。
 * @param foldState 当嵌套“onExit”的结果应折叠到该节点的父节点的状态时,计算回调
 * @returns 一个函数,使用上面的回调遍历“BinaryExpression”节点,从最外层的“BinaryExpression”节点返回对“onExit”的调用结果。
 *
 * @internal
 */
export function createBinaryExpressionTrampoline<TOuterState, TState, TResult>(
  onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState,
  onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
  onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined,
  onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined,
  onExit: (node: BinaryExpression, userState: TState) => TResult,
  foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined,
): (node: BinaryExpression, outerState: TOuterState) => TResult;

getPipelinePhase

在创建 emitBinaryExpression 的时候,我们调用了 getPipelinePhase 函数,它的函数签名如下:

function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) {
  switch (phase) {
    case PipelinePhase.Notification:
      if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) {
        return pipelineEmitWithNotification;
      }
    // falls through
    case PipelinePhase.Substitution:
      if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node) || node) !== node) {
        if (currentParenthesizerRule) {
          lastSubstitution = currentParenthesizerRule(lastSubstitution);
        }
        return pipelineEmitWithSubstitution;
      }
    // falls through
    case PipelinePhase.Comments:
      if (shouldEmitComments(node)) {
        return pipelineEmitWithComments;
      }
    // falls through
    case PipelinePhase.SourceMaps:
      if (shouldEmitSourceMaps(node)) {
        return pipelineEmitWithSourceMaps;
      }
    // falls through
    case PipelinePhase.Emit:
      return pipelineEmitWithHint;
    default:
      return Debug.assertNever(phase);
  }
}


PipelinePhase 是一个枚举类型,它的值如下:

const enum PipelinePhase {
  Notification,
  Substitution,
  Comments,
  SourceMaps,
  Emit,
}

先看 PipelinePhase.Notification: 这个类型,他最终返回的是 pipelineEmitWithNotification,这个函数的函数签名如下:


function pipelineEmitWithNotification(hint: EmitHint, node: Node) {
  const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node);
  onEmitNode(hint, node, pipelinePhase);
}

function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) {
  return getPipelinePhase(currentPhase + 1, emitHint, node);
}

终于看到 onEmitNode 这个函数了,它还记得 createPrinter 这个不?

export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer {
  var {
    onEmitNode = noEmitNotification,
    onBeforeEmitNode
  } = handlers;
}

export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) {
  callback(hint, node);
}

对应 pipelineEmitWithNotification 就是执行了一个回调函数。

我们再接着看 getPipelinePhase, 在匹配到 phase 的类型为 PipelinePhase.Emit 的时候,就会执行 pipelineEmitWithHint

pipelineEmitWithHint

function pipelineEmitWithHint(hint: EmitHint, node: Node): void {
  onBeforeEmitNode?.(node);
  if (preserveSourceNewlines) {
    const savedPreserveSourceNewlines = preserveSourceNewlines;
    beforeEmitNode(node);
    pipelineEmitWithHintWorker(hint, node);
    afterEmitNode(savedPreserveSourceNewlines);
  } else {
    pipelineEmitWithHintWorker(hint, node);
  }
  onAfterEmitNode?.(node);
  // clear the parenthesizer rule as we ascend
  currentParenthesizerRule = undefined;
}

// 保存 preserveSourceNewlines 的值
function afterEmitNode(savedPreserveSourceNewlines: boolean | undefined) {
  preserveSourceNewlines = savedPreserveSourceNewlines;
}

onBeforeEmitNode, onAfterEmitNode 同上 createPrinter 是一个可选参数。

pipelineEmitWithHintWorker

function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void {
  if (allowSnippets) {
    const snippet = getSnippetElement(node);
    if (snippet) {
      return emitSnippetNode(hint, node, snippet);
    }
  }
  if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile));
  if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier));
  if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true);
  if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration));
  if (hint === EmitHint.EmbeddedStatement) {
    Debug.assertNode(node, isEmptyStatement);
    return emitEmptyStatement(/*isEmbeddedStatement*/ true);
  }
  if (hint === EmitHint.Unspecified) {
    switch (node.kind) {
      // Pseudo-literals
      case SyntaxKind.TemplateHead:
      case SyntaxKind.TemplateMiddle:
      case SyntaxKind.TemplateTail:
        return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false);

      // ...
    }
    if (isExpression(node)) {
      hint = EmitHint.Expression;
      if (substituteNode !== noEmitSubstitution) {
        const substitute = substituteNode(hint, node) || node;
        if (substitute !== node) {
          node = substitute;
          if (currentParenthesizerRule) {
            node = currentParenthesizerRule(node);
          }
        }
      }
    }
  }
  if (hint === EmitHint.Expression) {
    switch (node.kind) {
      // Literals
      case SyntaxKind.NumericLiteral:
      case SyntaxKind.BigIntLiteral:
        return emitNumericOrBigIntLiteral(node as NumericLiteral | BigIntLiteral);

      // ...
    }
  }
  if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword);
  if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation);
  Debug.fail(`Unhandled SyntaxKind: ${ Debug.formatSyntaxKind(node.kind) }.`);
}

通过不同的 hint 来调用不同的函数,比如 emitSourceFileemitIdentifier 等等。

emitIdentifier

看个比较简单的 emitIdentifier

function emitIdentifier(node: Identifier) {
  const writeText = node.symbol ? writeSymbol : write;
  writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
  emitList(node, getIdentifierTypeArguments(node), ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
}

这里就简单明了了,先写入,再发射。

emitList

 function emitList<Child extends Node, Children extends NodeArray<Child>>(parentNode: Node | undefined, children: Children | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector<Child>, start?: number, count?: number) {
  emitNodeList(
    emit,
    parentNode,
    children,
    format | (parentNode && getEmitFlags(parentNode) & EmitFlags.MultiLine ? ListFormat.PreferNewLine : 0),
    parenthesizerRule,
    start,
    count);
}

这里有个 emit 函数:

function emit<T extends Node>(node: T, parenthesizerRule?: (node: T) => T): void;
function emit<T extends Node>(node: T | undefined, parenthesizerRule?: (node: T) => T): void;
function emit<T extends Node>(node: T | undefined, parenthesizerRule?: (node: T) => T) {
  if (node === undefined) return;
  const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node);
  pipelineEmit(EmitHint.Unspecified, node, parenthesizerRule);
  recordBundleFileInternalSectionEnd(prevSourceFileTextKind);
}

function pipelineEmit<T extends Node>(emitHint: EmitHint, node: T, parenthesizerRule?: (node: T) => T) {
  currentParenthesizerRule = parenthesizerRule;
  const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node);
  pipelinePhase(emitHint, node);
  currentParenthesizerRule = undefined;
}

getPipelinePhase 这里又是一个递归实现

function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType<typeof recordBundleFileInternalSectionStart>) {
  if (prevSourceFileTextKind) {
    recordBundleFileTextLikeSection(writer.getTextPos());
    sourceFileTextPos = getTextPosWithWriteLine();
    sourceFileTextKind = prevSourceFileTextKind;
  }
}

function recordBundleFileTextLikeSection(end: number) {
  if (sourceFileTextPos < end) {
    updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind);
    return true;
  }
  return false;
}

function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) {
  const last = lastOrUndefined(bundleFileInfo!.sections);
  if (last && last.kind === kind) {
    last.end = end;
  } else {
    bundleFileInfo!.sections.push({
      pos,
      end,
      kind
    });
  }
}

emitter