microsoft / microsoft/TypeScript
🚀 Proposal: Cross-Context Function Annotation (for APIs like `page.evaluate` of playwright)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
### Acknowledgement
- [x] 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:
```ts
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
```ts
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)
```ts
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` Type-Level Mechanism
If new syntax is not desirable, introduce a type-level mechanism:
```ts
type CrossContext =
(this: TGlobal, ...args: TArgs) => TResult;
```
Library usage example:
```ts
interface Page {
evaluate(
fn: CrossContext,
arg: A
): Promise;
}
```
With compiler rule:
- Functions of type `CrossContext<...>` may not reference outer lexical bindings
- Only globals available on `TGlobal` are allowed
Example behavior:
```ts
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
```ts
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` constraint would prevent runtime errors and better reflect modern JavaScript runtime architectures.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue names no source files, tests, or compiler entry points. Start by comparing the proposed syntax, type-level, and JSDoc approaches against the closure-capture and global-access examples, then define the compiler behavior and tests needed to demonstrate isolation across contexts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100