Concepts
The shared vocabulary you need before authoring. Two pages follow this one: Plugin protocol (the wire contract) and AST & Checker (the Go-side surface). This page is the first read.
What is a ttsc plugin?
A ttsc plugin is one npm package with two halves:
- A JS descriptor in
node_modulesthat tellsttscwhat the plugin does β its name, stage, and the Go source directory. - A Go sidecar that holds the actual transform or check logic.
ttscbuilds it on the consumer machine the first time it is used, then caches the binary.
my-ttsc-plugin/ installed in consumer's node_modules
βββ plugin.cjs βββ JS descriptor: factory or object
βββ package.json βββ carries name, ttsc.plugin marker, files[]
βββ plugin/ βββ Go sidecar source (built by ttsc)
βββ go.mod
βββ main.go βββ implements check/transform/build/fix/format
ttsc launcher (Node)
β
β spawns
βΌ
built Go binary (cached)
β
β stdout: JSON ({ diagnostics, typescript })
β stderr: human messages
βΌ
diagnostics or rewritten emitOne npm package, one contract, one config (tsconfig.jsonβs compilerOptions.plugins).
Stages and host-driven subcommands
ttsc knows two stages. Set one on the descriptor with stage: "transform" or stage: "check". The host invokes a fixed set of subcommands per stage:
| Stage | Subcommands the host spawns | What the plugin does |
|---|---|---|
"transform" | build, transform | mutates TypeScript AST before emit |
"check" | check, fix (opt-in), format (opt-in) | reports diagnostics; optionally applies edits in-place |
Non-zero exit means failure; the host prints stderr verbatim and aborts. version is a smoke verb the host never invokes. For exit-code rules, the verbatim-passthrough rule for transform, and what is fictional in older docs (e.g. <plugin>: fix not supported), see Plugin protocol.
Auto-discovery vs explicit plugin entries
A consumer can enable a plugin two ways:
-
Explicit (recommended for fixtures and tests): list the plugin in
compilerOptions.plugins[]. Wins over auto-discovery for the same package.// tsconfig.json { "compilerOptions": { "plugins": [{ "transform": "my-ttsc-plugin" }] } } -
Auto-discovery (
ttsc.pluginpackage field):ttscreadspackage.json#ttsc.pluginonly from packages listed directly in the nearest consumerpackage.jsonat or above the selected project.// your plugin's package.json { "name": "my-ttsc-plugin", "ttsc": { "plugin": { "transform": "my-ttsc-plugin" } } }
The name field is load-bearing. Auto-discovery matches the consumerβs dependencies/devDependencies entry string against your pluginβs package.json name exactly. If the consumer writes "ttsc-plugin-debugger-strip" but your name is "my-plugin", ttsc loads nothing, emits unchanged, and exits 0 β no error, no diagnostic.
Do I need the Checker?
| If your plugin⦠| Then use⦠|
|---|---|
| Only inspects syntax (banner stripping, comment walking, statement filtering, JSX-tag rewriting) | shimparser.ParseSourceFile β no Program, no Checker, no tsconfig |
| Inspects types, resolves symbols, follows imports, or checks signatures (validator generation, schema, type-aware lint) | driver.LoadProgram to get a *Program with a leased Checker |
When in doubt, start with driver.LoadProgram β the cost is one tsconfig parse and one checker-pool lease; the gain is you can introduce types later without rewriting. The implementation lives in AST & Checker.
Glossary
- Descriptor β the JS object (or factory) every plugin package exports. Carries
name,source,stage, optionalcomposes/contributors. Lives innode_modules. - Sidecar β the Go binary
ttscbuilds from the pluginβssourcedirectory. Receives subcommands over CLI; communicates overstdout(JSON fortransform) andstderr(human messages). - Stage β the
stagefield on the descriptor. Either"transform"(mutates AST before emit) or"check"(diagnostics; optionallyfix/format). - Subcommand β the first CLI argument the host passes to the sidecar (
check,transform,build,fix,format). Determined by the stage and the userβsttsccommand. - Shim β the narrow Go API at
github.com/microsoft/typescript-go/shim/...that exposes tsgoβs AST, Checker, parser, scanner, printer, and FS. The plugin boundary into tsgo. - Overlay β the synthesized
go.workworkspacettscwrites into the scratch build directory so the pluginβsgo 1.xmodule seesttscand shim packages locally. - Contributor β a Go-source package statically linked into a host pluginβs binary at build time. Used by
@ttsc/lintto compose third-party rule packages. Contributors ship as packages, not modules β see Plugin protocol β Contributors. - Host β the
ttscJavaScript launcher process that resolves descriptors, builds source plugins, spawns sidecars, and consumes their output. Not the same as the βcompiler hostβ of tsgo. - Cache β the on-disk store of built plugin binaries at the user-level ttsc cache (
~/.cache/ttsc/plugins/<key>/pluginon Linux, platform equivalents elsewhere) or$TTSC_CACHE_DIR/plugins/...when overridden. Cache key folds in ttsc version, tsgo version, platform, plugin source hash, overlay hash, contributor hashes, Go compiler content, and the Go build environment. See Architecture. - Transform path β the pipeline
ttscruns when atransform-stage plugin is active: parse β run plugin AST mutations β run tsgoβs printer β emit JS / d.ts / source maps. - Factory context β the object
ttscpasses to factory-form descriptors:{ binary, cwd, plugin, projectRoot, tsconfig }. The TypeScript surface for advanced descriptors that need to inspect the active host or the consumerβs tsconfig entry. --plugins-jsonβ the JSON arrayttscpasses to every plugin subcommand (per-plugin{name, stage, config}). Wire shape and ordering: Plugin protocol β--plugins-json.
Next
β Plugin protocol β the wire contract in detail.
β Authoring β Getting started β write your first plugin.