Skip to content

Looking for API ideas: Typed closure handle for BridgeJS #541

@kateinoigakukun

Description

@kateinoigakukun

Summary

BridgeJS needs a typed closure handle type that preserves object identity across the Swift/JS bridge. We're looking for community input on naming.

Background

JavaScriptKit already has JSClosure for untyped closures:

let closure = JSClosure { args -> JSValue in
    // ...
}

For BridgeJS, we need a typed variant with a generic parameter for the function signature. This enables type-safe declarations like:

@JSFunction func addEventListener(_ type: String, _ listener: (MessageEvent) -> Void)

The typed closure handle is essential for APIs like addEventListener/removeEventListener that require stable object identity, passing the same closure reference to both calls.

Related issues

Constraints

  1. Must coexist with JSClosure: we can't simply make JSClosure<T> generic (breaking change)
  2. Explicit conversion required: Swift doesn't support implicit conversion from closure literals, so users will write the type name frequently

Candidates

1. Nested type: JSClosure.Typed<T>

@JSClass struct JSHTMLElement {
    @JSFunction func addEventListener(_ type: String, _ listener: JSClosure.Typed<(MessageEvent) -> Void>)
    @JSFunction func removeEventListener(_ type: String, _ listener: JSClosure.Typed<(MessageEvent) -> Void>)
}

let handler = JSClosure.Typed<(MessageEvent) -> Void> { event in
    print(event.data)
}
document.addEventListener("load", handler)
document.removeEventListener("load", handler)

@JSFunction func requestAnimationFrame(_ callback: JSOneshotClosure.Typed<(Double) -> Void>)

requestAnimationFrame(JSOneshotClosure.Typed { timestamp in
  print(timestamp)
})
  • Clear relationship to JSClosure
  • Discoverable via autocomplete
  • Verbose (15 characters)

2. JSTypedClosure<T>

@JSClass struct JSHTMLElement {
    @JSFunction func addEventListener(_ type: String, _ listener: JSTypedClosure<(MessageEvent) -> Void>)
    @JSFunction func removeEventListener(_ type: String, _ listener: JSTypedClosure<(MessageEvent) -> Void>)
}

let handler = JSTypedClosure<(MessageEvent) -> Void> { event in
    print(event.data)
}
document.addEventListener("load", handler)
document.removeEventListener("load", handler)

@JSFunction func requestAnimationFrame(_ callback: JSTypedClosure<(Double) -> Void>)

requestAnimationFrame(JSTypedClosure.oneshot { timestamp in
  print(timestamp)
})

3. JSCallback<T>

@JSClass struct JSHTMLElement {
    @JSFunction func addEventListener(_ type: String, _ listener: JSCallback<(MessageEvent) -> Void>)
    @JSFunction func removeEventListener(_ type: String, _ listener: JSCallback<(MessageEvent) -> Void>)
}

let handler = JSCallback<(MessageEvent) -> Void> { event in
    print(event.data)
}
document.addEventListener("load", handler)
document.removeEventListener("load", handler)

@JSFunction func requestAnimationFrame(_ callback: JSCallback.Oneshot<(Double) -> Void>)

requestAnimationFrame(JSCallback.Oneshot { timestamp in
  print(timestamp)
})

3. [Need your input]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions