Skip to content

TypeScript Compilation Performance Optimization Opportunities #685

@jdmiranda

Description

@jdmiranda

Summary

Following recent performance improvements in PR #675 and the ongoing investigation of issue #666 (memory exhaustion during type-checking), I've identified several additional optimization opportunities that could significantly improve TypeScript compilation performance for @octokit/types and all downstream consumers.

These optimizations focus on reducing type instantiation depth, minimizing expensive type operations, and improving IDE responsiveness without requiring breaking changes to the public API.


Optimization Opportunities

1. Lazy Type Evaluation with Type Aliases

Current Issue:
The Endpoints type uses complex mapped types and UnionToIntersection that are evaluated immediately, even when only a subset of endpoints are used in a consuming project.

Proposed Solution:
Introduce lazy evaluation patterns using type aliases that defer complex type computations:

// Current approach (evaluates everything immediately)
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

// Optimized approach with lazy evaluation
type LazyUnionToIntersection<U> = {
  [K in keyof U]: (x: U[K]) => void
} extends Record<keyof U, (x: infer I) => void> ? I : never;

// Apply lazy evaluation to ExtractParameters
type ExtractParameters<T> = "parameters" extends keyof T
  ? LazyUnionToIntersection<{
      [K in keyof T["parameters"]]-?: T["parameters"][K];
    }[keyof T["parameters"]]>
  : {};

Expected Impact: 15-25% reduction in type instantiation depth, improved IDE responsiveness when working with individual endpoints.


2. Conditional Type Depth Reduction

Current Issue:
Deep conditional type chains in utilities like GetResponseTypeFromEndpointMethod can cause TypeScript to perform excessive type checking iterations.

Proposed Solution:
Flatten conditional type chains and use distributive conditional types more strategically:

// Current implementation
type Unwrap<T> = T extends Promise<infer U> ? U : T;
export type GetResponseTypeFromEndpointMethod<T extends AnyFunction> = Unwrap<ReturnType<T>>;

// Optimized: Combine operations to reduce instantiation depth
export type GetResponseTypeFromEndpointMethod<T extends AnyFunction> = 
  ReturnType<T> extends Promise<infer U> 
    ? U 
    : ReturnType<T>;

// Even better: Use infer directly in one step
export type GetResponseTypeFromEndpointMethod<T extends (...args: any[]) => any> = 
  T extends (...args: any[]) => Promise<infer R> 
    ? R 
    : T extends (...args: any[]) => infer R 
      ? R 
      : never;

Expected Impact: 10-15% reduction in type-checking time for projects using response type extraction utilities.


3. Strategic Use of interface vs type Aliases

Current Issue:
The codebase mixes interface and type declarations. TypeScript handles interface merging and extension differently than type aliases, and interfaces can be more performant for object shapes.

Proposed Solution:
Use interfaces for object shapes and extensible types, reserve type aliases for unions, mapped types, and computed types:

// For extensible object types - USE INTERFACE
export interface EndpointDefaults {
  baseUrl?: string;
  headers?: RequestHeaders;
  mediaType?: {
    format?: string;
    previews?: string[];
  };
  request?: RequestRequestOptions;
}

// For unions and complex computations - USE TYPE
export type RequestMethod = "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT";

// Current EndpointInterface is good - it's already an interface
// But ensure internal helper types use 'type' for computed operations

Expected Impact: 5-10% improvement in type checking performance due to more efficient caching of interface declarations.


4. Import Structure Optimization

Current Issue:
The barrel export pattern in index.ts (export * from "./...") causes TypeScript to load and analyze all type definitions even when consumers only need a subset.

Proposed Solution:
While maintaining backward compatibility, provide optimized sub-path exports in package.json:

