TL;DR
ts-blank-space (by Bloomberg) replaces TypeScript type annotations with whitespace — preserving exact source positions for perfect source maps, zero AST transformation. Node.js --experimental-strip-types (Node 22.6+) strips types natively at runtime — no build step, no dependencies, just node file.ts. @swc/core is the Rust-based TypeScript transpiler — strips types AND downlevels syntax, 20x faster than tsc. In 2026: Node.js --strip-types for scripts and development, swc for production builds, ts-blank-space for source-map-perfect debugging.
Key Takeaways
- ts-blank-space: ~50K weekly downloads — replaces types with spaces (identical byte positions), Bloomberg project
- Node.js --strip-types: built-in (Node 22.6+) — zero config, just run
node --strip-types file.ts - @swc/core: ~30M weekly downloads — full transpiler, type stripping + syntax downlevel + minification
- Type stripping = remove types, keep everything else (no enum transform, no decorator emit)
- ts-blank-space preserves exact byte offsets — debugger breakpoints match source exactly
- Node.js strip-types uses swc under the hood but only strips types (no transforms)
What Is Type Stripping?
// Source TypeScript:
function getScore(name: string, threshold: number = 80): boolean {
const score: number = calculateHealthScore(name)
return score >= threshold
}
// After type stripping (replace types with whitespace):
function getScore(name , threshold = 80) {
const score = calculateHealthScore(name)
return score >= threshold
}
// After traditional transpilation (rewrite AST):
function getScore(name, threshold = 80) {
const score = calculateHealthScore(name);
return score >= threshold;
}
// Type stripping is SIMPLER:
// - No AST transformation
// - Types become whitespace (same byte positions)
// - Source maps are trivial (or unnecessary)
// - Much faster than full transpilation
What Type Stripping CANNOT Handle
// These TypeScript features require CODE TRANSFORMATION, not just erasure:
// ❌ Enums (generate runtime code):
enum Status { Active, Inactive }
// Must become: var Status; (function(Status) { ... })(Status)
// ❌ Namespaces with values:
namespace Utils { export function parse() {} }
// Must become: var Utils; (function(Utils) { ... })(Utils)
// ❌ Parameter properties:
class Foo { constructor(public name: string) {} }
// Must become: class Foo { constructor(name) { this.name = name; } }
// ❌ Legacy decorators (experimentalDecorators):
@Injectable()
class Service {}
// ✅ These are fine (just erase):
// Type annotations, interfaces, type aliases, generics, as casts,
// satisfies, declare, abstract, readonly, override
ts-blank-space
ts-blank-space — Bloomberg's type eraser:
How it works
// Input (byte positions matter):
const x: number = 42
// ^^^^^^^^^ these bytes become spaces
// Output:
const x = 42
// ^^^^^^^^^ exact same positions — debugger breakpoints match!
// Compare to swc/esbuild:
const x = 42
// ← positions shifted — source map needed for debugging
Usage as a library
import tsBlankSpace from "ts-blank-space"
const tsCode = `
function greet(name: string): void {
console.log(\`Hello, \${name}!\`)
}
`
const jsCode = tsBlankSpace(tsCode)
// Output:
// function greet(name ) {
// console.log(`Hello, ${name}!`)
// }
// Source positions are IDENTICAL — no source map needed
Node.js loader
# Register as Node.js loader:
node --import ts-blank-space/register ./src/index.ts
# Or in package.json:
{
"scripts": {
"dev": "node --import ts-blank-space/register src/index.ts"
}
}
With Vitest
// vitest.config.ts
import { defineConfig } from "vitest/config"
export default defineConfig({
esbuild: false,
plugins: [
{
name: "ts-blank-space",
transform(code, id) {
if (!id.endsWith(".ts") && !id.endsWith(".tsx")) return
const { default: transform } = await import("ts-blank-space")
return { code: transform(code), map: null } // No map needed!
},
},
],
})
When it shines: debugging
Traditional transpiler (swc, esbuild):
Source: line 42, column 15
Output: line 38, column 10 ← shifted
Need: source map to map back
ts-blank-space:
Source: line 42, column 15
Output: line 42, column 15 ← exact same!
Need: nothing — breakpoints work perfectly
Node.js --experimental-strip-types
Node.js type stripping — built-in since Node 22.6:
Usage
# Node.js 22.6+ (experimental):
node --experimental-strip-types src/index.ts
# Node.js 23.6+ (stable, no flag needed):
node src/index.ts
# With tsconfig paths (Node 23+):
node --experimental-transform-types src/index.ts
What it does
node src/index.ts:
1. Detects .ts extension
2. Runs type stripping (via amaro/swc internally)
3. Executes the resulting JavaScript
4. NO build step, NO node_modules transpiler, NO config
Limitations:
- Only strips types (no enum, namespace, or decorator transform)
- No JSX support (.tsx)
- No tsconfig paths resolution (use --experimental-transform-types)
- Cannot import .ts from .js files without flag
package.json setup
{
"type": "module",
"scripts": {
"start": "node src/index.ts",
"dev": "node --watch src/index.ts"
},
"engines": {
"node": ">=23.6.0"
}
}
With --experimental-transform-types
# Enables enum and namespace transformation (not just stripping):
node --experimental-transform-types src/index.ts
# Now enums work:
enum Status { Active = "active", Inactive = "inactive" }
// Transformed to runtime code automatically
Comparison with tsx
# tsx (via esbuild — full transpilation):
npx tsx src/index.ts
# ✅ Handles: enums, decorators, JSX, tsconfig paths
# ✅ Works on all Node.js versions
# ❌ External dependency
# node --strip-types (built-in):
node src/index.ts
# ✅ No dependencies
# ✅ Perfect for simple TypeScript
# ❌ No enums (without --transform-types)
# ❌ No JSX
# ❌ Requires Node 23.6+
@swc/core
@swc/core — Rust-based TypeScript compiler:
Type stripping mode
import { transform } from "@swc/core"
const result = await transform(tsCode, {
jsc: {
parser: { syntax: "typescript", tsx: false },
target: "es2022",
transform: null, // No transformations — just strip types
},
sourceMaps: true,
})
console.log(result.code)
console.log(result.map)
Full transpilation (production builds)
import { transform } from "@swc/core"
const result = await transform(tsCode, {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
decorators: true,
},
target: "es2022",
transform: {
decoratorVersion: "2022-03",
react: { runtime: "automatic" },
},
},
module: { type: "es6" },
sourceMaps: true,
minify: true,
})
Performance comparison
Transpiling 1000 TypeScript files:
tsc (TypeScript compiler): ~12,000 ms
esbuild (Go): ~200 ms
@swc/core (Rust): ~180 ms
ts-blank-space (JS): ~50 ms (type strip only)
Node.js --strip-types: ~0 ms (on-demand per file)
For type stripping only:
ts-blank-space is fastest (no AST parse for simple cases)
Node.js built-in is zero-overhead (amortized per file)
For full transpilation:
swc and esbuild are comparable (~20x faster than tsc)
Used by bundlers
swc powers:
- Next.js (default compiler since v12)
- Vite (via @vitejs/plugin-react-swc)
- Parcel 2
- rspack / Rsbuild
You might already be using swc without knowing it
Feature Comparison
| Feature | ts-blank-space | Node.js --strip-types | @swc/core |
|---|---|---|---|
| Type stripping | ✅ | ✅ | ✅ |
| Enum transform | ❌ | ⚠️ (--transform-types) | ✅ |
| Decorator transform | ❌ | ❌ | ✅ |
| JSX / TSX | ❌ | ❌ | ✅ |
| Source maps needed | ❌ (same positions) | ❌ | ✅ |
| Minification | ❌ | ❌ | ✅ |
| Dependencies | 0 | 0 (built-in) | Native binary |
| Node.js version | Any | 23.6+ | Any |
| Speed (type strip) | ~50ms/1000 files | On-demand | ~180ms/1000 files |
| Weekly downloads | ~50K | built-in | ~30M |
When to Use Each
Use ts-blank-space if:
- Need perfect source-map-free debugging (breakpoints match exactly)
- Building tools that need position-preserving transforms
- Want zero source map overhead in development
Use Node.js --strip-types if:
- Running scripts, CLI tools, or small servers — zero config, zero deps
- Node.js 23.6+ is available
- Don't use enums, decorators, or JSX
- Simplest possible TypeScript execution
Use @swc/core if:
- Production builds that need minification and bundling
- Using enums, decorators, JSX, or legacy syntax
- Need source maps for production error tracking
- Already using Next.js, Vite with SWC, or rspack (you're already using it)
Also consider:
- tsx — wraps esbuild for running TypeScript, handles everything including paths
- esbuild — similar to swc, excellent for bundling
Migration Guide
From tsc to Node.js --strip-types (scripts and tools)
For scripts, CLI tools, and simple servers that don't use enums or decorators, replacing a tsc compile step with Node.js built-in type stripping eliminates the build step entirely:
# Before: TypeScript compilation + run
npx tsc && node dist/index.js
# After: direct execution (Node.js 23.6+)
node src/index.ts
# For watch mode during development:
node --watch src/index.ts
Update package.json scripts:
{
"scripts": {
"start": "node src/index.ts",
"dev": "node --watch src/index.ts",
"build": "tsc"
},
"engines": { "node": ">=23.6.0" }
}
The build script can remain for generating .d.ts type declarations if you publish a library. For applications, the build step may be entirely optional.
From esbuild/tsx to ts-blank-space (debugger-first workflows)
If your development workflow involves frequent debugger sessions and source map resolution is causing breakpoint drift:
# Before: tsx (esbuild-based, positions shift)
npx tsx src/index.ts
# After: ts-blank-space (positions preserved)
node --import ts-blank-space/register src/index.ts
The trade-off: ts-blank-space fails on enums and namespaces. Audit your codebase first:
# Find enum usage that would break ts-blank-space:
grep -r "^enum " src/
grep -r "^const enum " src/
If you have enums, either convert them to as const objects or keep using tsx/swc.
From ts-node to swc (speed improvement)
# Remove ts-node
npm uninstall ts-node
# Install @swc/core + register
npm install --save-dev @swc/core @swc/register
# Before: ts-node (slow, full type checking)
node -r ts-node/register src/index.ts
# After: swc (fast, type stripping only — no type checking)
node -r @swc/register src/index.ts
Note: swc does not perform type checking. Run tsc --noEmit separately in CI for type safety validation.
Community Adoption in 2026
ts-blank-space reaches approximately 50,000 weekly downloads — a niche but important tool in the JavaScript tooling ecosystem. Bloomberg's engineering team open-sourced it to solve a specific problem that no other tool addressed: position-preserving type erasure for perfect source-map-free debugging. Its adoption is concentrated in developer tooling projects, editor integrations, and debugging infrastructure where exact source positions matter. The TypeScript Language Server team has referenced ts-blank-space's approach when discussing fast iteration mode for the TypeScript compiler itself.
Node.js built-in type stripping (--strip-types / --experimental-strip-types) has no npm download count — it ships with Node.js 22.6+ and was stabilized (flag-free) in Node.js 23.6. Its adoption is measured differently: approximately 60% of Node.js developers use Node 20+ or 22+, and the feature is increasingly the first answer to "how do I run TypeScript without a build step?" in 2026. The Node.js team chose to use amaro (which wraps swc's type stripping) rather than implementing a new parser — making it consistent with swc's behavior while being a zero-install feature. For developers writing Node.js scripts, automation tools, or serverless functions, native type stripping has eliminated the need for tsx or ts-node in simple cases.
@swc/core reaches approximately 30 million weekly downloads, making it one of the most downloaded packages in the JavaScript ecosystem. The vast majority of these downloads are not from developers directly using the swc API — they are transitive dependencies from Next.js (which replaced Babel with swc in v12), Vite's React SWC plugin (@vitejs/plugin-react-swc), rspack/Rsbuild, and other build tools that use swc as their transformation engine. A developer using Next.js 14+ or Vite with the SWC React plugin is already using @swc/core in their project without configuring it. This transitive adoption makes swc the most widely deployed TypeScript transpiler in production environments, ahead of esbuild (which is itself broadly deployed via Vite's bundler layer).
Build Pipeline Integration and CI Workflows
Integrating type stripping into a CI pipeline requires thinking separately about two concerns: fast execution during development and type-correctness verification for production builds. ts-blank-space and Node.js built-in type stripping intentionally skip type checking — they only remove type syntax, leaving the code potentially type-incorrect but runnable. This means your CI pipeline still needs a tsc --noEmit step to verify type correctness even if your execution uses type stripping. The recommended pattern is to run tsc --noEmit once in CI for type-checking (potentially in a dedicated job), while using Node.js --strip-types or ts-blank-space for fast test execution in parallel jobs that do not need the type check gate.
SWC integrates into build pipelines as a drop-in replacement for TypeScript compilation. The @swc/cli package provides a swc command that processes TypeScript files and writes JavaScript output, parallel to what tsc does but without type checking. CI pipelines that previously ran tsc && node dist/index.js can switch to swc src -d dist && node dist/index.js for significantly faster build times. The trade-off is that the type check must be a separate step, but since type checking and code generation are decoupled, they can run in parallel in CI — the type check job and the build-and-test job start simultaneously, and the pipeline succeeds only if both pass.
Edge Cases and Compatibility Considerations
Each type-stripping approach has specific code patterns that can cause unexpected failures. ts-blank-space fails silently or incorrectly on TypeScript features that require code generation: const enums are replaced with whitespace, leaving the runtime referencing an undefined variable; namespaces with value exports produce runtime ReferenceErrors; and class parameter properties (public/private/protected/readonly in constructor parameters) strip the modifiers but leave the assignment missing from the constructor body. Before adopting ts-blank-space, a codebase audit for these patterns is essential. A quick grep for const enum, namespace, and constructor(public catches the most common incompatibilities.
Node.js built-in type stripping has its own compatibility boundary around import paths. TypeScript files that use .ts extensions in imports (import { helper } from './utils.ts') work correctly with Node's strip-types because Node.js ESM can now resolve .ts extensions when type stripping is enabled. However, files that use the .js extension in imports (the TypeScript convention for ESM compatibility with "moduleResolution": "nodenext") continue to require the extension mapping that the TypeScript compiler handles — Node's strip-types does not perform extension remapping, so import { helper } from './utils.js' only works if utils.js actually exists, not if only utils.ts exists. This is a subtle breaking change when migrating from tsx (which handles the remapping) to Node's built-in stripping.
SWC's compatibility surface is the broadest and also carries its own edge cases. SWC's implementation of TypeScript's decorator spec differs slightly from the TypeScript compiler's in complex scenarios involving multiple decorators and inheritance. Teams using NestJS (which heavily uses decorators) or TypeORM (which uses decorators for entity definitions) should run their test suite after switching to SWC to verify decorator behavior. The @swc/jest transformer includes compatibility fixes for the most common NestJS patterns, but customized decorator usage may require verifying against the reference TypeScript compiler output. SWC's treatment of ambient declarations and triple-slash references also differs from the TypeScript compiler in edge cases that primarily affect library authors.
Methodology
Download data from npm registry (weekly average, February 2026). Performance benchmarks on Node.js 22 with 1000 TypeScript files. Feature comparison based on ts-blank-space v0.x, Node.js 23.6, and @swc/core v1.x.
Compare TypeScript tooling and build packages on PkgPulse →
See also: esbuild vs SWC and dts-bundle-generator vs rollup-plugin-dts vs tsup dts, archiver vs adm-zip vs JSZip (2026).