Skip to content
/ Jack Public

A cross-platform framework for scripting SwiftUI & ObservableObjects with JavaScript

License

Notifications You must be signed in to change notification settings

aabewhite/Jack

Repository files navigation

Jack

Build Status Swift5 compatible Platform

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

Installation

Swift Package Manager

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")
]

Communication

See the planning document for a roadmap and existing feature requests.

License

Like the JKit and JavaScriptCore frameworks upon which it is built, Jack is licensed under the GNU LGPL license. See LICENSE.LGPL for details.

Dependencies

  • JXKit Cross-platform Swift interface to JavaScriptCore (LGPL)
  • JavaScriptCore: Cross-platform JavaScript engine (LGPL)1
  • OpenCombine Cross-platform Combine implementation (MIT)

Footnotes

  1. JavaScriptCore is included with macOS and iOS as part of the embedded WebCore framework (LGPL); on Linux JXKit uses WebKit GTK JavaScriptCore.

About

A cross-platform framework for scripting SwiftUI & ObservableObjects with JavaScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages