🎯

type-safe-monkey-patching

🎯Skill

from marius-townhouse/effective-typescript-skills

VibeIndex|
What it does

Enables type-safe runtime extension of global objects and DOM elements in TypeScript without sacrificing type checking or using `as any`.

πŸ“¦

Part of

marius-townhouse/effective-typescript-skills(83 items)

type-safe-monkey-patching

Installation

Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills --all
Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills -s prefer-unknown-over-any exhaustiveness-checking
Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills -a opencode claude-code
Quick InstallInstall with npx
npx skills add marius-townhouse/effective-typescript-skills -l
git cloneClone repository
git clone https://github.com/marius-townhouse/effective-typescript-skills.git
πŸ“– Extracted from docs: marius-townhouse/effective-typescript-skills
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Use when adding properties to window, document, or DOM elements. Use when extending built-in objects at runtime. Use when working with jQuery or D3 globals. Use when migrating JavaScript that uses global variables.

Overview

# Type-Safe Approaches to Monkey Patching

Overview

Monkey patching - adding properties to built-in objects at runtime - is a JavaScript pattern that becomes problematic in TypeScript. TypeScript doesn't know about properties you've added to window, document, or DOM elements, leading to type errors. While as any is the quick fix, it sacrifices type safety entirely. There are better approaches that maintain type checking while modeling your runtime modifications.

When to Use This Skill

  • Adding global variables to window or document
  • Attaching data to DOM elements
  • Working with libraries that require global state (jQuery, D3)
  • Migrating JavaScript code that uses monkey patching
  • Storing application state on global objects

The Iron Rule

Never use (obj as any).property for monkey patching. Use interface augmentation or narrower type assertions that preserve type safety.

Detection

Watch for these patterns:

```typescript

// RED FLAGS - Untyped monkey patching

(window as any).myApp = { / ... / };

(document as any).user = currentUser;

(element as any).customData = data;

// These lose all type safety

(window as any).usr = user; // Typo not caught

(window as any).user = /regex/; // Wrong type not caught

```

Type-Safe Approaches

Approach 1: Interface Augmentation (Global)

Best when the property is truly global and always available:

```typescript

// types/global.d.ts

interface User {

name: string;

id: number;

}

declare global {

interface Window {

/* The currently logged-in user /

user: User;

}

}

// Usage - fully type-safe

window.user = { name: "Alice", id: 1 }; // OK

window.user = { name: "Alice" }; // Error: missing 'id'

window.usr = user; // Error: typo caught

console.log(window.user.name); // Autocomplete works

```

Approach 2: Augmentation with undefined (Safer)

When the global might not be set:

```typescript

declare global {

interface Window {

/* The currently logged-in user - may not be set /

user: User | undefined;

}

}

// Forces handling of undefined

function greetUser() {

if (window.user) {

alert(Hello ${window.user.name}!); // OK after check

}

}

// Or use optional chaining

alert(Hello ${window.user?.name ?? 'Guest'}!);

```

Approach 3: Custom Type Assertion (Scoped)

When you don't want to pollute the global Window type:

```typescript

type MyWindow = typeof window & {

/* The currently logged-in user /

user: User | undefined;

};

// Assignment

(window as MyWindow).user = currentUser;

// Access

const user = (window as MyWindow).user;

if (user) {

console.log(user.name);

}

```

Approach 4: DOM Element Data (Type-Safe)

For attaching data to DOM elements:

```typescript

// Define extended element type

interface ExtendedElement extends HTMLElement {

customData?: {

initialized: boolean;

value: number;

};

}

// Use with type assertion

const el = document.getElementById('myElement') as ExtendedElement;

el.customData = { initialized: true, value: 42 };

// Better: Use data attributes or WeakMap

const elementData = new WeakMap();

elementData.set(el, { initialized: true, value: 42 });

```

Pressure Resistance Protocol

