JXKit
JXKit is a cross-plarform swift module for interfacing with
JavaScriptCore. It provides a fluent API for working with an embedded
JXContext,
including script evaluation, error handling, and Codable mashalling.
JXKit is cross-platform for Darwin (macOS/iOS) and Linux, with experimental support for Windows and Android.
JSC to be used on platforms where the Objective-C runtime is unavailable (e.g., Linux).
API
Browse the API Documentation.
Direct function invocation
Functions can be accessed (and cached) to be invoked directly with codable arguments:
let context = JXContext()
let hypot = try context.global["Math"]["hypot"]
assert(hypot.isFunction == true)
let result = try hypot.call(withArguments: try [context.encode(3), context.encode(4)])
let hypotValue = try result.double
assert(hypotValue == 5.0)Codable passing
JXKit supports encoding and decoding Swift types directly into the JXValue instances, which enables Codable instances to be passed back and forth to the virtual machine with minimal overhead. Since encoding & decoding doesn't use JSON stringify & parse, this can lead to considerable performance improvements when interfacing between Swift & JS.
The above invocation of Math.hypot can instead be performed by wrapping the arguments in an Encodable struct, and returning a Decodable value.
/// An example of invoking `Math.hypot` in a wrapper function that takes an encodable argument and returns a Decodable retult.
struct AB: Encodable { let a, b: Double }
struct C: Decodable { let c: Double }
let context = JXContext()
let hypot = try context.eval("(function(args) { return { c: Math.hypot(args.a, args.b) }; })")
assert(hypot.isFunction == true)
let result: C = try hypot.call(withArguments: [context.encode(AB(a: 3, b: 4))]).toDecodable(ofType: C.self)
assert(result.c == 5)JavaScriptCore Compatibility
The JXKit API is a mostly drop-in replacement for the Objective-C JavaScriptCore framework available on most Apple devices. E.g., the following JavaScriptCore code:
import JavaScriptCore
let jsc = JSContext()
let value: JSValue = jsc.evaluateScript("1+2")
assert(value.int == 3)becomes:
import JXKit
let jxc = JXContext()
let value: JXValue = try jxc.eval("1+2")
assert(try value.int == 3)Installation
Note: Requires Swift 5.5+
Swift Package Manager
The Swift Package Manager is a tool for managing the distribution of Swift code.
- Add the following to your
Package.swiftfile:
// swift-tools-version:5.6
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
.package(name: "JXKit", url: "https://github.com/jectivex/JXKit.git", .upToNextMajor(from: "3.0.0")),
],
targets: [
.target(
name: "MyPackage",
dependencies: ["JXKit"]),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]),
]
)- Build your project:
$ swift buildLicense
Like the JavaScriptCore framework upon which it is built, JXKit is licensed under the GNU LGPL license. See LICENSE.LGPL for details.
Related
Projects that are based on JXKit:
- [Jack][]: Cross-platform framework for scripting
Combine.ObservableObjectand SwiftUI (LGPL)
Dependencies
- JavaScriptCore: Cross-platform JavaScript engine (LGPL)1
TODO
- Consider evalClosureAsync variant for async code.
- Better reporting of errors from async code / Promises.
Footnotes
-
JavaScriptCore is included with macOS and iOS as part of the embedded WebCore framework (LGPL); on Linux JXKit uses WebKit GTK JavaScriptCore. ↩