Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@ jobs:

strategy:
matrix:
# NOTE - 21+ is failing due to odd bug - maybe bug in node - see https://github.com/nonara/ts-patch/issues/153
node-version: [ 18.x, 20.x ]
node-version: [ 22.x, 24.x ]

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}

- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v5
with:
path: |
~/.cache/yarn
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6

- name: Setup Node.js to publish to npmjs.org
uses: actions/setup-node@v2
uses: actions/setup-node@v6
with:
node-version: '20.x'
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'

- name: Install Packages
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ generated/
.env
.vscode
.idea/jsLibraryMappings.xml
.idea/copilot.*.xml
todo/
TODO.ts
.agents/
.claude/
/.codex

# Junk
temp/
Expand Down
67 changes: 49 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ 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 installation via on-the-fly, in-memory patching _or_ as a persistent patch
* Can patch individual libraries (see `ts-patch /?`)
* 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
Expand Down Expand Up @@ -48,6 +51,7 @@ _Migrating from ttypescript is easy! See: [Method 1: Live Compiler](#method-1-li
* [Recommended Tools](#recommended-tools)
* [Discussion](#discussion)
* [Advanced Options](#advanced-options)
* [Compatibility Direction](#compatibility-direction)
* [Maintainers](#maintainers)
* [Help Wanted](#help-wanted)
* [License](#license)
Expand All @@ -70,29 +74,42 @@ The live compiler patches on-the-fly, each time it is run.

**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 patch modifies the typescript installation within the node_modules path. It requires additional configuration
to remain persisted, but it carries less load time and complexity compared to the live compiler.
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.

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

```shell
# For advanced options, see: ts-patch /?
ts-patch install
ts-patch uninstall
```

2. Add `prepare` script (keeps patch persisted after npm install)
The lower-level `patch` and `unpatch` commands are also limited to those two targets:

`package.json`
```jsonc
{
/* ... */
"scripts": {
"prepare": "ts-patch install -s"
}
}
```
```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

Expand All @@ -107,7 +124,7 @@ ts-patch install
{ "transform": "transformer-module" },
{ "transform": "transformer2", "extraOption": 123 },
{ "transform": "trans-with-mapping", "resolvePathAliases": true },
{ "transform": "esm-transformer", "isEsm": true },
{ "transform": "esm-transformer.mjs" },

// Program Transformer
{ "transform": "transformer-module5", "transformProgram": true }
Expand All @@ -124,7 +141,6 @@ ts-patch install
| 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))_ |
| isEsm | boolean | Transformer is ES Module (_note: experimental_ — requires [esm](https://www.npmjs.com/package/esm)) |
| 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)_ |
Expand All @@ -133,6 +149,8 @@ ts-patch install

_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).
Expand Down Expand Up @@ -329,6 +347,19 @@ Override patch cache directory

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 -->
Expand Down
16 changes: 8 additions & 8 deletions jest.config.ts → jest.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { Config } from '@jest/types';
import * as os from 'os';
const os = require('os');

const config: Config.InitialOptions = {
testEnvironment: "node",
/** @type {import('@jest/types').Config.InitialOptions} */
const config = {
testEnvironment: 'node',
preset: 'ts-jest',
roots: [ '<rootDir>/test/tests' ],
testRegex: '.*(test|spec)\\.tsx?$',
moduleFileExtensions: [ 'ts', 'tsx', 'js', 'jsx', 'json', 'node' ],
moduleFileExtensions: [ 'ts', 'tsx', 'mts', 'cts', 'js', 'jsx', 'mjs', 'cjs', 'json', 'node' ],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: './test/tsconfig.json' }],
},
modulePaths: [ "<rootDir>/node_modules" ],
modulePaths: [ '<rootDir>/node_modules' ],
// coveragePathIgnorePatterns: [
// 'src/installer/lib/system/errors.ts$'
// ],
Expand All @@ -21,6 +21,6 @@ const config: Config.InitialOptions = {
'/node_modules/(?!(ts-transformer-keys|ts-transformer-enumerate|ts-nameof)/)'
],
maxConcurrency: os.cpus().length
}
};

export default config;
module.exports = config;
43 changes: 28 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
"description": "Patch typescript to support custom transformers in tsconfig.json",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./compiler": "./dist/compiler/typescript.js",
"./compiler/typescript": "./dist/compiler/typescript.js",
"./compiler/tsc": "./dist/compiler/tsc.js",
"./compiler/tsserver": "./dist/compiler/tsserver.js",
"./compiler/tsserverlibrary": "./dist/compiler/tsserverlibrary.js",
"./package.json": "./package.json"
},
"scripts": {
"compile": "yarn run compile:core && yarn run compile:patch",
"build": "yarn run clean && yarn run compile:patch && yarn run compile:core",
Expand Down Expand Up @@ -47,35 +59,36 @@
"chalk": "^4.1.2",
"global-prefix": "^4.0.0",
"minimist": "^1.2.8",
"resolve": "^1.22.2",
"semver": "^7.6.3",
"resolve": "^1.22.12",
"semver": "^7.7.4",
"strip-ansi": "^6.0.1"
},
"bin": {
"ts-patch": "./dist/bin/ts-patch.js",
"tspc": "./dist/bin/tspc.js"
},
"engines": {
"node": ">=22.15.0"
},
"devDependencies": {
"@types/esm": "^3.2.2",
"@types/jest": "^29.5.10",
"@types/jest": "^30.0.0",
"@types/minimist": "^1.2.2",
"@types/mock-fs": "^4.13.1",
"@types/node": "^16.11.5",
"@types/node": "^25.6.0",
"@types/resolve": "^1.20.1",
"@types/semver": "^7.3.13",
"@types/shelljs": "^0.8.9",
"esm": "^3.2.25",
"jest": "^29.7.0",
"rimraf": "^5.0.7",
"shelljs": "^0.8.5",
"@types/semver": "^7.7.1",
"@types/shelljs": "^0.10.0",
"jest": "^30.3.0",
"rimraf": "^6.1.3",
"shelljs": "^0.10.0",
"standard-version": "^9.5.0",
"ts-jest": "^29.1.1",
"ts-expose-internals": "npm:[email protected]",
"ts-jest": "^29.4.9",
"ts-next": "npm:[email protected]",
"ts-node": "^10.9.1",
"ts-patch": "^3.3.0",
"tsconfig-paths": "^4.2.0",
"typescript": "5.7.2",
"ts-next": "npm:typescript@beta",
"ts-expose-internals": "npm:[email protected]"
"typescript": "6.0.3"
},
"workspaces": {
"packages": [
Expand Down
55 changes: 0 additions & 55 deletions projects/core/resolver-hook.js

This file was deleted.

5 changes: 0 additions & 5 deletions projects/core/shared/plugin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ export interface PluginConfig {
*/
import?: string;

/**
* Is the transformer an ES Module
*/
isEsm?: boolean

/**
* Plugin entry point format type, default is program
*/
Expand Down
12 changes: 7 additions & 5 deletions projects/core/src/actions/check.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { LogLevel, PatchError } from '../system';
import { assertSupportedTypeScript, LogLevel, PatchError } from '../system';
import chalk from 'chalk';
import { getTsPackage } from '../ts-package';
import { PatchDetail } from "../patch/patch-detail";
import { getTsModule } from "../module";
import { getTsModule, TsModule } from "../module";
import { getInstallerOptions, InstallerOptions } from "../options";


Expand Down Expand Up @@ -33,10 +33,12 @@ export function check(moduleNameOrNames?: string | string[], opts?: Partial<Inst

/* Load Package */
const tsPackage = getTsPackage(dir);
assertSupportedTypeScript(tsPackage);
const { packageDir, version } = tsPackage;


targetModuleNames ??= tsPackage.moduleNames;
targetModuleNames = targetModuleNames
? targetModuleNames.map(name => TsModule.normalizePatchableName(name))
: tsPackage.moduleNames.filter(name => (TsModule.patchableNames as readonly string[]).includes(name));

/* Check Modules */
log(`Checking TypeScript ${chalk.blueBright(`v${version}`)} installation in ${chalk.blueBright(packageDir)}\r\n`);
Expand All @@ -45,7 +47,7 @@ export function check(moduleNameOrNames?: string | string[], opts?: Partial<Inst
for (const moduleName of targetModuleNames) {
/* Validate */
if (!tsPackage.moduleNames.includes(moduleName))
throw new PatchError(`${moduleName} is not a valid TypeScript module in ${packageDir}`);
throw new PatchError(`${moduleName} is not present in ${packageDir}`);

/* Report */
const tsModule = getTsModule(tsPackage, moduleName, { skipCache: options.skipCache });
Expand Down
Loading