Back to Blog

JavaScriptCore Cross-Platform Development

26
Jan
2024
Development
Cross-Platform Development with JavaScriptCore

JavaScriptCore is a powerful framework that allows you to run JavaScript code on MacOS and iOS devices. It is the engine behind Safari, WebKit, and many other web technologies apps.

But did you know you can also use JavaScriptCore to create cross-platform applications that leverage native features and performance? This blog post will explore how JavaScriptCore source code works, its benefits and limitations, and how you can build amazing cross-platform software.

What is Cross Platform Development?

Cross-Platform development is developing software products that work on multiple platforms instead of creating a service that works on a single platform. For example, a cross-platform app can run on Android, iOS, Windows, and other operating systems with minimal or no changes in the code.

This approach can reduce time to market and the resources developers need to provide a consistent User Experience (UX) across different devices, from desktop to mobile platforms.

There are several tools and cross-platform frameworks to help developers, including Flutter, React Native, Ionic, Xamarin, and Kotlin Multiplatform. Each tool has major advantages and disadvantages, depending on the project requirements, the Programming Language, the performance, the User Interface (UI), and the support.

How Does Cross Platform Development Work?

Devs use cross-platform development to create software that can run on different platforms or devices, like Android, iOS, Windows, Mac, Linux, and Web Browsers. But how can developers write one app that can run on so many different systems without having to rewrite the code for each one?

The key is that developers use a common language or framework that a cross-platform development tool can translate or compile into native code for each platform.

For example, developers use Dart, a programming language that Flutter, a cross-platform development tool, can compile into native code for Android and iOS or transpile into JavaScript for web browsers. Developers also use JavaScript frameworks like React and libraries like React Native.

By providing a layer of abstraction, cross-platform development tools can also handle the differences between platforms, such as screen sizes, input methods, hardware features, and operating system APIs.

Developers use web technologies such as HTML, CSS, and JavaScript, and Ionic, a cross-platform development tool, wraps them into a native container that can access the native features of each platform.

Teams also use C#, a programming language that Xamarin, another cross-platform development tool, can compile into native code for Android, iOS, Windows, and Mac and use a common Application Programming Interface (API) that can access the native features of each platform.

What is JavaScriptCore?

JavaScriptCore is the built-in JavaScript engine for WebKit, which implements ECMAScript as in the ECMA-262 specification. Apple originally developed it in 1998 as part of their Mac OS X operating system. It later became part of the open-source WebKit project in 2005.

JavaScriptCore is often referred to by different names, such as SquirrelFish and SquirrelFish Extreme, which are the code names for the major revisions of the engine over the years. 

Within the context of Safari, Nitro and Nitro Extreme are also commonly used, which are the marketing terms from Apple for the native-like performance improvements of JavaScriptCore. However, the name of the project and the library is always JavaScriptCore.

Requisites to Use JavaScriptCore

Before using JavaScriptCore to create cross-platform apps, you must have some prerequisites. These include:

JavaScript Knowledge: You don't need to be an expert. Still, you should be familiar with the core concepts and features of the language, such as variables, functions, objects, syntax, arrays, loops, promises, and wrapper functions.

Bun Working Installation: This fast and modern JavaScript runtime environment extends JavaScriptCore with its native components, functionalities, and tools. You can install Bun with this simple command: curl -fsSL <https://bun.sh/install> | bash. Bun will also install JavaScriptCore for you, so you don't need to worry about that.

Code Editor. You can use any editor that supports JavaScript and TypeScript, such as Visual Studio Code, Atom, Sublime Text, etc. You might also want to install extensions or plugins that enhance your coding experience, such as syntax highlighting, code formatting, linting, etc.

Device/Emulator. You can use any device that supports mobile operating systems like iOS (iPhone and iPad) and Android and desktop OS like Windows (Android phone, tablet, and PC) to run and test your apps. You can also use an emulator or simulator to run your apps on your computer, such as Xcode, Android Studio, etc.

How Does JavaScriptCore Work?

JavaScriptCore is a framework that enables cross-platform development with JavaScript. But how does it work under the hood? Let's take a look at the core components of JavaScriptCore and how they interact with each other. It is an optimizing virtual machine, which means it can execute JavaScript code faster and more efficiently by applying various techniques. JavaScriptCore consists of the following building blocks:

