TypeScript famously "becomes JavaScript via the delete key", and the process of deleting TypeScript syntax to transform a file into pure JavaScript is called "type stripping".

Browsers cannot run TypeScript directly, so usually developers will use a bundler to perform type stripping. Even serverside JavaScript runtimes like Node.js required TypeScript transformation before running projects, until recently. Fortunately, we now have access to native type stripping directly inside recent versions of Node.js (see type stripping in the Node.js docs). Having typ…
TypeScript famously "becomes JavaScript via the delete key", and the process of deleting TypeScript syntax to transform a file into pure JavaScript is called "type stripping".

Browsers cannot run TypeScript directly, so usually developers will use a bundler to perform type stripping. Even serverside JavaScript runtimes like Node.js required TypeScript transformation before running projects, until recently. Fortunately, we now have access to native type stripping directly inside recent versions of Node.js (see type stripping in the Node.js docs). Having type stripping directly inside our runtime opens up some interesting possibilities.
Type Stripping without a Bundler
Here’s an interactive component:
=
This component was created with a static site generator called Wunphile, which requires no external dependencies and has no bundler. Here is the component’s source code:
import type { BehaviorModule } from 'wunphile'
export default {
behaviorModuleUrl: import.meta.url,
behavior: (container) => {
const num1In = container.getElementsByClassName('demo-math-num1')[0] as HTMLInputElement
const num2In = container.getElementsByClassName('demo-math-num2')[0] as HTMLInputElement
const opIn = container.getElementsByClassName('demo-math-op')[0] as HTMLSelectElement
const output = container.getElementsByClassName('demo-math-output')[0] as HTMLOutputElement
function solveOrErr(): string {
if (num1In.value === '' || num2In.value === '') {
return ''
}
const num1 = parseInt(num1In.value)
const num2 = parseInt(num2In.value)
if (isNaN(num1) || isNaN(num2)) {
return 'Invalid input'
}
switch (opIn.value) {
case '*':
return String(num1 * num2)
case '/':
if (num2 === 0) {
return 'Cannot divide by 0'
}
return String(num1 / num2)
case '+':
return String(num1 + num2)
case '-':
return String(num1 - num2)
default:
return 'Unsupported operation'
}
}
function onChange() {
output.value = solveOrErr()
}
num1In.addEventListener('input', onChange)
num2In.addEventListener('input', onChange)
opIn.addEventListener('input', onChange)
onChange()
},
} satisfies BehaviorModule
But wait... the code is in TypeScript... how does that work if there is no bundler and no external dependencies? Wouldn’t you need a library to transform it and make it compatible with browsers?
Well, not with Node.js and a clever trick!
Despite the fact that Node.js now supports TypeScript syntax out of the box, the underlying JavaScript engine, V8, does not support TypeScript syntax. The way Node.js handles this is by performing type stripping on the source before passing it to V8. Its type stripping works similarly to ts-blank-space, where TypeScript syntax is replaced with blank space. This process is very fast, and makes things like source maps unnecessary.
Since we know that plain JavaScript is being passed to V8 without any TypeScript syntax, is there a way we can access that? As it turns out, there is.
In JavaScript, calling toString on a function returns its source code. We can use this to get the stripped source of a function.
To illustrate this, we can create a script called index.ts:
type Profile = {
name: string
age: number
description: string
}
function createProfile(name: string, age: number): Profile {
const description: string = `${name} is someone who is ${age} years old.`
return {
name,
age,
description,
}
}
console.log(createProfile.toString())
Now, let’s run node index.ts and see the code we get out:
function createProfile(name , age ) {
const description = `${name} is someone who is ${age} years old.`
return {
name,
age,
description,
}
}
The program prints the stripped source of createProfile that V8 sees. This technique is what enables Wunphile component behaviors to be written in TypeScript without introducing any external dependencies.
Caveats
This technique only works for code that was read from a TypeScript file directly by Node.js; calling new Function or eval with TypeScript code will result in a syntax error. You can still dynamically import TypeScript files and get sources for their functions, but you cannot strip any arbitrary string containing TypeScript code. If you need to strip strings, using ts-blank-space is a good idea.