Skip to content

Commit 6720ec9

Browse files
committed
BridgeJS: Global namespace configuration and tests
1 parent 64441fa commit 6720ec9

File tree

57 files changed

+673
-413
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+673
-413
lines changed

Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import class Foundation.JSONDecoder
1717
}
1818

1919
func _update(swiftSource: String, dtsSource: String) throws -> PlayBridgeJSOutput {
20-
let exportSwift = ExportSwift(progress: .silent, moduleName: "Playground")
20+
let exportSwift = ExportSwift(progress: .silent, moduleName: "Playground", exposeToGlobal: false)
2121
let sourceFile = Parser.parse(source: swiftSource)
2222
try exportSwift.addSourceFile(sourceFile, "Playground.swift")
2323
let exportResult = try exportSwift.finalize()
@@ -40,8 +40,7 @@ import class Foundation.JSONDecoder
4040
children: [importSkeleton]
4141
)
4242
],
43-
sharedMemory: false,
44-
exposeToGlobal: true
43+
sharedMemory: false
4544
)
4645
let linked = try linker.link()
4746

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ unittest:
1111
--disable-sandbox \
1212
js test --prelude ./Tests/prelude.mjs -Xnode --expose-gc
1313

14+
.PHONY: unittest-with-global
15+
unittest-with-global:
16+
cp Tests/BridgeJSRuntimeTests/bridge-js.config.exposeToGlobal.json Tests/BridgeJSRuntimeTests/bridge-js.config.local.json
17+
swift package --allow-writing-to-directory Tests/BridgeJSRuntimeTests \
18+
bridge-js --package-path Tests/BridgeJSRuntimeTests
19+
rm -f Tests/BridgeJSRuntimeTests/bridge-js.config.local.json
20+
env JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1 swift package --swift-sdk "$(SWIFT_SDK_ID)" \
21+
--disable-sandbox \
22+
js test --prelude ./Tests/prelude.exposeToGlobal.mjs -Xnode --expose-gc
23+
1424
.PHONY: regenerate_swiftpm_resources
1525
regenerate_swiftpm_resources:
1626
npm run build

Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin {
4444
"export",
4545
"--module-name",
4646
target.name,
47+
"--target-dir",
48+
target.directoryURL.path,
4749
"--output-skeleton",
4850
outputSkeletonPath.path,
4951
"--output-swift",

Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ extension BridgeJSCommandPlugin.Context {
107107
"export",
108108
"--module-name",
109109
target.name,
110+
"--target-dir",
111+
target.directoryURL.path,
110112
"--output-skeleton",
111113
generatedJavaScriptDirectory.appending(path: "BridgeJS.ExportSwift.json").path,
112114
"--output-swift",

Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ public struct BridgeJSConfig: Codable {
1010
/// If not present, the tool will be searched for in the system PATH.
1111
public var tools: [String: String]?
1212

13+
/// Whether to expose exported Swift APIs to the global namespace.
14+
///
15+
/// When `true`, exported functions, classes, and namespaces are available
16+
/// via `globalThis` in JavaScript. When `false`, they are only available
17+
/// through the exports object returned by `createExports()`.
18+
///
19+
/// Default: `false`
20+
public var exposeToGlobal: Bool = false
21+
1322
/// Load the configuration file from the SwiftPM package target directory.
1423
///
1524
/// Files are loaded **in this order** and merged (later files override earlier ones):
@@ -49,7 +58,8 @@ public struct BridgeJSConfig: Codable {
4958
/// Merge the current configuration with the overrides.
5059
func merging(overrides: BridgeJSConfig) -> BridgeJSConfig {
5160
return BridgeJSConfig(
52-
tools: (tools ?? [:]).merging(overrides.tools ?? [:], uniquingKeysWith: { $1 })
61+
tools: (tools ?? [:]).merging(overrides.tools ?? [:], uniquingKeysWith: { $1 }),
62+
exposeToGlobal: overrides.exposeToGlobal
5363
)
5464
}
5565
}

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import BridgeJSUtilities
2020
public class ExportSwift {
2121
let progress: ProgressReporting
2222
let moduleName: String
23-
23+
private let exposeToGlobal: Bool
2424
private var exportedFunctions: [ExportedFunction] = []
2525
private var exportedClasses: [ExportedClass] = []
2626
private var exportedEnums: [ExportedEnum] = []
@@ -30,9 +30,10 @@ public class ExportSwift {
3030
private let enumCodegen: EnumCodegen = EnumCodegen()
3131
private let closureCodegen = ClosureCodegen()
3232

33-
public init(progress: ProgressReporting, moduleName: String) {
33+
public init(progress: ProgressReporting, moduleName: String, exposeToGlobal: Bool) {
3434
self.progress = progress
3535
self.moduleName = moduleName
36+
self.exposeToGlobal = exposeToGlobal
3637
}
3738

3839
/// Processes a Swift source file to find declarations marked with @JS
@@ -55,6 +56,8 @@ public class ExportSwift {
5556

5657
/// Finalizes the export process and generates the bridge code
5758
///
59+
/// - Parameters:
60+
/// - exposeToGlobal: Whether to expose exported APIs to the global namespace (default: false)
5861
/// - Returns: A tuple containing the generated Swift code and a skeleton
5962
/// describing the exported APIs
6063
public func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? {
@@ -68,7 +71,8 @@ public class ExportSwift {
6871
functions: exportedFunctions,
6972
classes: exportedClasses,
7073
enums: exportedEnums,
71-
protocols: exportedProtocols
74+
protocols: exportedProtocols,
75+
exposeToGlobal: exposeToGlobal
7276
)
7377
)
7478
}

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,26 @@ struct BridgeJSLink {
1212
var exportedSkeletons: [ExportedSkeleton] = []
1313
var importedSkeletons: [ImportedModuleSkeleton] = []
1414
let sharedMemory: Bool
15-
let exposeToGlobal: Bool
15+
var exposeToGlobal: Bool
1616
private let namespaceBuilder = NamespaceBuilder()
1717

1818
init(
1919
exportedSkeletons: [ExportedSkeleton] = [],
2020
importedSkeletons: [ImportedModuleSkeleton] = [],
21-
sharedMemory: Bool,
22-
exposeToGlobal: Bool = true
21+
sharedMemory: Bool
2322
) {
2423
self.exportedSkeletons = exportedSkeletons
2524
self.importedSkeletons = importedSkeletons
2625
self.sharedMemory = sharedMemory
27-
self.exposeToGlobal = exposeToGlobal
26+
self.exposeToGlobal = exportedSkeletons.contains { $0.exposeToGlobal }
2827
}
2928

3029
mutating func addExportedSkeletonFile(data: Data) throws {
3130
let skeleton = try JSONDecoder().decode(ExportedSkeleton.self, from: data)
3231
exportedSkeletons.append(skeleton)
32+
if skeleton.exposeToGlobal {
33+
exposeToGlobal = true
34+
}
3335
}
3436

3537
mutating func addImportedSkeletonFile(data: Data) throws {
@@ -80,7 +82,7 @@ struct BridgeJSLink {
8082
var enumStaticAssignments: [String] = []
8183
}
8284

83-
private func collectLinkData(exposeToGlobal: Bool) throws -> LinkData {
85+
private func collectLinkData() throws -> LinkData {
8486
var data = LinkData()
8587

8688
// Swift heap object class definitions
@@ -1079,11 +1081,11 @@ struct BridgeJSLink {
10791081
)
10801082
printer.write(lines: namespaceInitCode)
10811083

1082-
let propertyAssignments = try generateNamespacePropertyAssignments(
1083-
data: data,
1084-
exportedSkeletons: exportedSkeletons,
1085-
namespaceBuilder: namespaceBuilder,
1086-
exposeToGlobal: exposeToGlobal
1084+
let propertyAssignments = try generateNamespacePropertyAssignments(
1085+
data: data,
1086+
exportedSkeletons: exportedSkeletons,
1087+
namespaceBuilder: namespaceBuilder,
1088+
exposeToGlobal: exposeToGlobal
10871089
)
10881090
printer.write(lines: propertyAssignments)
10891091
}
@@ -1097,7 +1099,7 @@ struct BridgeJSLink {
10971099
}
10981100

10991101
func link() throws -> (outputJs: String, outputDts: String) {
1100-
let data = try collectLinkData(exposeToGlobal: exposeToGlobal)
1102+
let data = try collectLinkData()
11011103
let outputJs = try generateJavaScript(data: data)
11021104
let outputDts = generateTypeScript(data: data)
11031105
return (outputJs, outputDts)
@@ -1160,7 +1162,6 @@ struct BridgeJSLink {
11601162
) throws -> [String] {
11611163
let printer = CodeFragmentPrinter()
11621164

1163-
// Only write globalThis property assignments when exposeToGlobal is true
11641165
if exposeToGlobal {
11651166
printer.write(lines: data.enumStaticAssignments)
11661167
}
@@ -1171,7 +1172,6 @@ struct BridgeJSLink {
11711172

11721173
let hierarchicalLines = try namespaceBuilder.buildHierarchicalExportsObject(
11731174
exportedSkeletons: exportedSkeletons,
1174-
exposeToGlobal: exposeToGlobal,
11751175
renderFunctionImpl: { function in
11761176
let (js, _) = try self.renderExportedFunction(function: function)
11771177
return js
@@ -2478,7 +2478,6 @@ extension BridgeJSLink {
24782478

24792479
fileprivate func buildHierarchicalExportsObject(
24802480
exportedSkeletons: [ExportedSkeleton],
2481-
exposeToGlobal: Bool,
24822481
renderFunctionImpl: (ExportedFunction) throws -> [String]
24832482
) throws -> [String] {
24842483
let printer = CodeFragmentPrinter()
@@ -2698,7 +2697,7 @@ extension BridgeJSLink {
26982697
guard hasContent(node: childNode) else {
26992698
continue
27002699
}
2701-
2700+
27022701
let exportKeyword = exposeToGlobal ? "" : "export "
27032702
printer.write("\(exportKeyword)namespace \(childName) {")
27042703
printer.indent()

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,19 +499,27 @@ public struct ExportedSkeleton: Codable {
499499
public let classes: [ExportedClass]
500500
public let enums: [ExportedEnum]
501501
public let protocols: [ExportedProtocol]
502+
/// Whether to expose exported APIs to the global namespace.
503+
///
504+
/// When `true`, exported functions, classes, and namespaces are available
505+
/// via `globalThis` in JavaScript. When `false`, they are only available
506+
/// through the exports object.
507+
public var exposeToGlobal: Bool
502508

503509
public init(
504510
moduleName: String,
505511
functions: [ExportedFunction],
506512
classes: [ExportedClass],
507513
enums: [ExportedEnum],
508-
protocols: [ExportedProtocol] = []
514+
protocols: [ExportedProtocol] = [],
515+
exposeToGlobal: Bool
509516
) {
510517
self.moduleName = moduleName
511518
self.functions = functions
512519
self.classes = classes
513520
self.enums = enums
514521
self.protocols = protocols
522+
self.exposeToGlobal = exposeToGlobal
515523
}
516524
}
517525

Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ import TS2Skeleton
162162
help: "The name of the module for external function references",
163163
required: true
164164
),
165+
"target-dir": OptionRule(
166+
help: "The SwiftPM package target directory",
167+
required: true
168+
),
165169
"output-skeleton": OptionRule(
166170
help: "The output file path for the skeleton of the exported Swift APIs",
167171
required: true
@@ -181,7 +185,13 @@ import TS2Skeleton
181185
arguments: Array(arguments.dropFirst())
182186
)
183187
let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true")
184-
let exporter = ExportSwift(progress: progress, moduleName: doubleDashOptions["module-name"]!)
188+
let targetDirectory = URL(fileURLWithPath: doubleDashOptions["target-dir"]!)
189+
let config = try BridgeJSConfig.load(targetDirectory: targetDirectory)
190+
let exporter = ExportSwift(
191+
progress: progress,
192+
moduleName: doubleDashOptions["module-name"]!,
193+
exposeToGlobal: config.exposeToGlobal
194+
)
185195
for inputFile in positionalArguments.sorted() {
186196
let sourceURL = URL(fileURLWithPath: inputFile)
187197
guard sourceURL.pathExtension == "swift" else { continue }

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,12 @@ import Testing
4747
func snapshotExport(input: String) throws {
4848
let url = Self.inputsDirectory.appendingPathComponent(input)
4949
let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8))
50-
let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule")
50+
let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule", exposeToGlobal: false)
5151
try swiftAPI.addSourceFile(sourceFile, input)
5252
let name = url.deletingPathExtension().lastPathComponent
5353

5454
let (_, outputSkeleton) = try #require(try swiftAPI.finalize())
55-
let encoder = JSONEncoder()
56-
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
57-
let outputSkeletonData = try encoder.encode(outputSkeleton)
58-
var bridgeJSLink = BridgeJSLink(sharedMemory: false, exposeToGlobal: true)
59-
try bridgeJSLink.addExportedSkeletonFile(data: outputSkeletonData)
55+
let bridgeJSLink: BridgeJSLink = BridgeJSLink(exportedSkeletons: [outputSkeleton], sharedMemory: false)
6056
try snapshot(bridgeJSLink: bridgeJSLink, name: name + ".Export")
6157
}
6258

@@ -74,7 +70,7 @@ import Testing
7470
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
7571
let outputSkeletonData = try encoder.encode(importTS.skeleton)
7672

77-
var bridgeJSLink = BridgeJSLink(sharedMemory: false, exposeToGlobal: true)
73+
var bridgeJSLink = BridgeJSLink(sharedMemory: false)
7874
try bridgeJSLink.addImportedSkeletonFile(data: outputSkeletonData)
7975
try snapshot(bridgeJSLink: bridgeJSLink, name: name + ".Import")
8076
}
@@ -83,45 +79,16 @@ import Testing
8379
"Namespaces.swift",
8480
"StaticFunctions.swift",
8581
"StaticProperties.swift",
86-
"EnumNamespace.swift"
82+
"EnumNamespace.swift",
8783
])
88-
func testWithoutGlobal(inputFile: String) throws {
84+
func snapshotExportWithGlobal(inputFile: String) throws {
8985
let url = Self.inputsDirectory.appendingPathComponent(inputFile)
9086
let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8))
91-
let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule")
87+
let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule", exposeToGlobal: true)
9288
try swiftAPI.addSourceFile(sourceFile, inputFile)
93-
89+
let name = url.deletingPathExtension().lastPathComponent
9490
let (_, outputSkeleton) = try #require(try swiftAPI.finalize())
95-
let encoder = JSONEncoder()
96-
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
97-
let outputSkeletonData = try encoder.encode(outputSkeleton)
98-
99-
var bridgeJSLink = BridgeJSLink(sharedMemory: false, exposeToGlobal: false)
100-
try bridgeJSLink.addExportedSkeletonFile(data: outputSkeletonData)
101-
102-
let (outputJs, outputDts) = try bridgeJSLink.link()
103-
104-
// Verify no global declarations
105-
#expect(!outputDts.contains("declare global"))
106-
#expect(!outputJs.contains("globalThis."))
107-
108-
// Save snapshots
109-
let name = url.deletingPathExtension().lastPathComponent + "_NoGlobal.Export"
110-
try assertSnapshot(
111-
name: name,
112-
filePath: #filePath,
113-
function: #function,
114-
sourceLocation: #_sourceLocation,
115-
input: outputJs.data(using: .utf8)!,
116-
fileExtension: "js"
117-
)
118-
try assertSnapshot(
119-
name: name,
120-
filePath: #filePath,
121-
function: #function,
122-
sourceLocation: #_sourceLocation,
123-
input: outputDts.data(using: .utf8)!,
124-
fileExtension: "d.ts"
125-
)
91+
let bridgeJSLink: BridgeJSLink = BridgeJSLink(exportedSkeletons: [outputSkeleton], sharedMemory: false)
92+
try snapshot(bridgeJSLink: bridgeJSLink, name: name + ".Global.Export")
12693
}
12794
}

0 commit comments

Comments
 (0)