microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.95k stars 12.47k forks source link

TypeScript Extensions Proposal #60421

Closed shlyk closed 5 hours ago

shlyk commented 5 hours ago

🔍 Search Terms

TypeScript Extensions Declaration merging

✅ Viability Checklist

⭐ Suggestion

A proposal to add first-class extensions to TypeScript, enabling developers to extend existing classes in a type-safe and modular way.

Well, right now you could:

  1. Mess with prototypes (yuck!)
  2. Write utility functions (and lose that nice method chaining)
  3. Use mixins (and deal with all that composition complexity)
  4. Try declaration merging (and give up on proper encapsulation)

TypeScript extensions would elegantly bridge the gap between JavaScript's prototype-based inheritance and TypeScript's type system by providing a safe, controlled way to extend objects that aligns with both languages' core principles:

1. Alignment with JavaScript's Prototype Nature

2. Type-Safe Prototype Enhancement Instead of unsafe and discouraging modifications like:

declare global {
  interface Array<T> {
    first(): T | undefined;
  }
}

Array.prototype.first = function () {
  const [first] = this;
  return first;
};

We get something clean and simple like this:

extension Array<T> {
  first(): T | undefined {
    const [first] = this;
    return first;
  }
}

3. Zero Runtime Overhead

The original request was made in 2014, but with TypeScript's current maturity and the proven success of extensions in other languages, it might be time to revisit this feature. The modern TypeScript team might have a different perspective now, especially given:

  1. Growing complexity of TypeScript applications
  2. Need for better code organization patterns
  3. Success of similar features in other languages
  4. TypeScript's enhanced transformation capabilities

📃 Motivating Example

Swift, Kotlin, and C# — these languages have proven that extensions aren’t just a nice-to-have feature — they’re a game-changer for code organization and maintainability. What’s exciting is that we can learn from all of these implementations, taking the best parts of each while adapting them to TypeScript’s unique characteristics and use cases:

Swift Extensions Kotlin Extensions C# Extension Methods

💻 Use Cases

// Example 1: Array Extensions
// Shows how to add common utility methods to arrays with proper TypeScript generics.
extension Array<T> {
  first(): T | undefined {
    const [first] = this;
    return first;
  }

  isEmpty(): boolean {
    return this.length === 0;
  }
}

['A', 'B', 'C'].first(); // returns 'A'
[].isEmpty(); // returns `true`
// Example 2: Date Extensions with Private State
// Demonstrates how extensions can use private fields (#today) for encapsulation.
extension Date {
  #today = new Date();

  isToday(): boolean {
    return this.#today.toDateString() === this.toDateString();
  }
}

const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));
yesterday.isToday(); // returns `false`
// Example 3: Number Extensions with Method Chaining
// Shows how to extend primitives with mathematical utilities.
// The clamp method constrains a number between min and max values,
// demonstrating how extensions can make mathematical operations more readable.
// Note the double dot (..) syntax required for number methods.
extension Number {
    clamp(min: number, max: number): number {
        return Math.min(Math.max(this, min), max);
    }
}

10..clamp(0, 5); // returns 5
// Example 4: Class Layer Organization with Extensions
// Shows how extensions enable logical grouping of class functionality
// while maintaining clean separation of concerns and type safety.

// service.ts
class Service {
 // Implement core state management
}

// actions.ts
import { Service } from './service';

extension Service {
 add(...): ... {
   // Implement
 }

 remove(...): ... {
   // Implement
 }

 update(...): ... {
   // Implement
 }

 complete(...): ... {
   // Implement
 }
}

// queries.ts
import { Service } from './service';

extension Service {
 getAll(): ... {
   // Implement
 }

 getById(...): ... {
   // Implement
 }

 search(...): ... {
   // Implement
 }
}
jcalz commented 5 hours ago

(Note: I'm not a TS team member, feel free to ignore me if you want to wait for official word)

You checked this:

  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)

but it's clearly a runtime feature (non-ECMAscript syntax with JavaScript output). If you want this feature it needs to be a JS feature first (that is, it should look like your proposal but without the type annotations, such as extension Array { first() { const [first] = this; return first; } isEmpty() { return this.length === 0; } }) and then TS will support it. Stuff like this needs to go through the TC39 process and isn't in scope for TypeScript until it reaches Stage 3 of that process.

Nothing substantial has changed from https://github.com/microsoft/TypeScript/issues/9#issuecomment-174407229.

This proposal will be declined. You might want to save the TS team the trouble of doing so by closing this yourself.