Skip to content

Commit d35a940

Browse files
committed
BridgeJS: Use @JS types from other modules in the same package
1 parent 8d279a0 commit d35a940

83 files changed

Lines changed: 1380 additions & 175 deletions

File tree

Some content is hidden

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

Benchmarks/Sources/Generated/JavaScript/BridgeJS.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3415,5 +3415,8 @@
34153415
}
34163416
]
34173417
},
3418-
"moduleName" : "Benchmarks"
3418+
"moduleName" : "Benchmarks",
3419+
"usedExternalModules" : [
3420+
3421+
]
34193422
}

Examples/MultiModule/Package.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "MultiModule",
7+
platforms: [
8+
.macOS(.v14)
9+
],
10+
dependencies: [.package(name: "JavaScriptKit", path: "../../")],
11+
targets: [
12+
.target(
13+
name: "Core",
14+
dependencies: [
15+
"JavaScriptKit"
16+
],
17+
swiftSettings: [
18+
.enableExperimentalFeature("Extern")
19+
],
20+
plugins: [
21+
.plugin(name: "BridgeJS", package: "JavaScriptKit")
22+
]
23+
),
24+
.executableTarget(
25+
name: "MultiModule",
26+
dependencies: [
27+
"Core",
28+
"JavaScriptKit",
29+
],
30+
swiftSettings: [
31+
.enableExperimentalFeature("Extern")
32+
],
33+
plugins: [
34+
.plugin(name: "BridgeJS", package: "JavaScriptKit")
35+
]
36+
),
37+
]
38+
)

Examples/MultiModule/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# MultiModule Example
2+
3+
This example demonstrates using `@JS` types defined in one module (`Core`) from another module (`App`) within the same Swift package.
4+
5+
## Building and Running
6+
7+
1. Build the project:
8+
```sh
9+
swift package --swift-sdk $SWIFT_SDK_ID js --use-cdn
10+
```
11+
12+
2. Serve the files:
13+
```sh
14+
npx serve
15+
```
16+
17+
Then open your browser to `http://localhost:3000`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import JavaScriptKit
2+
3+
@JS public struct Vector3D {
4+
public let x: Double
5+
public let y: Double
6+
public let z: Double
7+
8+
@JS public init(x: Double, y: Double, z: Double) {
9+
self.x = x
10+
self.y = y
11+
self.z = z
12+
}
13+
14+
@JS public func magnitude() -> Double {
15+
(x * x + y * y + z * z).squareRoot()
16+
}
17+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Core
2+
import JavaScriptKit
3+
4+
@JS public func currentVelocity() -> Vector3D {
5+
Vector3D(x: 0.1, y: 0.2, z: 0.3)
6+
}

Examples/MultiModule/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>MultiModule Example</title>
6+
</head>
7+
8+
<body>
9+
<script type="module" src="index.js"></script>
10+
</body>
11+
12+
</html>

Examples/MultiModule/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
2+
const { exports } = await init({});
3+
4+
const velocity = exports.currentVelocity();
5+
6+
const output = document.createElement("pre");
7+
output.innerText =
8+
`currentVelocity() = (${velocity.x}, ${velocity.y}, ${velocity.z})\n`
9+
+ `magnitude = ${velocity.magnitude()}`;
10+
document.body.appendChild(output);

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,5 +300,8 @@
300300
}
301301
]
302302
},
303-
"moduleName" : "PlayBridgeJS"
303+
"moduleName" : "PlayBridgeJS",
304+
"usedExternalModules" : [
305+
306+
]
304307
}

Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct BridgeJSBuildPlugin: BuildToolPlugin {
2020

2121
private func createGenerateCommand(context: PluginContext, target: SwiftSourceModuleTarget) throws -> Command {
2222
let outputSwiftPath = context.pluginWorkDirectoryURL.appending(path: "BridgeJS.swift")
23+
let outputSkeletonPath = context.pluginWorkDirectoryURL.appending(path: "JavaScript/BridgeJS.json")
2324

2425
let inputSwiftFiles = target.sourceFiles.filter {
2526
!$0.url.path.hasPrefix(context.pluginWorkDirectoryURL.path + "/")
@@ -63,6 +64,14 @@ struct BridgeJSBuildPlugin: BuildToolPlugin {
6364
])
6465
}
6566

67+
for skeleton in dependencySkeletons(context: context, target: target) {
68+
arguments.append(contentsOf: [
69+
"--dependency-skeleton",
70+
"\(skeleton.moduleName)=\(skeleton.url.path)",
71+
])
72+
inputFiles.append(skeleton.url)
73+
}
74+
6675
let allSwiftFiles = inputSwiftFiles + pluginGeneratedSwiftFiles
6776
arguments.append(contentsOf: allSwiftFiles.map(\.path))
6877

@@ -71,8 +80,47 @@ struct BridgeJSBuildPlugin: BuildToolPlugin {
7180
executable: try context.tool(named: "BridgeJSTool").url,
7281
arguments: arguments,
7382
inputFiles: inputFiles,
74-
outputFiles: [outputSwiftPath]
83+
outputFiles: [outputSwiftPath, outputSkeletonPath]
7584
)
7685
}
86+
87+
private struct DependencySkeleton {
88+
let moduleName: String
89+
let url: URL
90+
}
91+
92+
/// We only read skeletons from dependencies with a `bridge-js.config.json` file.
93+
/// For the build system to correctly order the plugins, we need to set the skeleton
94+
/// files as input. However, I don’t think we have enough information here to determine
95+
/// whether the plugin which generates this is applied to the dependency, so we use
96+
/// the presence of `bridge-js.config.json` instead.
97+
private func dependencySkeletons(
98+
context: PluginContext,
99+
target: SwiftSourceModuleTarget
100+
) -> [DependencySkeleton] {
101+
let localTargets: [SwiftSourceModuleTarget] = target.recursiveTargetDependencies
102+
.compactMap { dependency in
103+
guard
104+
let swiftTarget = dependency as? SwiftSourceModuleTarget,
105+
context.package.targets.contains(where: { $0.id == swiftTarget.id }),
106+
FileManager.default.fileExists(atPath: pathToConfigFile(target: swiftTarget).path)
107+
else {
108+
return nil
109+
}
110+
return swiftTarget
111+
}
112+
113+
var skeletons: [DependencySkeleton] = []
114+
var seenTargetNames = Set<String>()
115+
for swiftTarget in localTargets where seenTargetNames.insert(swiftTarget.name).inserted {
116+
let skeletonURL = BridgeJSPluginPaths.skeletonURL(
117+
targetName: swiftTarget.name,
118+
packageID: context.package.id,
119+
buildPluginWorkDirectoryURL: context.pluginWorkDirectoryURL
120+
)
121+
skeletons.append(DependencySkeleton(moduleName: swiftTarget.name, url: skeletonURL))
122+
}
123+
return skeletons
124+
}
77125
}
78126
#endif

0 commit comments

Comments
 (0)