From 5c96de5befa5cf872e5d518b0303e3a7820086b3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 14:52:49 +0900 Subject: [PATCH 1/3] BridgeJS: Restore `new SwiftClass`-style constructor by using `Object.create` --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 28 ++++++-------- .../BridgeJSLinkTests/Namespaces.Export.d.ts | 4 +- .../BridgeJSLinkTests/Namespaces.Export.js | 37 +++++++------------ .../BridgeJSLinkTests/SwiftClass.Export.d.ts | 2 +- .../BridgeJSLinkTests/SwiftClass.Export.js | 25 +++++-------- 5 files changed, 38 insertions(+), 58 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 73c19b69..49cabf41 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -43,18 +43,16 @@ struct BridgeJSLink { let swiftHeapObjectClassJs = """ /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __construct(ptr, deinit) { - return new SwiftHeapObject(ptr, deinit); - } - - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -525,13 +523,11 @@ struct BridgeJSLink { var constructorLines: [String] = [] constructorLines.append("static __construct(ptr) {") constructorLines.append( - "return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4) + "return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.name)_deinit, \(klass.name).prototype);" + .indent(count: 4) ) constructorLines.append("}") constructorLines.append("") - constructorLines.append("constructor(pointer, deinit) {") - constructorLines.append("super(pointer, deinit);".indent(count: 4)) - constructorLines.append("}") jsLines.append(contentsOf: constructorLines.map { $0.indent(count: 4) }) if let constructor: ExportedConstructor = klass.constructor { @@ -541,7 +537,7 @@ struct BridgeJSLink { } var funcLines: [String] = [] funcLines.append("") - funcLines.append("static init(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") + funcLines.append("constructor(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) funcLines.append(contentsOf: thunkBuilder.bodyLines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: thunkBuilder.cleanupLines.map { $0.indent(count: 4) }) @@ -551,7 +547,7 @@ struct BridgeJSLink { jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "init\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" + "constructor\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" .indent(count: 4) ) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts index 65b9360e..d5b901c2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts @@ -51,10 +51,10 @@ export interface UUID extends SwiftHeapObject { } export type Exports = { Greeter: { - init(name: string): Greeter; + constructor(name: string): Greeter; } Converter: { - init(): Converter; + constructor(): Converter; } UUID: { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index a9678497..df3f30de 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -60,18 +60,16 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __construct(ptr, deinit) { - return new SwiftHeapObject(ptr, deinit); - } - - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -81,14 +79,11 @@ export async function createInstantiator(options, swift) { } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return new Greeter(ptr, instance.exports.bjs_Greeter_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } - static init(name) { + constructor(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); @@ -104,14 +99,11 @@ export async function createInstantiator(options, swift) { } class Converter extends SwiftHeapObject { static __construct(ptr) { - return new Converter(ptr, instance.exports.bjs_Converter_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Converter_deinit, Converter.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } - static init() { + constructor() { const ret = instance.exports.bjs_Converter_init(); return Converter.__construct(ret); } @@ -124,12 +116,9 @@ export async function createInstantiator(options, swift) { } class UUID extends SwiftHeapObject { static __construct(ptr) { - return new UUID(ptr, instance.exports.bjs_UUID_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UUID_deinit, UUID.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } uuidString() { instance.exports.bjs_UUID_uuidString(this.pointer); const ret = tmpRetString; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index 65391433..8c680fbc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -17,7 +17,7 @@ export interface Greeter extends SwiftHeapObject { } export type Exports = { Greeter: { - init(name: string): Greeter; + constructor(name: string): Greeter; } takeGreeter(greeter: Greeter): void; } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 9f79f20f..379e76a4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -60,18 +60,16 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __construct(ptr, deinit) { - return new SwiftHeapObject(ptr, deinit); - } - - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -81,14 +79,11 @@ export async function createInstantiator(options, swift) { } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return new Greeter(ptr, instance.exports.bjs_Greeter_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } - static init(name) { + constructor(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); From 0deecb2c7092d0201c6618f30493287a448c4408 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 14:53:38 +0900 Subject: [PATCH 2/3] Revert "BridgeJS: Update tests to use new static init() API" This reverts commit 326e5937c41b113d816f4ab23d861142e4c3faba. --- Tests/prelude.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index ddb232d2..6a26dc8a 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -115,7 +115,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(exports.roundTripString(v), v); } - const g = exports.Greeter.init("John"); + const g = new exports.Greeter("John"); assert.equal(g.greet(), "Hello, John!"); g.changeName("Jane"); assert.equal(g.greet(), "Hello, Jane!"); From 87f0a4de2ffc06a18ad3ee516123cd86c7aaa610 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 14:57:30 +0900 Subject: [PATCH 3/3] Revert "BridgeJS: Update examples and documentation for `@JS init`" This reverts commit ed482fe4630e4733b3f654fda6eb3095bddb449d. --- Examples/ExportSwift/index.js | 2 +- Examples/PlayBridgeJS/Sources/JavaScript/app.js | 4 ++-- .../Articles/Exporting-Swift-to-JavaScript.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/ExportSwift/index.js b/Examples/ExportSwift/index.js index fcf7c983..4c5576b2 100644 --- a/Examples/ExportSwift/index.js +++ b/Examples/ExportSwift/index.js @@ -2,7 +2,7 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({}); const Greeter = exports.Greeter; -const greeter = Greeter.init("World"); +const greeter = new Greeter("World"); const circle = exports.renderCircleSVG(100); // Display the results diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 30179791..b14db79b 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -52,7 +52,7 @@ export class BridgeJSPlayground { createTS2Skeleton: this.createTS2Skeleton } }); - this.playBridgeJS = exports.PlayBridgeJS.init(); + this.playBridgeJS = new exports.PlayBridgeJS(); console.log('BridgeJS initialized successfully'); } catch (error) { console.error('Failed to initialize BridgeJS:', error); @@ -162,4 +162,4 @@ export class BridgeJSPlayground { hideError() { this.errorDisplay.classList.remove('show'); } -} +} \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md index 6de7db0b..6ce30772 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md @@ -133,7 +133,7 @@ In JavaScript: import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({}); -const cart = exports.ShoppingCart.init(); +const cart = new exports.ShoppingCart(); cart.addItem("Laptop", 999.99, 1); cart.addItem("Mouse", 24.99, 2); console.log(`Items in cart: ${cart.getItemCount()}`); @@ -158,7 +158,7 @@ export interface ShoppingCart extends SwiftHeapObject { export type Exports = { ShoppingCart: { - init(): ShoppingCart; + new(): ShoppingCart; } } ``` @@ -175,8 +175,8 @@ You can export functions to specific namespaces by providing a namespace paramet import JavaScriptKit // Export a function to a custom namespace -@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { - return "namespaced" +@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { + return "namespaced" } ```