Modes #
typestrip has two modes. Pick one with the mode option. Erase mode is the default.
await transform(src); // erase mode
await transform(src, { mode: "downlevel" });
Erase mode ("erase") |
Downlevel mode ("downlevel") |
|
|---|---|---|
Erasable syntax (types, annotations, interface, type, declare, generics, as, satisfies, !, and so on) |
Erased | Erased |
| Non-erasable syntax | Reported | Down-leveled |
| Positions | Every offset preserved | Every line preserved; columns preserved outside converted constructs |
| Engine | build/erase.wasm |
build/downlevel.wasm |
| Oracle | ts-blank-space | ts.transpileModule |
Neither mode type-checks, and neither lowers JavaScript to an older target. An input that TypeScript's parser accepts but its checker rejects (two extends clauses, for example) is stripped as parsed.
Erase mode #
Erase mode erases erasable syntax, the same way ts-blank-space and Node's type stripping do. The output needs no source map.
Position guarantee. The output has the same UTF-16 length as the input, and every kept character is at its original offset. Each erased character becomes a space. Line terminators are kept. A few erased characters become ;, ( or ) where that keeps ASI and arrow-function semantics.
let x: number = 1;
let x = 1;
Non-erasable syntax is reported, not changed. Each of these constructs is an UnsupportedSyntaxError, or an onError call when you pass onError:
enumandconst enum- an instantiated
namespaceormodule - parameter properties (
constructor(private x: number)) import x = require("y")andimport x = A.Bexport =- legacy
<T>xcasts
Downlevel mode #
Downlevel mode erases everything erase mode erases. It also down-levels the non-erasable constructs listed above, matching what ts.transpileModule does at runtime. Every other piece of the file is identical to erase mode's output.
Position guarantee. Every line is preserved. A converted construct is rewritten on its own lines, so columns inside it, and after it on the same line, can shift. Columns everywhere else are preserved. Inserted text never contains a line break.
Output shape #
An enum or instantiated namespace becomes a function called with the container object as this. The source's own braces are the function body.
enum E { A, B = A + 2 }
var E;(function(E) { A=0;var A;this.A=A;this[A]="A"; B = A + 2;var B;this.B=B;typeof B=="string"||(this[B]="B"); }).call(E||(E={}),E);
namespace N { export const a = 1; }
var N;(function(N) { const a = 1;;this.a=a; }).call(N||(N={}),N);
A parameter property becomes a field and an assignment at the top of the constructor body.
class P { constructor(private x: number) {} }
class P {x; constructor( x ) {this.x=x;} }
import x = require("y") becomes const x = require("y"), import x = A.B becomes var x = A.B, and export = x becomes module.exports = x.
What downlevel mode still reports #
- A legacy cast where erasing it would trigger ASI. Downlevel mode erases
<T>xcasts, except when the cast is the operand ofreturn,throw, or a non-starredyield, and a line terminator sits between the cast and its operand. Erasing the cast there would end the statement at the keyword. Downlevel mode reports that one case, the same way erase mode does. - Mixed module styles.
import x = require()andexport =down-level torequireandmodule.exports. A file that also has a value-level ESMimportorexportcan't run under either module system after that. Downlevel mode reports each CommonJS-only construct in such a file and leaves it in place.
Choosing a mode #
Use erase mode when the code already runs under Node's type stripping, or under any tool with the same rules (erasableSyntaxOnly in tsconfig.json). It is smaller, and every position matches the source.
Use downlevel mode when the code uses enums, namespaces, or parameter properties and you can't change it. Line numbers still match the source, so stack traces and DevTools line positions work without a source map.
The two engines are separate binaries. A program that only uses downlevel mode never loads erase.wasm, and the reverse is also true. See size in the README.