{
  "exports": {
    ".": {
      "types": "./dist-types/index.d.ts",
      "import": "./dist-src/index.js"
    },
    "./endpoints": {
      "types": "./dist-types/generated/Endpoints.d.ts",
      "import": "./dist-src/generated/Endpoints.js"
    },
    "./utilities": {
      "types": "./dist-types/GetResponseTypeFromEndpointMethod.d.ts",
      "import": "./dist-src/GetResponseTypeFromEndpointMethod.js"
    }
  }
}

Allow consumers to import only what they need:

// Instead of importing everything
import type { Endpoints, GetResponseTypeFromEndpointMethod } from "@octokit/types";

// Import only endpoints
import type { Endpoints } from "@octokit/types/endpoints";
// Import only utilities
import type { GetResponseTypeFromEndpointMethod } from "@octokit/types/utilities";

Expected Impact: 20-30% reduction in initial type-checking time for projects that only use specific parts of the library.


5. Type Caching with Intermediate Types

Current Issue:
Complex type operations in the Endpoints interface are re-computed multiple times across different parts of the codebase.

Proposed Solution:
Create intermediate type aliases that TypeScript can cache:

// Instead of inline complex operations
type Operation<S extends keyof operations> = {
  parameters: ExtractParameters<operations[S]>;
  response: ExtractResponse<operations[S]>;
};

// Cache frequently-used endpoint patterns
type CachedEndpointOperation<S extends keyof operations> = Operation<S>;

// Use the cached version in the main Endpoints interface
export interface Endpoints {
  [route: string]: CachedEndpointOperation<keyof operations>;
}

Expected Impact: 10-20% reduction in redundant type calculations, especially in large projects with many endpoint usages.


Implementation Strategy

Phase 1: Non-Breaking Optimizations (Recommended First)

  1. Optimize conditional type depth (The automated release is failing 🚨 #2)
  2. Add intermediate type caching (AuthInterface & StrategyInterface #5)
  3. Apply interface vs type alias optimization (Publish new versions of Octokit Libraries using @octokit/types #3)

Risk: Minimal - These changes maintain the same public API surface

Phase 2: Opt-in Improvements

  1. Add package.json exports for sub-path imports (AuthInterface #4)

Risk: Low - Provides new capabilities while maintaining backward compatibility

Phase 3: Advanced Optimizations (Requires Testing)

  1. Implement lazy type evaluation (Initial version #1)

Risk: Medium - Requires thorough testing to ensure type inference behavior remains consistent


Backward Compatibility

All proposed optimizations maintain the existing public API. The type contracts remain identical, only the internal implementation strategies change. This means:

  • ✅ No breaking changes to consuming code
  • ✅ Same type inference behavior
  • ✅ Existing imports continue to work
  • ✅ Can be released as a minor version

Benchmarking Plan

I'm happy to help with:

  1. Creating benchmark suite - Test compilation time with various project sizes
  2. Measuring type instantiation depth - Using tsc --extendedDiagnostics
  3. IDE responsiveness testing - Measuring IntelliSense speed in VS Code
  4. Memory profiling - Tracking TypeScript compiler memory usage

Example benchmark approach:

# Before optimization
tsc --extendedDiagnostics --noEmit 2>&1 | grep "Instantiations\|Time"

# After optimization
tsc --extendedDiagnostics --noEmit 2>&1 | grep "Instantiations\|Time"

Contributing

I'm eager to help implement these optimizations. I can:

  • Submit PRs for individual optimizations with benchmarks
  • Help set up performance regression tests
  • Collaborate on measuring real-world impact in downstream packages

These optimizations build on the excellent recent work in PR #675 and directly address the performance concerns raised in issue #666. The TypeScript ecosystem would greatly benefit from faster type-checking in such a widely-used package (used by 158+ npm packages).

Would the maintainers be interested in these improvements? I'm happy to start with a proof-of-concept PR for the lowest-risk optimizations (#2, #3, #5) to demonstrate the performance gains.


Related Issues & Context

Thank you for maintaining this critical piece of the Octokit ecosystem!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: MaintenanceAny dependency, housekeeping, and clean up Issue or PR

    Type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions