How to import modules in Swift's JavaScriptCore?

swift modules
import statement in swift
swift _exported
swift modules list
swift submodule
swift import struct
swift import struct from another file
swift import framework

I'm trying to use Swift's JavaScriptCore framework to take advantage of an existing JavaScript library that uses ES6 modules. Specifically, morse-pro by Stephen C Phillips. I've added the files to an Xcode playground, then used this code to load the library and run it in the JavaScript context:

import JavaScriptCore
var jsContext = JSContext()
// set up exception handler for javascript errors
jsContext?.exceptionHandler = { context, exception in
    if let exc = exception {
        print("JS Exception:", exc.toString())
    }
}
// read the javascript files in and evaluate
if let jsSourcePath = Bundle.main.path(forResource: "morse-pro-master/src/morse-pro-message", ofType: "js") {
    do {
        let jsSourceContents = try String(contentsOfFile: jsSourcePath)
        jsContext?.evaluateScript(jsSourceContents)
    } catch {
        print(error.localizedDescription)
    }
}

This approach works fine with simple "Hello world" sort of tests, but it chokes on the morse-pro library with this JavaScript error:

SyntaxError: Unexpected token '*'. import call expects exactly one argument.

The error appears to be caused by this line in morse-pro-message.js:

import * as Morse from './morse-pro';

which I believe is trying to import all the morse-pro files as a module.

I'm not familiar with ES6 modules, but the library appears to be working for others in normal JavaScript contexts. Is there something wrong with the way I'm loading the library in Swift? Or are modules a feature that JavaScriptCore doesn't support? (The documentation just says it supports "JavaScript" and doesn't get any more specific.)

I would appreciate any suggestions that point me in the direction of getting this library running in a JavaScriptCore VM.

After much bumbling around in the dark, I found a way to make the library available to Swift without having to manually alter it.

First, as @estus suggested, I installed the library using NPM, which converts it to ES5 but does not resolve the dependencies. So it's still a bunch of separate files that call each other with require and export keywords that neither browsers nor JavaScriptCore understand.

Then I used Browserify to bundle all the dependencies into a single file so that JavaScriptCore could understand it. The normal operation of Browserify hides all the code, so I used the "--standalone" flag to tell it to make marked functions available. If you export the ES5 file directly, it creates a generic object and puts your exported functions under .default. I want them slightly more accessible, so I created a new file to list the exports and then ran Browserify on that. So for example, a file called "morse-export.js" containing:

module.exports.MorseMessage = require('./lib/morse-pro-message.js').default;

Then I run Browserify on it like this:

browserify ./morse-export.js --standalone Morse > ./morse-bundle.js

And include the morse-bundle.js file in my Swift code using Bundle.main.path(forResource). Now I can access the MorseMessage class using Morse.MorseMessage, so back in Swift:

jsContext?.evaluateScript("var morseMessage = new Morse.MorseMessage()")
print(jsContext!.evaluateScript("morseMessage.translate('abc')"))

prints ".- -... -.-." as you would expect.

The downside of this is that you have to manually add whatever classes and functions you want exported to your export file. Still, this seems to be the simplest way to do it. If there's a better way, I would love to hear about it!

Swift Imports, How to be a Swift importer (and possibly exporter). In our first example we're just importing the UIKit module as usual — no funny business  How to Import Python Modules in Server Side Swift. let’s take a look at how to import a Python module. Assume that there is a Python script “/tmp/clstest.py”, now I can use Perfect

While using import for modules is not possible.

You can support require('path/filename'); syntax.

This is done by providing require as a function to JS. The import command is (unfortunately) too exotic to implement in the given restrictions of JSContext.

See the following implementations

Objective-C.
@interface MyContext ()
@property JSContext *context;

@implementation MyContext

- (void) setupRequire {
    MyContext * __weak weakSelf = self;

    self.context[@"require"] = ^(NSString *path) {

        path = [weakSelf resolvePath:path];

        if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {
            NSString *message = [NSString stringWithFormat:@"Require: File "%@" does not exist.", path];
            weakSelf.context.exception = [JSValue valueWithNewErrorFromMessage:message inContext:weakSelf.context];
            return;
        }

        [weakSelf loadScript:path];
    };
} 

- (NSString *) resolvePath:(NSString *)path {
    path = path.stringByResolvingSymlinksInPath;
    return path.stringByStandardizingPath;
}

- (void) loadScript:(NSString *)path {
    NSError *error;
    NSString *script = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];

    if (error) {
        script = @"";
        NSLog(@"Error: Could not read file in path "%@" to string. (%@)", path, error);
        // Return void or throw an error here.
        return
    }

    [self.context evaluateScript:script];
}

This example is based on code in Phoenix https://github.com/kasper/phoenix

https://github.com/kasper/phoenix/blob/master/Phoenix/PHContext.m#L195-L206

Swift 4

This example is based on code in CutBox https://github.com/cutbox/CutBox

import JavascriptCore

class JSService {

    let context = JSContext()

    let shared = JSService()

    let require: @convention(block) (String) -> (JSValue?) = { path in
        let expandedPath = NSString(string: path).expandingTildeInPath

        // Return void or throw an error here.
        guard FileManager.default.fileExists(atPath: expandedPath)
            else { debugPrint("Require: filename \(expandedPath) does not exist")
                   return nil }

        guard let fileContent = try? String(contentsOfFile: expandedFilename) 
            else { return nil }

        return JSService.shared.context.evaluateScript(fileContent)
    }

