ts-patch
Patch typescript to allow custom transformers (plugins) during build.
Plugins are specified in tsconfig.json
, or provided programmatically in CompilerOptions
.
Migrating from ttypescript is easy! See: 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 /?
) - Hook build process by transforming the
Program
(see: Transforming Program) - Add, remove, or modify diagnostics (see: Altering Diagnostics)
- Fully compatible with legacy ttypescript projects
- (new) Experimental support for ES Module based transformers
Table of Contents
- ts-patch
- Table of Contents
- Installation
- Usage
- Configuration
- Writing Transformers
- Advanced Options
- Maintainers
- License
Installation
- Install package
<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
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.
- Install the patch
# For advanced options, see: ts-patch /?
ts-patch install
- Add
prepare
script (keeps patch persisted after npm install)
package.json
{
/* ... */
"scripts": {
"prepare": "ts-patch install -s"
}
}
Configuration
tsconfig.json: Add transformers to compilerOptions
in plugins
array.
Examples
{
"compilerOptions": {
"plugins": [
// Source Transformers
{ "transform": "transformer-module" },
{ "transform": "transformer2", "extraOption": 123 },
{ "transform": "trans-with-mapping", "resolvePathAliases": true },
{ "transform": "esm-transformer, "isEsm": true },
// 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) |
isEsm | boolean | Transformer is ES Module (note: experimental — requires esm) |
resolvePathAliases | boolean | Resolve path aliases in transformer (requires tsconfig-paths) |
type | string | See: 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
Writing Transformers
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
(program: ts.Program, config: PluginConfig, extras: TransformerExtras) => ts.TransformerFactory
PluginConfig: Type Declaration
TransformerExtras: Type Declaration
ts.TransformerFactory: (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile
Note: Additional legacy signatures 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.
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" }
{ transform: "typescript-is/lib/transform-inline/transformer" }
{ transform: "typia/lib/transform" }
(
{ transform: "@nestia/core/lib/transform" }
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
(program: ts.Program, host: ts.CompilerHost | undefined, options: PluginConfig, extras: ProgramTransformerExtras) => ts.Program
ProgramTransformerExtras >>> Type Declaration
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
Program Transformer Example
/**
* 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
Live Examples:
{ transform: "@typescript-virtual-barrel/compiler-plugin", transformProgram: true }
Resources
Recommended Reading
- How-To: Advice for working with the TS Compiler API
- How-To: TypeScript Transformer Handbook
- Article: How to Write a TypeScript Transform (Plugin)
- Article: Creating a TypeScript Transformer
Recommended Tools
Tool | Type | Description |
---|---|---|
TS AST Viewer | Web App | Allows you to see the Node structure and other TS properties of your source code. |
ts-expose-internals | NPM Package | Exposes internal types and methods of the TS compiler API |
Discussion
#compiler-internals-and-api
on TypeScript Discord Server- TSP 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
Maintainers
Ron S. |
Help Wanted
If you're interested in helping and have a high level of skill with the TS compiler API, please reach out!
License
This project is licensed under the MIT License, as described in LICENSE.md