Skip to content

Instantly share code, notes, and snippets.

@pbk20191
Created November 26, 2025 01:17
Show Gist options
  • Select an option

  • Save pbk20191/1cc3c6b3c1f58422d8a20f59f8690eab to your computer and use it in GitHub Desktop.

Select an option

Save pbk20191/1cc3c6b3c1f58422d8a20f59f8690eab to your computer and use it in GitHub Desktop.
import JavaScriptCore
@objc(JSModuleLoaderDelegate)
public protocol JSModuleLoaderDelegate: NSObjectProtocol {
@objc(context:fetchModuleForIdentifier:withResolveHandler:andRejectHandler:)
func context(_ context: JSContext, fetchModuleFor identifier: JSValue, withResolveHandler: JSValue, andRejectHandler: JSValue)
@objc(willEvaluateModule:)
optional func willEvaluateModule(_ key: URL)
@objc(didEvaluateModule:)
optional func didEvaluateModule(_ key: URL)
}
class DefaultJSCModuleLoader: NSObject, JSModuleLoaderDelegate {
func context(_ context: JSContext, fetchModuleFor identifier: JSValue, withResolveHandler resolve: JSValue, andRejectHandler reject: JSValue) {
RunLoop.current.perform {
do {
let moduleScript = try JSScript_Proxy(
type: .module,
with: identifier.toString(),
and: URL(string: identifier.toString())!,
nil,
inVirtualMachine: context.virtualMachine
)
let evalResult = context.evaluateJSScript(moduleScript.jsScript)
resolve.call(withArguments: [evalResult])
} catch let error as NSError {
reject.call(withArguments: [
JSValue(newErrorFromMessage: error.localizedDescription, in: context)!])
}
}
}
}
extension JSContext {
@NSManaged var _remoteInspectionEnabled:Bool
@NSManaged var _includesNativeCallStackWhenReportingExceptions:Bool
@NSManaged var _debuggerRunLoop:CFRunLoop?
@NSManaged weak var moduleLoaderDelegate:JSModuleLoaderDelegate?
@objc(evaluateJSScript:)
@NSManaged func evaluateJSScript(_ script: NSObject) -> JSValue
@objc(dependencyIdentifiersForModuleJSScript:)
@NSManaged func dependencyIdentifiersForModuleJSScript(_ script: NSObject) -> JSValue
}
class JSScript_Proxy: NSObject {
private static let clazz:NSObject.Type = {
let _ = JSVirtualMachine.self
return NSClassFromString("JSScript")! as! NSObject.Type
}()
let jsScript:NSObject
@objc(JSScriptType) enum JSScriptType:NSInteger {
@objc(kJSScriptTypeProgram)
case program
@objc(kJSScriptTypeModule)
case module
}
@objc(cacheBytecodeWithError:)
@NSManaged func cacheBytecode() throws
@objc(isUsingBytecodeCache)
@NSManaged func isUsingBytecodeCache() -> Bool
@objc(type)
@NSManaged func type() -> JSScriptType
@objc(sourceURL)
@NSManaged func sourceURL() -> URL
@objc(forwardingTargetForSelector:)
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return jsScript
}
private class ScriptInitializer_proxy {
let inner = JSScript_Proxy.clazz
@objc(forwardingTargetForSelector:)
func forwardingTarget(for aSelector: Selector!) -> Any? {
return inner
}
@objc(scriptOfType:withSource:andSourceURL:andBytecodeCache:inVirtualMachine:error:)
@NSManaged func script(of type:JSScriptType, with source:String, and sourceURL:URL, andBytecodeCache cachePath:URL?, inVirtualMachine vm:JSVirtualMachine) throws -> NSObject
@objc(scriptOfType:memoryMappedFromASCIIFile:withSourceURL:andBytecodeCache:inVirtualMachine:error:)
@NSManaged func script(of type:JSScriptType, memoryMappedFromASCIIFile filePath:URL, with sourceURL:URL, andBytecodeCache cachePath:URL?, inVirtualMachine vm:JSVirtualMachine) throws -> NSObject
}
/// Create a JSScript for the specified virtual machine.
/// - returns: The new script.
/// - throws: A description of why the script could not be created if the result is nil.
/// - Parameters:
/// - type: The type of JavaScript source.
/// - source: The source code to use when the script is evaluated by the JS vm.
/// - sourceURL: The source URL to associate with this script. For modules, this is the module identifier.
/// - cachePath: A URL containing the path where the VM should cache for future execution. On creation, we use this path to load the cached bytecode off disk. If the cached bytecode at this location is stale, you should delete that file before calling this constructor.
/// - vm: The JSVirtualMachine the script can be evaluated in.
///
/// - The file at cachePath should not be externally modified for the lifecycle of vm.
init(
type: JSScriptType,
with source:String,
and sourceURL:URL,
_ cachePath:URL? = nil,
inVirtualMachine vm:JSVirtualMachine
) throws {
self.jsScript = try ScriptInitializer_proxy().script(of: type, with: source, and: sourceURL, andBytecodeCache: cachePath, inVirtualMachine: vm)
}
/// Create a JSScript for the specified virtual machine with a path to a codesigning and bytecode caching.
/// - returns: The new script.
/// - throws: A description of why the script could not be created if the result is nil.
/// - Parameters:
/// - type: The type of JavaScript source.
/// - filePath: A URL containing the path to a JS source code file on disk.
/// - sourceURL: The source URL to associate with this script. For modules, this is the module identifier.
/// - cachePath: A URL containing the path where the VM should cache for future execution. On creation, we use this path to load the cached bytecode off disk. If the cached bytecode at this location is stale, you should delete that file before calling this constructor.
/// - vm: The JSVirtualMachine the script can be evaluated in.
///
/// - The files at filePath and cachePath should not be externally modified for the lifecycle of vm.
/// - This method will file back the memory for the source. If the file at filePath is not ascii this method will return nil.
init(
type:JSScriptType,
filePath:URL,
sourceURL:URL,
_ cachePath:URL? = nil,
inVirtualMachine vm:JSVirtualMachine
) throws {
self.jsScript = try ScriptInitializer_proxy().script(of: type, memoryMappedFromASCIIFile: filePath, with: sourceURL, andBytecodeCache: cachePath, inVirtualMachine: vm)
}
init(
_ unsafe:NSObject
) {
precondition(unsafe.isKind(of: Self.clazz))
self.jsScript = unsafe
}
override func isEqual(_ object: Any?) -> Bool {
if (object as? NSObject) == jsScript {
return true
}
if let k = object as? JSScript_Proxy {
return k.jsScript === jsScript
}
return false
}
override var hash: Int {
return jsScript.hash
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment