Jack is a cross-platform framework that enables you to export properties of your Swift classes to an embedded JavaScript environment, enabling your app to provide scriptable extensions.
import Jack
class AppleJack : JackedObject {
@Stack var name: String // exports the property to JS and acts as Combine.Published
@Stack var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
/// Functions are exported as method properties, and can be re-named for export
@Jack("haveBirthday") var _haveBirthday = haveBirthday
func haveBirthday() -> Int {
age += 1
return age
}
static func demo() throws {
let aj = AppleJack(name: "Jack Appleseed", age: 24)
let jxc = try aj.jack().context
let namejs = try jxc.eval("name").stringValue
assert(namejs == aj.name)
let agejs = try jxc.eval("age").int
assert(agejs == aj.age)
assert(aj.haveBirthday() == 25) // direct Swift call
let newAge = try jxc.eval("haveBirthday()").int // scripted method invocation
assert(newAge == 26)
assert(aj.age == 26)
}
}
Browse the API Documentation.
Jack uses JXKit to provide a simple way to export your Swift properties and functions to an embedded JavaScript context.
The framework is cross-platform (iOS/macOS/tvOS/Linux) and can be used to export Swift instances to a scripting envrionment.
This framework integrates transparently with SwiftUI's EnvironmentObject
pattern, and so can be used to enhance existing ObservableObject
instances with scriptable app-specific plug-ins.
Consider an example ping-pong game between Swift and JavaScript:
import Jack
// A standard Combine-based ObservableObject
class PingPongNative : ObservableObject {
@Published var score = 0
/// - Returns: true if a point was scored
func ping() -> Bool {
if Bool.random() == true {
self.score += 1
return true // score
} else {
return false // returned
}
}
}
// An enhanced scriptable ObservableObject
class PingPongScripted : JackedObject {
@Stack var score = 0
/// - Returns: true if a point was scored
func pong() throws -> Bool {
// evaluate the javascript with "score" as a readable/writable property
try jsc.env.eval("Math.random() > 0.5 ? this.score += 1 : false").booleanValue
}
}
let playerA = PingPongNative()
let playerB = PingPongScripted()
var server: any ObservableObject = Bool.random() ? playerA : playerB
let announcer = playerA.$score.combineLatest(playerB.$score).sink { scoreA, scoreB in
print("SCORE:", scoreA, scoreB, "Serving:", server === playerA ? "SWIFT" : "JAVASCRIPT")
}
while playerA.score < 21 && playerB.score < 21 {
if server === playerA {
while try !playerA.ping() && !playerB.pong() { continue }
} else if server === playerB {
while try !playerB.pong() && !playerA.ping() { continue }
}
if (playerA.score + playerB.score) % 5 == 0 {
print("Switching Servers")
server = server === playerA ? playerB : playerA
}
}
print("Winner: ", playerA.score > playerB.score ? "Swift" : "JavaScript")
_ = announcer // no longer needed
An excerpt from a game might be appear as:
SCORE: 0 0 Serving: JAVASCRIPT
SCORE: 1 0 Serving: JAVASCRIPT
Switching Servers
SCORE: 2 3 Serving: SWIFT
SCORE: 3 6 Serving: SWIFT
SCORE: 3 7 Serving: SWIFT
Switching Servers
SCORE: 3 7 Serving: JAVASCRIPT
SCORE: 4 11 Serving: JAVASCRIPT
SCORE: 13 20 Serving: JAVASCRIPT
SCORE: 13 21 Serving: JAVASCRIPT
Winner: JavaScript
The Swift Package Manager is a tool for managing the distribution of Swift code.
Add the following to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/jectivex/Jack", from: "1.0.0")
]
See the planning document for a roadmap and existing feature requests.
- Need help or have a general question? Ask on Stack
Overflow (tag
swiftjack
). - Found a bug or have a feature request? Open an issue.
- Want to contribute? Submit a pull request.
Like the JKit and JavaScriptCore frameworks upon which it is built, Jack is licensed under the GNU LGPL license. See LICENSE.LGPL for details.
- JXKit Cross-platform Swift interface to JavaScriptCore (LGPL)
- JavaScriptCore: Cross-platform JavaScript engine (LGPL)1
- OpenCombine Cross-platform Combine implementation (MIT)
Footnotes
-
JavaScriptCore is included with macOS and iOS as part of the embedded WebCore framework (LGPL); on Linux JXKit uses WebKit GTK JavaScriptCore. ↩