    init() {
        self.context.setObject(self.require, 
                               forKeyedSubscript: "require" as NSString)
    }

    func repl(_ string: String) -> String {
        return self.context.evaluateScript(string).toString()
    }
}

Swift Import Declarations, These pieces then form the basis of one or more modules, which may then be packaged into libraries or frameworks. In this way, import  These pieces then form the basis of one or more modules, which may then be packaged into libraries or frameworks. In this way, import declarations are the glue that holds everything together. Yet despite their importance, most Swift developers are familiar only with their most basic form: import module

I too have same issue in accessing my own JS code, which uses 3 external libraries(Lodash, Moment and Moment-range).

Initially i have tried by importing all 4 .js files(1-My own file and 3-libraries) inside my app bundle. But when im trying like this its not worked. I faced issue in place where i have imported those libraries inside my own JS code.

Workaround:

At last i have copied and pasted all codes from those libraries codes inside my own code and imported only one .js file(my own file) inside my app. So in this case no need to import anything from any library. So my code worked successfully.

NOTE: I am not sure whether my workaround is good approach or not. I just thought to register my issue and workaround which helped me.

My code:

lazy var context: JSContext? = {
    let context = JSContext()

    guard let timeSlotJS = Bundle.main.path(forResource: "app", ofType: "js") else {
        print("Unable to read resource file")
        return nil
    }

    do {
        let timeSlotContent = try String(contentsOfFile: timeSlotJS, encoding: String.Encoding.utf8)

        _ = context?.evaluateScript(timeSlotContent)
    } catch {
        print("Error on extracting js content")
    }

    let _ = context?.evaluateScript("var console = { log: function(message) { _consoleLog(message) } }")

    // Print log messages
    let consoleLog: @convention(block) (String) -> Void = { message in
        print("Javascript log: " + message)
    }
    context?.setObject(unsafeBitCast(consoleLog, to: AnyObject.self), forKeyedSubscript: "_consoleLog" as NSCopying & NSObjectProtocol)

    // Print exception messages
    context!.exceptionHandler = { context, exception in
        print("JS Error: \(exception!)")
    }

    return context
}()

func accessingJSCode() {
    let timeSlotScript = "DigitalVaultTimeSlotAvailabilityFuntion({\"services\": \(services)}, {\"response\": {\"result\": {\"CustomModule1\": {\"row\":\(rowValue)}}, \"uri\": \"/crm/private/json/CustomModule1/searchRecords\"}});"
    print(timeSlotScript)
    let timeSlots = context!.evaluateScript(timeSlotScript)

    // Then here i parsed my output JSON from script.
}

Thanks!!

Access Control, In addition to offering various levels of access control, Swift reduces the need to unit and that can be imported by another module with Swift's import keyword. Before we conclude our spotlight on Swift import declarations, browsing the Swift documentation led me to an interesting declaration attribute that’s currently not officially released: @_exported. According to the docs, applying this attribute to an import declaration exports the import module, submodule, or declaration from the current module.

swift/Modules.rst at master · apple/swift · GitHub, Importing <import> the module gives access to these declarations and allows them to be used in your code. import Chess import Foundation. You can also  Once we create a module we can use the Swift package manager to automatically import and build the module when we build our project. To do this, all we need to do is to define a dependency on the module within the projects Package.swift file. The following example shows how we could define a dependency on the Ifaddrs module:

Importing Modules into the Xcode Project, Importing Modules into the Xcode Project. What if you want to use a community React Native component, i.e., something that is not part of react-native , but is  2. Wrapping cmark in a module. In C, you’d now #include one or more of the library’s header files to make their declarations visible to your own code. Swift can’t handle C header files directly; it expects dependencies to be modules.

Import Module in Swift - SyntaxDB, Used to import a module with pre-defined functions, constants, and classes. Syntax. import moduleName. Notes. Many modules are included in Swift, but they can  You don't need to import Swift classes, but you need to import external modules (targets) The Default Access Control level in Swift is Internal access Considering that tests are on another target on PrimeNumberModelTests.swift you need to import the target that contains the class that you want to test, if your target is called MyProject will need to add import MyProject to the PrimeNumberModelTests :

Comments
  • dont know if it helps or not. try installing babel and transform your javascript files to es5
  • github.com/scp93ch/morse-pro contains only src which is ES6. It's very common to not commit dist to a repo. If you cloned it, it's expected that you will build it to ES5 by your own with npm build. To avoid that, install packages from NPM as a rule of thumb.
  • I'm pretty sure 'import' is not correctly implemented in JavaScriptCore
  • what is new Morse.MorseMessage() ?? could you please explain the js code
  • @mirhpedanielle I'm a little rusty on this so forgive inaccuracies. MorseMessage is a javascript class from Stephen Phillips's morse-pro. In this answer I used Browserify's --standalone flag to create a javascript object called Morse and attach MorseMessage to it, to make it easy to import into Swift's Javascript context. Once that's done (and the bundle is imported), MorseMessage works like any normal Javascript class.
  • thanks buddy!!.. but I have a doubts it necessary to create a bundle with bundle name .. and also calling jsContext.evaluateScript is not working
  • It worked for me. If you figure out a better way to do it without a bundle, please post an answer.