When pressured to use as any for quick monkey patching:

  1. Evaluate need: Is monkey patching truly necessary, or can you restructure?
  2. Choose approach: Interface augmentation for global, custom type for scoped
  3. Add undefined: Unless you're certain the value is always present
  4. Document: Add JSDoc comments explaining the monkey patch
  5. Consider alternatives: Can you use a module-level variable instead?

Red Flags

| Anti-Pattern | Why It's Bad |

|--------------|--------------|

| (window as any).prop | No type safety, typos not caught |

| (document as any).data | Wrong types not caught |

| Global augmentation for page-specific data | Lies about availability |

| Missing undefined in augmentation | Hides race conditions |

Common Rationalizations

"It's just one property"

Reality: Every as any is a potential runtime error. One property today becomes twenty tomorrow, all unchecked.

"I'll be careful"

Reality: Your colleagues won't know about the property. They'll misspell it. Type-safe augmentation documents and enforces the contract.

"Augmentation is too much boilerplate"

Reality: A three-line interface declaration saves hours of debugging typos and wrong types.

"It's legacy code, we'll fix it later"

Reality: Interface augmentation takes the same time as as any but gives you safety immediately.

Better Alternatives to Monkey Patching

Consider these before monkey patching:

```typescript

// 1. Module-level state

let currentUser: User | undefined;

export function setUser(user: User) { currentUser = user; }

export function getUser() { return currentUser; }

// 2. Context/dependency injection

class AppContext {

user: User | undefined;

}

const context = new AppContext();

// 3. React Context, Vue provide/inject, etc.

const UserContext = createContext(undefined);

// 4. WeakMap for DOM data

const elementState = new WeakMap();

```

Quick Reference

| Scenario | Recommended Approach |

|----------|---------------------|

| Global always available | Interface augmentation |

| Global sometimes available | Augmentation with \| undefined |

| Page-specific global | Custom type assertion |

| DOM element data | WeakMap or data attributes |

| Library requires global | Interface augmentation + documentation |

The Bottom Line

Monkey patching in JavaScript requires explicit typing in TypeScript. Use interface augmentation for global properties and custom type assertions for scoped modifications. Never use as any - it defeats the purpose of TypeScript. Always consider whether monkey patching is truly necessary; often there's a cleaner architectural solution.

Reference

  • Effective TypeScript, 2nd Edition by Dan Vanderkam
  • Item 47: Prefer Type-Safe Approaches to Monkey Patching

More from this repository10

🎯
tsdoc-comments🎯Skill

Generates TypeScript documentation comments (TSDoc) to explain public APIs, complex types, and provide comprehensive code documentation with IDE tooltips.

🎯
async-over-callbacks🎯Skill

Transforms callback-based asynchronous code into clean, readable async/await patterns for better type flow and error handling.

🎯
context-type-inference🎯Skill

Helps restore precise type context when extracting values, preventing type inference errors through annotations, const assertions, and type preservation techniques.

🎯
create-objects-all-at-once🎯Skill

Efficiently initializes multiple TypeScript objects simultaneously using concise object literal syntax and spread operators.

🎯
module-by-module-migration🎯Skill

Guides developers through systematic TypeScript module migration, breaking down complex refactoring into manageable, incremental steps.

🎯
ts-js-relationship🎯Skill

Explains TypeScript's relationship to JavaScript, highlighting how it adds static typing and catches errors before runtime while remaining fully compatible with JavaScript code.

🎯
code-gen-independent🎯Skill

Generates JavaScript code despite TypeScript type errors and demonstrates that TypeScript types are erased at runtime, requiring alternative type checking strategies.

🎯
precise-string-types🎯Skill

Enforces strict string type constraints and prevents unintended string type conversions in TypeScript projects.

🎯
variadic-tuple-types🎯Skill

Demonstrates advanced TypeScript tuple type techniques for creating flexible function signatures with varying argument lengths and types.

🎯
type-display-attention🎯Skill

Displays and simplifies complex TypeScript types to improve IDE readability and developer experience.