microsoft / microsoft/TypeScript

🚀 Proposal: Cross-Context Function Annotation (for APIs like `page.evaluate` of playwright)

オープン
#63,194 コメント 3 件 リアクション 2 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Awaiting More Feedback Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

Acknowledgement
  • I acknowledge that issues using this template may be closed without further explanation at the maintainer's discretion.
Comment

Motivation

APIs such as page.evaluate in browser automation libraries (e.g. Playwright) execute a function in a different JavaScript runtime context (browser context instead of Node.js).

Example:

const myText = "Hello world";
await page.evaluate(() => {
  console.log(myText); // ❌ Runtime error (myText is not defined in browser)
});

Although this fails at runtime, TypeScript does not report an error. The compiler assumes the lambda executes in the same lexical and global environment as the caller.

This creates a mismatch between TypeScript’s static model and the actual runtime semantics of cross-context APIs.


Problem Statement

TypeScript currently has no way to express that:

  • A function executes in a separate runtime context
  • The function must not capture outer-scope variables
  • The function must not access Node.js globals
  • The function runs against a specific global type (e.g. Window)

As a result:

  • Accidental closure capture is allowed
  • Incorrect global usage is not flagged
  • Runtime errors occur that could be prevented at compile time

Real-World Example

const secret = 42;

await page.evaluate(() => {
  console.log(secret); // ❌ Runtime error: secret is not defined
});

TypeScript currently allows this because it has no notion of execution context isolation.


Proposed Solution

Introduce a way to declare that a function:

  1. Executes in a separate context
  2. Has no access to outer lexical scope
  3. Uses a specified global type

Option A: isolated Function Modifier (New Syntax)
const myText = "Hello world";
await page.evaluate(isolated () => {
  console.log(document.title); // ✅ OK
  console.log(myText);  // ❌ Compile error
});

Semantics:

  • No closure capture allowed
  • No outer-scope variable access
  • Global type defined by API signature
  • Treated as if compiled in a separate program with structured-clone semantics

This would be conceptually similar to --isolatedModules, but at function scope.


Option B: CrossContext<TGlobal> Type-Level Mechanism

If new syntax is not desirable, introduce a type-level mechanism:

type CrossContext<TGlobal, TArgs extends any[], TResult> =
  (this: TGlobal, ...args: TArgs) => TResult;

Library usage example:

interface Page {
  evaluate<R, A>(
    fn: CrossContext<Window, [A], R>,
    arg: A
  ): Promise<R>;
}

With compiler rule:

  • Functions of type CrossContext<...> may not reference outer lexical bindings
  • Only globals available on TGlobal are allowed

Example behavior:

const x = 5;

await page.evaluate(() => {
  console.log(x);        // ❌ Error: outer scope capture not allowed
  console.log(process);  // ❌ Error: not part of Window
  console.log(document); // ✅ OK
});

Option C: JSDoc Annotation
await page.evaluate(
  /** @crossContext Window */
  () => {
    console.log(document.title);
  }
);

Compiler behavior:

  • Treat function as isolated
  • Restrict global access
  • Disallow closure capture

This avoids syntax changes while still enabling static safety.


Prior Art

  • Web Workers (structured cloning, no shared closures)
  • postMessage semantics
  • Rust’s Send / Sync closure restrictions
  • Sandboxed runtimes (e.g. Electron multi-process model)

Benefits

  • Prevents a common class of runtime errors
  • Improves safety of browser automation, workers, and SSR
  • Makes execution semantics explicit
  • Improves editor tooling and developer experience
  • Enables safer multi-runtime APIs

Non-Goals

  • Not intended as a security boundary
  • Not intended to restrict normal functions
  • Not a replacement for ESLint, but a stronger compiler guarantee

Why This Belongs in TypeScript (Not Just ESLint)

While ESLint could detect some closure capture cases, only the TypeScript compiler can:

  • Properly reason about type environments
  • Enforce global type restrictions
  • Integrate deeply with existing type inference

Cross-context execution is increasingly common in:

  • Browser automation
  • Workers
  • Hybrid runtimes
  • SSR frameworks

TypeScript currently has no way to model this distinction.


Summary

TypeScript lacks a mechanism to model execution-context isolation. Introducing an isolated function modifier or a CrossContext<TGlobal> constraint would prevent runtime errors and better reflect modern JavaScript runtime architectures.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

この issue では、ソースファイル、テスト、コンパイラのエントリポイントが指定されていません。まず、提案されている構文、type-level、JSDoc のアプローチを、クロージャキャプチャとグローバルアクセスの例と比較し、その後、コンテキスト間の分離を示すために必要なコンパイラの動作とテストを定義してください。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
javascript, typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。