[![npm version](https://badge.fury.io/js/ts-patch.svg)](https://badge.fury.io/js/ts-patch)
[![NPM Downloads](https://img.shields.io/npm/dm/ts-patch.svg?style=flat)](https://npmjs.org/package/ts-patch)
![Build Status](https://github.com/nonara/ts-patch/workflows/Build/badge.svg)

# ts-patch

Patch typescript to allow custom transformers (plugins) during build.

Plugins are specified in `tsconfig.json`, or provided programmatically in `CompilerOptions`.

> [!IMPORTANT]
> ts-patch v4 supports TypeScript 6 and later only. For TypeScript 5 projects, use ts-patch v3.

_Migrating from ttypescript is easy! See: [Method 1: Live Compiler](#method-1-live-compiler)_

## Features

* Patch typescript via on-the-fly, in-memory compiler routes _or_ persistent patches for supported files
* Can patch supported compiler libraries (see `ts-patch /?`)
* Hook build process by transforming the `Program` (see: [Transforming Program](#transforming-program))
* Add, remove, or modify diagnostics (see: [Altering Diagnostics](#altering-diagnostics))
* Fully compatible with legacy [ttypescript](https://github.com/cevek/ttypescript) projects
* **(new)** Experimental support for ES Module based transformers

# Table of Contents

<!-- TOC -->
* [ts-patch](#ts-patch)
  * [Features](#features)
* [Table of Contents](#table-of-contents)
* [Installation](#installation)
* [Usage](#usage)
  * [Method 1: Live Compiler](#method-1-live-compiler)
  * [Method 2: Persistent Patch](#method-2-persistent-patch)
* [Configuration](#configuration)
  * [Plugin Options](#plugin-options)
* [Writing Transformers](#writing-transformers)
  * [Source Transformers](#source-transformers)
    * [Source Transformer Entry Point](#source-transformer-entry-point)
    * [Source Transformer Example](#source-transformer-example)
    * [Altering Diagnostics](#altering-diagnostics)
    * [Note](#note)
  * [Program Transformers](#program-transformers)
    * [Program Transformer Entry Point](#program-transformer-entry-point)
    * [Configuring Program Transformers](#configuring-program-transformers)
    * [Program Transformer Example](#program-transformer-example)
  * [Plugin Package Configuration](#plugin-package-configuration)
    * [Example](#example)
  * [Resources](#resources)
    * [Recommended Reading](#recommended-reading)
    * [Recommended Tools](#recommended-tools)
    * [Discussion](#discussion)
* [Advanced Options](#advanced-options)
* [Compatibility Direction](#compatibility-direction)
* [Maintainers](#maintainers)
  * [Help Wanted](#help-wanted)
* [License](#license)
<!-- TOC -->

# Installation

1. Install package
```sh
<yarn|npm|pnpm> add -D ts-patch
```

# Usage

## Method 1: Live Compiler

The live compiler patches on-the-fly, each time it is run.

**Via commandline:** Simply use `tspc` (instead of `tsc`)

**With tools such as ts-node, webpack, ts-jest, etc:** specify the compiler as  `ts-patch/compiler`

Additional live routes are available for tools that need a specific TypeScript entry:

| Route | Library identity supplied to transformers |
|-------|-------------------------------------------|
| `ts-patch/compiler` | `typescript` |
| `ts-patch/compiler/typescript` | `typescript` |
| `ts-patch/compiler/tsc` | `tsc` |
| `ts-patch/compiler/tsserver` | `tsserver` |
| `ts-patch/compiler/tsserverlibrary` | `tsserverlibrary` |

## Method 2: Persistent Patch

Persistent patching modifies supported TypeScript files inside `node_modules`. It is still available for TypeScript 6,
but the patchable surface is narrower than it was in earlier TypeScript versions.

TypeScript 6 ships thin entry shims for several library files. Some service entries delegate to shared implementation
files; for example, `tsserverlibrary.js` delegates to `typescript.js`. Rewriting those service files in place would not
preserve enough entry context to distinguish direct compiler API use from `tsc`, `tsserver`, or `tsserverlibrary` use.
For those entries, use the live compiler routes instead.

`install` and `uninstall` remain available, but they now affect only `typescript.js` and `tsc.js`:

```shell
ts-patch install
ts-patch uninstall
```

The lower-level `patch` and `unpatch` commands are also limited to those two targets:

```shell
ts-patch patch typescript tsc
ts-patch unpatch typescript tsc
```

`tsserver.js` and `tsserverlibrary.js` are not persistent patch targets on TypeScript 6. Use the live routes above so
transformers still receive the correct library identity for those service entries.

# Configuration

**tsconfig.json**: Add transformers to `compilerOptions` in `plugins` array.

**Examples**
```jsonc
{
    "compilerOptions": {
        "plugins": [
            // Source Transformers
            { "transform": "transformer-module" },
            { "transform": "transformer2", "extraOption": 123 },
            { "transform": "trans-with-mapping", "resolvePathAliases": true },
            { "transform": "esm-transformer.mjs" },

            // Program Transformer
            { "transform": "transformer-module5", "transformProgram": true }
        ]
    }
}
```

## Plugin Options

| Option             | Type    | Description                                                                                                   |
|--------------------|---------|:--------------------------------------------------------------------------------------------------------------|
| **transform**      | string  | Module name or path to transformer _(*.ts or *.js)_                                                           |
| after              | boolean | Apply transformer after stock TS transformers                                                                 |
| afterDeclarations  | boolean | Apply transformer to declaration (*.d.ts) files                                                               |
| transformProgram   | boolean | Transform `Program` during `ts.createProgram()` _(see: [Program Transformers](#program-transformers))_        |
| resolvePathAliases | boolean | Resolve path aliases in transformer (requires [tsconfig-paths](https://www.npmjs.com/package/tsconfig-paths)) |
| type               | string  | See: [Source Transformer Entry Point](#source-transformer-entry-point) (default: 'program')                   |
| import             | string  | Name of exported transformer function _(defaults to `default` export)_                                        |
| tsConfig           | string  | tsconfig.json file _for transformer_ (allows specifying compileOptions, path mapping support, etc)            |
| _..._              |         | Provide your own custom options, which will be passed to the transformer                                      |

_Note: Required options are bold_

_As of v4, `isEsm` has been removed. Transformer module format is inferred from the file extension and package metadata._

# Writing Transformers

For an overview of the typescript compiler (such as what a `SourceFile` and `Program` is) see: [Typescript Compiler Notes](https://github.com/microsoft/TypeScript-Compiler-Notes).

## Source Transformers

Source Transformers will transform the AST of SourceFiles during compilation, allowing you to alter the output of the JS or declarations files.

### Source Transformer Entry Point

```ts
(program: ts.Program, config: PluginConfig, extras: TransformerExtras) => ts.TransformerFactory
```

**PluginConfig**: [Type Declaration](https://github.com/nonara/ts-patch/blob/master/projects/core/shared/plugin-types.ts)  
**TransformerExtras**: [Type Declaration](https://github.com/nonara/ts-patch/blob/master/projects/core/shared/plugin-types.ts)  
**ts.TransformerFactory**: `(context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile`

_Note: Additional [legacy signatures](https://github.com/cevek/ttypescript#pluginconfigtype) are supported, but it is not recommended to develop a new transformer using them._

### Source Transformer Example

Transformers can be written in JS or TS.

```ts
import type * as ts from 'typescript';
import type { TransformerExtras, PluginConfig } from 'ts-patch';

/** Changes string literal 'before' to 'after' */
export default function (program: ts.Program, pluginConfig: PluginConfig, { ts: tsInstance }: TransformerExtras) {
  return (ctx: ts.TransformationContext) => {
    const { factory } = ctx;
    
    return (sourceFile: ts.SourceFile) => {
      function visit(node: ts.Node): ts.Node {
        if (tsInstance.isStringLiteral(node) && node.text === 'before') {
          return factory.createStringLiteral('after');
        }
        return tsInstance.visitEachChild(node, visit, ctx);
      }
      return tsInstance.visitNode(sourceFile, visit);
    };
  };
}

```

**Live Examples**:

[`{ transform: "typescript-transform-paths" }`](https://github.com/LeDDGroup/typescript-transform-paths)

[`{ transform: "typescript-is/lib/transform-inline/transformer" }`](https://github.com/woutervh-/typescript-is)

[`{ transform: "typia/lib/transform" }`](https://github.com/samchon/typia) ([💻playground](https://typia.io/playground))

[`{ transform: "@nestia/core/lib/transform" }`](https://github.com/samchon/nestia)

### Altering Diagnostics

Diagnostics can be altered in a Source Transformer.

To alter diagnostics you can use the following, provided from the `TransformerExtras` parameter:

| property           | description                                         |
|--------------------|-----------------------------------------------------|
| diagnostics        | Reference to `Diagnostic` array                     |   
| addDiagnostic()    | Safely add `Diagnostic` to `diagnostics` array      |
| removeDiagnostic() | Safely remove `Diagnostic` from `diagnostics` array | 

### Note

_This alters diagnostics during _emit only_. If you want to alter diagnostics in your IDE as well, you'll need to create a LanguageService plugin to accompany your source transformer_

## Program Transformers

Sometimes you want to do more than just transform source code. For example you may want to:

- TypeCheck code after it's been transformed
- Generate code and add it to the program
- Add or remove emit files during transformation

For this, we've introduced what we call a Program Transformer. The transform action takes place during `ts.createProgram`, and allows
re-creating the `Program` instance that typescript uses.

### Program Transformer Entry Point

```ts
(program: ts.Program, host: ts.CompilerHost | undefined, options: PluginConfig, extras: ProgramTransformerExtras) => ts.Program
```

**ProgramTransformerExtras** >>> [Type Declaration](https://github.com/nonara/ts-patch/blob/master/projects/core/shared/plugin-types.ts)

### Configuring Program Transformers

To configure a Program Transformer, supply `"transformProgram": true` in the config transformer entry.

_Note: The `before`, `after`, and `afterDeclarations` options do not apply to a Program Transformer and will be ignored_

[See Config Example](#configuration)

### Program Transformer Example
```TypeScript
/** 
 * Add a file to Program
 */
import * as path from 'path';
import type * as ts from 'typescript';
import type { ProgramTransformerExtras, PluginConfig } from 'ts-patch';

export const newFile = path.resolve(__dirname, 'added-file.ts');

export default function (
  program: ts.Program, 
  host: ts.CompilerHost | undefined, 
  options: PluginConfig, 
  { ts: tsInstance }: ProgramTransformerExtras
) {
  return tsInstance.createProgram(
    /* rootNames */ program.getRootFileNames().concat([ newFile ]),
    program.getCompilerOptions(),
    host,
    /* oldProgram */ program
  );
}
```

**Note:** For a more complete example, see [Transforming Program with additional AST transformations](https://github.com/nonara/ts-patch/discussions/29#discussioncomment-325979)

**Live Examples**:

[`{ transform: "@typescript-virtual-barrel/compiler-plugin", transformProgram: true }`](https://github.com/zaguiini/typescript-virtual-barrel)

[`{ transform: "ts-overrides-plugin", transformProgram: true }`](https://github.com/DiFuks/ts-overrides-plugin)

## Plugin Package Configuration

The plugin package configuration allows you to specify custom options for your TypeScript plugin. 
This configuration is defined in the `package.json` of your plugin under the `tsp` property.

An example use case is enabling `parseAllJsDoc` if you require full JSDoc parsing in tsc for your transformer in TS v5.3+. (see: [5.3 JSDoc parsing changes](https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/#optimizations-by-skipping-jsdoc-parsing))

For all available options, see the `PluginPackageConfig` type in [plugin-types.ts](https://github.com/nonara/ts-patch/blob/master/projects/core/shared/plugin-types.ts)

### Example

```jsonc
{
  "name": "your-plugin-name",
  "version": "1.0.0",
  "tsp": {
    "tscOptions": {
      "parseAllJsDoc": true
    }
  }
}
```

## Resources

### Recommended Reading

- How-To: [Advice for working with the TS Compiler API](https://github.com/nonara/ts-patch/discussions/31)
- How-To: [TypeScript Transformer Handbook](https://github.com/madou/typescript-transformer-handbook)
- Article: [How to Write a TypeScript Transform (Plugin)](https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943)
- Article: [Creating a TypeScript Transformer](https://43081j.com/2018/08/creating-a-typescript-transform?source=post_page-----731e2b0b66e6----------------------)

### Recommended Tools

| Tool                                                                 | Type        | Description                                                                                 |
|----------------------------------------------------------------------|-------------|---------------------------------------------------------------------------------------------|
| [TS AST Viewer](https://ts-ast-viewer.com/)                          | Web App     | Allows you to see the `Node` structure and other TS properties of your source code.         |
| [ts-expose-internals](https://github.com/nonara/ts-expose-internals) | NPM Package | Exposes internal types and methods of the TS compiler API                                   |

### Discussion

- `#compiler-internals-and-api` on [TypeScript Discord Server](https://discord.com/invite/typescript)
- TSP [Discussions](https://github.com/nonara/ts-patch/discussions) Board

# Advanced Options

**(env) `TSP_SKIP_CACHE`**

Skips patch cache when patching via cli or live compiler.

**(env) `TSP_COMPILER_TS_PATH`**

Specify typescript library path to use for `ts-patch/compiler` (defaults to `require.resolve('typescript')`)

**(env) `TSP_CACHE_DIR`**

Override patch cache directory

**(cli) `ts-patch clear-cache`**

Cleans patch cache & lockfiles

# Compatibility Direction

TypeScript 6 changed the compiler distribution shape by introducing thin CommonJS shims around several implementation
files. ts-patch uses live routing where entry identity matters because a live route can preserve whether a tool requested
`typescript`, `tsc`, `tsserver`, or `tsserverlibrary` without permanently rewriting service-entry shims.

For TypeScript 6, persistent patching is intentionally narrow: only `typescript.js` and `tsc.js` are patchable targets.
Service entries should be loaded through `ts-patch/compiler/tsserver` or `ts-patch/compiler/tsserverlibrary`.

The TypeScript team has also announced a future Go-based compiler line. ts-patch will evaluate that implementation as a
separate compatibility track, including whether the JavaScript compiler API and transformer hooks remain available or
whether a redesigned integration is required.

# Maintainers

<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tr>
    <td align="center"><a href="https://github.com/nonara"><img src="https://avatars0.githubusercontent.com/u/1427565?v=4" width="100px;" alt=""/><br /><sub><b>Ron S.</b></sub></a></td>
  </tr>
</table>

## Help Wanted

If you're interested in helping and are knowledgeable with the TS compiler codebase, feel free to reach out!

# License

This project is licensed under the MIT License, as described in `LICENSE.md`
