[BridgeJS] Support @JS var declarations for global scope imports
Motivation
Currently, importing JavaScript APIs from the global scope requires users to write TypeScript declaration files and import APIs through the standard BridgeJS workflow. For simple use cases where users only need to access a few well-known global APIs like console.log() or document.getElementById(), this approach can feel heavyweight and requires additional setup steps.
Proposed Solution
Allow users to declare global scope imports directly in Swift using @JS var declarations with protocol interfaces, eliminating the need for separate .d.ts files for simple global API access.
High-level API
@JSImport protocol JSConsole {
func log(_ message: String)
func error(_ message: String)
}
@JSImport var console: JSConsole
@JSImport protocol JSElement {
var innerHTML: String { get set }
func addEventListener(_ event: String, _ handler: @escaping () -> Void)
}
@JSImport protocol JSDocument {
func getElementById(_ id: String) -> JSElement?
}
@JSImport(from: .global) var document: JSDocument
@JSImport(from: .module("uuid"), jsName: "v4")
func uuidv4() -> String
@JSExport func run() {
print(uuidv4())
}
Implementation
This feature would require adding a macro plugin to support @JS var [name]: [Type] declarations that generates getter/setter accessors interacting with the code generated by @JS protocol
If it works well, we might be able to make ImportTS just to generate @JS annotated Swift code and centralize ABI-facing code in ExportSwift
@krodak I'd like to hear your thoughts on this idea
@kateinoigakukun I love this idea! The only thing I would suggest is that we pick a different macro name for importing JS to Swift, so that it is clear which macros usages are bridging Swift to JS, and which ones are bridging JS to Swift. That also might help with the implementation under the hood as well, since the two different directions are opposite use cases.
Not sure what the name should be. And wondering if it would make sense to use two different names for each use case if we go this route. Maybe @JSImport for bringing JS to Swift, and either @JS or @JSExport for Swift to JS?
If we use the same @JS for both directions, the only thing that cues developers (and the implementation under the hood) of the difference in direction is the type on the right hand side (JSConsole, JSElement, etc). Otherwise, this usage looks a lot like an export if one is used to the other use case of exporting Swift to JS.
This would provide a cleaner API for exposing JS to Swift. If it helps prioritize, we're already able to achieve this using the lower-level existing constructs in JavaScriptKit for now. Happy to adopt this once someone has capacity to implement this.
@scottmarchant I think having different names for them makes a lot of sense. It will make it easier for documentation too IMO