Skip to main content
Home

Built and signed on GitHub Actions

Works with
This package works with Node.js, Deno, Bun
This package works with Node.js
This package works with Deno
This package works with Bun
JSR Score100%
Downloads150/wk
Published3 months ago (1.4.1)

A super fast TS type-stripper

Type-Strip

Type-Strip is a super-fast type-stripper: TypeScript code goes in, and JavaScript code with type annotations removed comes out.

It also ensures forward compatibility of your code with the TC39 Type Annotation Proposal.

If you're using modern TypeScript today, then Type-Strip might be the only build step you need.

Features

  • Strips type annotations
  • (Optionally) strips comments
  • (Optionally) rewrites .ts imports to .js
  • (Optionally) remap aliases in import specifiers
  • Fast. See the benchmark
  • Throws when an unsupported syntax is detected.

See the options for more

Installation

Depending on your runtime / package-manager:

deno add jsr:@fcrozatier/type-strip
npx jsr add @fcrozatier/type-strip
pnpm dlx jsr add @fcrozatier/type-strip
yarn dlx jsr add @fcrozatier/type-strip

Usage

Strip a string of code, files etc.

import stripTypes from '@fcrozatier/type-strip';

stripTypes("let num: number = 0;", {/* options */});
// let num = 0;

Example

Input file ./example.ts

import { capitalize } from '$utils/strings.ts';

/**
 * This class implements a Person
 */
class Person {
  // Index signature
  [key: string]: any;

  name: string;
  constructor(name: string) {
    this.name = capitalize(name);
  }
  greet(): string {
    return `Hello, my name is ${this.name}`;
  }
}

Options:

{
  removeComments: true,
  pathRewriting: true,
  remapSpecifiers: {
    filePath: "./example.ts",
    imports: {
      "$utils/": "./lib/utils/",
    },
  },
}

Output:

import { capitalize } from './lib/utils/strings.js';

class Person {
  name;
  constructor(name) {
    this.name = capitalize(name);
  }
  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

Options

Unsupported features

The goal of the TC39 proposal is to add type annotations without modifying the semantics of the language. This means that TypeScript-only features requiring a transpilation step are not supported.

Auto-Accessors in Classes

Auto-Accessors are not yet natively supported in JavaScript and are part of the stage 3 Decorator proposal

class Person {
  accessor name: string;

  constructor(name: string) {
    this.name = name;
  }
}

An alternative is to use explicit getters/setters

class Person {
  #name: string;
  get name() {
    return this.#name;
  }
  set name(value: string) {
    this.#name = value;
  }
  constructor(name: string) {
    this.name = name;
  }
}

Ambient Declarations

Ambient Declarations are still up for discussion in the TC39 type annotation proposal. At the moment the proposal doesn't reserve space for ambient declarations.

declare const foo: Bar

If you only need them for type-checking, the alternative is to expose them in a separate file.

Decorators

Decorators are not yet natively supported in JavaScript, and are part of the stage 3 Decorator proposal

@Logger("debug")
class Custom extends HTMLElement {
  //...
}

If you rely on them you can still have a transpilation step beforehand. Another alternative is to use the mixin pattern

export const Flying = <T extends new (...args: any[]) => any>(
  superclass: T,
) => {
  return class extends superclass {
    constructor(...options: any[]) {
      console.log("initializing...");
      super(...options);
    }
    fly() {
      console.log("flying!");
    }
  };
};

class Dog {}
class FlyingDog extends Flying(Dog) {}
const flyingDog = new FlyingDog(); //-> initializing...
flyingDog.fly(); //-> flying!

Enums

Enums are a TypeScript feature that requires transpilation and has runtime semantics. There is a recent stage 0 proposal to include them in the language.

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

An alternative is to use a const object

const Direction = {
  Up: "UP",
  Down: "DOWN",
  Left: "LEFT",
  Right: "RIGHT",
} as const

type Direction = keyof typeof Direction;

JSX

JSX is not intended to be implemented by browsers, but to be used by preprocessors. It's not part of the TC39 proposal.

Namespaces

Namespaces are a legacy TypeScript-specific construct to provide modularity and encapsulation. They are not supported by the TC39 type annotation proposal. Standard ES modules are the preferred way to address these needs

Overloads

Function overloads are still up for discussion in the TC39 type annotation proposal. At the moment the proposal doesn't reserve space for function overloads.

/**
 * Returns an array of numbers between 0 and `stop` (excluded) in increments of 1
 */
export function range(stop: number): number[];
/**
 * Returns an array of numbers between `start` and `stop` (excluded) in increments of 1
 */
export function range(start: number, stop: number): number[];
/**
 * Returns an array of numbers between `start` and `stop` (excluded) in increments of `step`
 */
export function range(start: number, stop: number, step: number): number[];
export function range(startOrStop: number, stop?: number, step?: number) {
  let start = startOrStop;
  if (stop !== undefined && step !== undefined) {
    return Array.from(
      { length: (stop - start) / step },
      (_, i) => start + i * step,
    );
  } else if (stop !== undefined) {
    return Array.from({ length: stop - start }, (_, i) => start + i);
  } else {
    const stop = startOrStop;
    start = 0;
    return Array.from({ length: stop - start }, (_, i) => start + i);
  }
}

An alternative is to branch depending on the number of arguments or their type

type RangeOptions =
  | [stop: number]
  | [start: number, stop: number]
  | [start: number, stop: number, step: number];

/**
 * Makes an array of numbers between `start` (defaults to 0) and `stop` (excluded) in increments of `step` (defaults to 1)
 */
export function range(...args: RangeOptions) {
  switch (args.length) {
    case 1: {
      const [stop] = args;
      return Array.from({ length: stop }, (_, i) => i);
    }
    case 2: {
      const [start, stop] = args;
      return Array.from({ length: stop - start }, (_, i) => start + i);
    }
    case 3: {
      const [start, stop, step] = args;
      return Array.from(
        { length: (stop - start) / step },
        (_, i) => start + i * step,
      );
    }
  }
}

Parameter Properties

Parameter Properties are a convenience TypeScript syntax for turning a constructor parameter into a class property, but do not align with existing Javascript semantics. They are not supported by the TC39 type annotation proposal.

class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // No body necessary
  }
}

