Skip to content

jectivex/JXKit

Repository files navigation

JXKit

Build Status Swift5 compatible Platform

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.

  1. Add the following to your Package.swift file:
// 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"]),
        ]
    )
  1. Build your project:
$ swift build

License

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.ObservableObject and SwiftUI (LGPL)

Dependencies

TODO

  • Consider evalClosureAsync variant for async code.
  • Better reporting of errors from async code / Promises.

Footnotes

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