mcpField
T
type-safe-monkey-patching

type-safe-monkey-patching

github.com/marius-townhouse開発644 回インストール38 週間前に更新
リポジトリ

type-safe-monkey-patching について

グローバルオブジェクトや DOM 要素にプロパティを追加する TypeScript 開発者向けのスキルです。any を使わず、インターフェース拡張やスコープを限定した型アサーションで型安全に扱う方法を紹介します。

主な機能

  1. 01インターフェース拡張でグローバルプロパティを宣言
  2. 02未設定の可能性を undefined で表現
  3. 03独自の型アサーションで変更範囲を限定
  4. 04DOM 要素の型を拡張
  5. 05モジュール内状態などの代替案を検討

活用シーン

  1. 01window や document に型付きプロパティを追加
  2. 02DOM 要素にアプリケーションデータを付与
  3. 03グローバル状態に依存する JavaScript の移行
  4. 04モンキーパッチでの any の使用を回避

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill type-safe-monkey-patching

インストール後、Claude Code で/type-safe-monkey-patchingを入力して読み込まれたか確認します。

詳細

モンキーパッチに対する型安全なアプローチ

概要

モンキーパッチとは、実行時に組み込みオブジェクトへプロパティを追加する手法で、TypeScript では問題が生じる JavaScript のパターンです。TypeScript は、window、document、DOM 要素に追加したプロパティを認識しないため、型エラーが発生します。手早く済ませるなら as any ですが、型安全性が完全に失われます。実行時の変更をモデル化しながら型チェックを維持できる、より良い方法があります。

このスキルを使用する場面

  • window または document にグローバル変数を追加するとき
  • DOM 要素にデータを付加するとき
  • グローバル状態を必要とするライブラリ(jQuery、D3)を使用するとき
  • モンキーパッチを使用する JavaScript コードを移行するとき
  • グローバルオブジェクトにアプリケーションの状態を保存するとき

絶対に守るルール

モンキーパッチに (obj as any).property を決して使用しないでください。型安全性を維持できる、インターフェースの拡張または対象を絞った型アサーションを使用してください。

検出

次のパターンに注意してください。

// 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

型安全なアプローチ

アプローチ 1:インターフェースの拡張(グローバル)

プロパティが本当にグローバルで、常に利用可能な場合に最適です。

// 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
アプローチ 2:undefined を含めた拡張(より安全)

グローバルが設定されていない場合があるときに使用します。

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'}!`);
アプローチ 3:カスタム型アサーション(スコープ限定)

グローバルな Window 型を汚染したくない場合に使用します。

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);
}
アプローチ 4:DOM 要素のデータ(型安全)

DOM 要素にデータを付加する場合に使用します。

// 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<HTMLElement, { initialized: boolean; value: number }>();
elementData.set(el, { initialized: true, value: 42 });

圧力に負けないための手順

手早くモンキーパッチを実装するために as any を使うよう求められた場合:

  1. 必要性を評価する:モンキーパッチは本当に必要ですか。それとも、構造を変更できますか。
  2. アプローチを選ぶ:グローバルにはインターフェースの拡張、スコープ限定にはカスタム型を使用します。
  3. undefined を追加する:値が常に存在すると確信できない限り、追加します。
  4. ドキュメントに記録する:モンキーパッチを説明する JSDoc コメントを追加します。
  5. 代替案を検討する:代わりにモジュールレベルの変数を使用できますか。

危険信号

アンチパターン問題点
(window as any).prop型安全性がなく、入力ミスを検出できません
(document as any).data型の誤りを検出できません
ページ固有のデータに対するグローバルな拡張利用可能性について誤った情報を示します
拡張に undefined がない競合状態を見えなくします

よくある正当化

「プロパティは 1 つだけです」

実際のところ:as any はすべて、実行時エラーの原因になり得ます。今日の 1 つのプロパティが明日には 20 個になり、それらすべてが未チェックのままになります。

「注意して使います」

実際のところ:同僚はそのプロパティを知りません。入力を間違えるでしょう。型安全な拡張なら、契約が文書化され、強制されます。

「拡張は定型コードが多すぎます」

実際のところ:3 行のインターフェース宣言により、入力ミスや型の誤りのデバッグに何時間も費やさずに済みます。

「レガシーコードなので、後で直します」

実際のところ:インターフェースの拡張にかかる時間は as any と同じですが、すぐに安全性が得られます。

モンキーパッチに代わるより良い方法

モンキーパッチを使う前に、次の方法を検討してください。

// 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<User | undefined>(undefined);

// 4. WeakMap for DOM data
const elementState = new WeakMap<Element, ElementState>();

クイックリファレンス

状況推奨するアプローチ
グローバルが常に利用可能インターフェースの拡張
グローバルが利用できない場合がある| undefined を含めた拡張
ページ固有のグローバルカスタム型アサーション
DOM 要素のデータWeakMap またはデータ属性
ライブラリがグローバルを必要とするインターフェースの拡張とドキュメント化

要点

JavaScript でモンキーパッチを使用する場合、TypeScript では明示的な型付けが必要です。グローバルプロパティにはインターフェースの拡張を、スコープ限定の変更にはカスタム型アサーションを使用してください。TypeScript の目的を損なうため、as any は決して使用しないでください。モンキーパッチが本当に必要かどうかを常に検討してください。多くの場合、よりすっきりとしたアーキテクチャ上の解決策があります。

参考資料

  • Dan Vanderkam 著『Effective TypeScript』第 2 版
  • 項目 47:モンキーパッチには型安全なアプローチを優先する

コメント