Skip to main content

The Emitter

The emitter is the final stage of compilation, responsible for generating output files. Located in src/compiler/emitter.ts (273KB), it transforms the type-checked AST into JavaScript and declaration files.

Emit Entry Point

// From src/compiler/emitter.ts (line 752)
export function emitFiles(
    resolver: EmitResolver,
    host: EmitHost,
    targetSourceFile: SourceFile | undefined,
    { scriptTransformers, declarationTransformers }: EmitTransformers,
    emitOnly: boolean | EmitOnly | undefined,
): EmitResult
The emitter uses the resolver to query type information and applies transformers to modify the output.

Emit Process

1

Transform AST

Apply transformers to modify the syntax tree:
// Transformers convert TypeScript features to JavaScript
// Example: async/await → generator functions (for ES5)
async function getData() {
    return await fetch("/api");
}

// Transformed to:
function getData() {
    return __awaiter(this, void 0, void 0, function* () {
        return yield fetch("/api");
    });
}
2

Generate Code

Convert AST nodes to JavaScript text:
// TypeScript input
interface User {
    name: string;
    age: number;
}

class UserManager {
    getUser(): User {
        return { name: "Alice", age: 30 };
    }
}

// JavaScript output (ES5)
var UserManager = /** @class */ (function () {
    function UserManager() {
    }
    UserManager.prototype.getUser = function () {
        return { name: "Alice", age: 30 };
    };
    return UserManager;
}());
3

Create Declaration Files

Generate .d.ts files for type information:
// Generated .d.ts file
interface User {
    name: string;
    age: number;
}

declare class UserManager {
    getUser(): User;
}
4

Generate Source Maps

Create mappings between output and source:
{
  "version": 3,
  "file": "output.js",
  "sourceRoot": "",
  "sources": ["input.ts"],
  "mappings": "AAAA,IAAM,CAAC,GAAG,CAAC..."
}

Output Formats

TypeScript can emit different JavaScript module formats:

Module Formats

Node.js style modules:
// Input
export function greet(name: string) {
    return `Hello, ${name}`;
}

// Output (CommonJS)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.greet = greet;
function greet(name) {
    return "Hello, " + name;
}
Config:
{
  "compilerOptions": {
    "module": "CommonJS"
  }
}

Emit Targets

The target option controls which JavaScript version is emitted:

ES3

Ancient JavaScript (IE8 and below)
{ "target": "ES3" }

ES5

Modern legacy (IE9-11)
{ "target": "ES5" }

ES2015+

Modern JavaScript
{ "target": "ES2020" }

ESNext

Latest features
{ "target": "ESNext" }

Target Impact on Output

ES2015+:
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}
ES5:
var Point = /** @class */ (function () {
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    return Point;
}());

Declaration Files

Declaration files (.d.ts) contain type information without implementation:

Generating Declarations

{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "emitDeclarationOnly": false
  }
}

declaration

Generate .d.ts files alongside JavaScript

declarationMap

Generate source maps for declaration files

emitDeclarationOnly

Emit only .d.ts files (skip JavaScript)

Declaration File Example

// source.ts
export class Calculator {
    private value = 0;
    
    add(n: number): this {
        this.value += n;
        return this;
    }
    
    getResult(): number {
        return this.value;
    }
}

export function multiply(a: number, b: number): number {
    return a * b;
}
// source.d.ts (generated)
export declare class Calculator {
    private value;
    add(n: number): this;
    getResult(): number;
}

export declare function multiply(a: number, b: number): number;

Source Maps

Source maps enable debugging TypeScript code in browsers:

Source Map Configuration

{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSourceMap": false,
    "inlineSources": false,
    "sourceRoot": "",
    "mapRoot": ""
  }
}
Generate separate .js.map files:
// output.js
var x = 1;
//# sourceMappingURL=output.js.map
Embed source map in JavaScript file:
// output.js
var x = 1;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9u...
Include original TypeScript source in source map:
{
  "version": 3,
  "sources": ["input.ts"],
  "sourcesContent": ["const x: number = 1;"],
  "mappings": "AAAA,IAAM,CAAC,GAAG,CAAC"
}

Transformers

Transformers modify the AST before emission:

Built-in Transformers

Removes TypeScript-specific syntax:
// Input
function greet(name: string): string {
    return `Hello, ${name}`;
}

// Output
function greet(name) {
    return `Hello, ${name}`;
}

Custom Transformers

// Define a custom transformer
import ts from "typescript";

const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
    return (sourceFile) => {
        const visitor = (node: ts.Node): ts.Node => {
            // Transform nodes here
            if (ts.isStringLiteral(node)) {
                return ts.factory.createStringLiteral(
                    node.text.toUpperCase()
                );
            }
            return ts.visitEachChild(node, visitor, context);
        };
        return ts.visitNode(sourceFile, visitor);
    };
};

// Use with program
program.emit(
    undefined,
    undefined,
    undefined,
    false,
    { before: [transformer] }
);

Emit Options

Output Control

{
  "compilerOptions": {
    "outDir": "./dist",
    "outFile": "./bundle.js",
    "removeComments": true,
    "noEmit": false,
    "noEmitOnError": true
  }
}

outDir

Directory for output files

outFile

Concatenate output into single file

removeComments

Strip comments from output

noEmit

Skip emit entirely (type checking only)

noEmitOnError

Don’t emit if there are errors

JavaScript Emit Options

{
  "compilerOptions": {
    "importHelpers": true,
    "downlevelIteration": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "newLine": "lf"
  }
}
Import helper functions from tslib instead of inlining:
// Without importHelpers
var __awaiter = (this && this.__awaiter) || function() { /* ... */ };

// With importHelpers
import { __awaiter } from "tslib";
Emit more accurate iteration for ES5/ES3:
// Input
for (const item of array) { }

// With downlevelIteration (accurate)
for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
    var item = array_1[_i];
}
Keep const enum declarations in output:
// Input
const enum E { A = 1 }

// Without preserve (inlined)
console.log(1);

// With preserve (kept)
var E;
(function (E) { E[E["A"] = 1] = "A"; })(E || (E = {}));
console.log(1);

Emit Helpers

TypeScript uses helper functions for downleveling:
// Common helpers from emitter.ts
var __extends = (this && this.__extends) || /* ... */;
var __awaiter = (this && this.__awaiter) || /* ... */;
var __generator = (this && this.__generator) || /* ... */;
var __decorate = (this && this.__decorate) || /* ... */;
var __spreadArray = (this && this.__spreadArray) || /* ... */;
Use importHelpers: true to import from tslib instead of duplicating helpers:
npm install tslib
{
  "compilerOptions": {
    "importHelpers": true
  }
}

Incremental Emit

TypeScript can store compilation state for faster rebuilds:
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo"
  }
}
The .tsbuildinfo file contains:
  • File hashes
  • Dependency graph
  • Emit signatures
  • Diagnostic information
Incremental compilation can dramatically speed up rebuilds by only recompiling changed files and their dependents.

Best Practices

Match Target to Environment

Set target based on your runtime:
{
  "compilerOptions": {
    "target": "ES2020",  // Modern browsers
    "lib": ["ES2020", "DOM"]
  }
}

Enable Source Maps

Always generate source maps for debugging:
{
  "compilerOptions": {
    "sourceMap": true
  }
}

Use Declaration Maps

For libraries, enable declaration maps:
{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true
  }
}

Optimize Bundle Size

Use importHelpers to reduce duplication:
{
  "compilerOptions": {
    "importHelpers": true
  }
}

Compiler Overview

Learn about the full compilation pipeline

Type Checking

Understand type validation

Build docs developers (and LLMs) love