1. Lexer: Lexer is responsible for the lexical analysis, i.e., breaking down the script source into a series of tokens, such as keywords, identifiers, operators, literals, etc.

2. Parser: The parser carries out the syntactic analysis, i.e., consuming the tokens from the lexer and building the corresponding syntax tree, representing the code's structure and meaning.

3. Start-Up Interpreter (LLInt): LLInt executes the bytecodes the parser produces. Offlineasm, a portable assembly that can compile to x86, ARMv7, and C, writes the LLInt. The LLInt aims for no start-up cost besides lexing and parsing while following the just-in-time compilers' calling, stack, and register conventions.

4. Baseline JIT: Baseline JIT kicks in for functions invoked at least six times or takes a loop thousands of times faster. The Baseline JIT compiles the bytecodes to native code, which can run more quickly than the LLInt. The Baseline JIT also performs sophisticated polymorphic inline caching for almost all heap accesses, which means it can optimize the access to object properties and methods based on the types of the objects.

5. Low-Latency JIT Optimization (DFG): DFG is an advanced compiler that uses speculative execution to optimize the code based on the profiling information collected by the LLInt and the Baseline JIT. The DFG can perform aggressive optimizations such as constant folding, dead code elimination, loop invariant code motion, etc. The DFG can also handle complex data structures such as arrays, strings, and typed arrays.

6. High-Throughput JIT Optimization (FTL): FTL advanced compiler uses the LLVM infrastructure to generate highly optimized machine code. The FTL can perform even more optimizations than the DFG, such as vectorization, register allocation, instruction scheduling, etc. The FTL can also leverage the hardware features of the target platform, such as SIMD instructions, floating-point operations, etc.

Why Use JavaScriptCore for Cross Platform Development?

JavaScriptCore works by providing a C API that you can use to create and manipulate JavaScript values, contexts, and virtual machines. A JavaScript value represents any JavaScript data type, such as a number, a string, a function, or an object.

A JavaScript context is an execution environment containing the global object and other variables. A JavaScript virtual machine is a self-contained environment that manages memory and garbage collection for multiple contexts.

You can use JavaScriptCore to evaluate JavaScript code from within your native app and access the results as JavaScript values. You can also use JavaScriptCore to create custom objects to expose to the JavaScript environment and vice versa.

For example, you can create a native object that implements a JSExport protocol and pass it to a JavaScript context. Then, you can access the properties and methods of that object from JavaScript code as if it were a regular JavaScript object.

Similarly, you can create a JavaScript object, pass it to a native function, and access its properties and methods from native code as if it were a regular native language object.

JavaScriptCore can also handle the differences between platforms, such as screen sizes, input methods, hardware features, and operating system APIs, by providing a layer of abstraction.

For example, you can use JavaScriptCore to create a cross-platform UI framework to render native applications for iOS and macOS or web components for web browsers.

You can also use JavaScriptCore to create a cross-platform game engine using native graphics and audio APIs for each platform. Here is an example of JavaScriptCore in Cross-Platform Development with Swift:

// Import the JavaScriptCore framework
import JavaScriptCore

// Create a JSContext instance
let context = JSContext()

// Evaluate some JavaScript code
let result = context.evaluateScript("Math.sqrt(16)")

// Convert the result to a Swift value
let number = result?.toNumber()
print(number) // 4

// Create a native object that conforms to JSExport protocol
@objc protocol GreeterJSExports: JSExport {
 func greet(name: String) -> String
}

// Implement the protocol
@objc class Greeter: NSObject, GreeterJSExports {
 func greet(name: String) -> String {
  return "Hello, \(name)!"
  }
 }

// Create an instance of the native object
let greeter = Greeter()

// Pass the native object to the JavaScript context
context.setObject(greeter, forKeyedSubscript: "greeter" as NSString)

// Call the native object's method from JavaScript code
context.evaluateScript("greeter.greet('Alice')" ) // Hello, Alice!

Conclusions

Cross-platform app development with JavaScriptcore can offer a practical solution to speed up development time, lower maintenance costs, and create a more consistent UX. However, it can also pose drawbacks to native development, such as lower performance, compatibility or performance issues, platform limitations, vulnerable calls, and design challenges.

JavaScriptCore allows you to bring the world of JavaScript to Safari, App Store, and MacOS. Therefore, you need to choose the best option for your project goals and requirements and weigh the pros and cons of each tool and platform.