The alternative is to use explicit field declarations and assignments

class Params {
  public readonly x: number;
  protected y: number;
  private z: number;

  constructor(x: number, y: number, z: number) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
}

Prefix-style type assertion

This is a legacy TypeScript syntax, the preferred way to assert a type is to use an as-expression

const something: unknown = "this is a string";
const legacy = (<string>something).length; // legacy prefix-style
const modern = (something as string).length; // as-expression

Importing types without the type keyword

You need to import types explicitly to avoid runtime errors. To enforce this make sure your tsconfig.json contains the "verbatimModuleSyntax": true rule.

Automatic semi-column insertion

The JavaScript syntax doesn't enforce the use of semi-columns, which can result in ambiguous or altogether buggy programs when stripping types. It's recommended to enforce the semi-columns rule in your formatter.

age // Without a semi-column this will become `age(1);` after type stripping
type Foo = string;
(1);

If you want to rely on automatic semi-column insertion at "transpilation time", then tsBlankSpace is a very good alternative. Note that it has a different focus than Type-Strip as it supports JSX, and inserts blank spaces instead of just stripping the types, which can result in bigger file sizes.

Benchmark

Type-Strip is super-fast: for short files of about 1000 lines of code it's 20% faster than esbuild.

The relative performance of Type-Strip on files of 998 lines

Check out the interactive plot

Benchmark of Type-Strip vs esbuild on small files

Built and signed on
GitHub Actions

Report package

Please provide a reason for reporting this package. We will review your report and take appropriate action.

Please review the JSR usage policy before submitting a report.

Add Package

deno add jsr:@fcrozatier/type-strip

Import symbol

import * as type_strip from "@fcrozatier/type-strip";
or

Import directly with a jsr specifier

import * as type_strip from "jsr:@fcrozatier/type-strip";

Add Package

pnpm i jsr:@fcrozatier/type-strip
or (using pnpm 10.8 or older)
pnpm dlx jsr add @fcrozatier/type-strip

Import symbol

import * as type_strip from "@fcrozatier/type-strip";

Add Package

yarn add jsr:@fcrozatier/type-strip
or (using Yarn 4.8 or older)
yarn dlx jsr add @fcrozatier/type-strip

Import symbol

import * as type_strip from "@fcrozatier/type-strip";

Add Package

vlt install jsr:@fcrozatier/type-strip

Import symbol

import * as type_strip from "@fcrozatier/type-strip";

Add Package

npx jsr add @fcrozatier/type-strip

Import symbol

import * as type_strip from "@fcrozatier/type-strip";

Add Package

bunx jsr add @fcrozatier/type-strip

Import symbol

import * as type_strip from "@fcrozatier/type-strip";