如何在 Swift 的 JavaScriptCore 中导入模块?
Posted
技术标签:
【中文标题】如何在 Swift 的 JavaScriptCore 中导入模块?【英文标题】:How to import modules in Swift's JavaScriptCore? 【发布时间】:2018-01-20 10:04:33 【问题描述】:我正在尝试使用 Swift 的 javascriptCore 框架来利用现有的使用 ES6 模块的 JavaScript 库。具体来说,morse-pro by Stephen C Phillips。我已将文件添加到 Xcode 游乐场,然后使用此代码加载库并在 JavaScript 上下文中运行它:
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)
这种方法适用于简单的“Hello world”类型的测试,但它会因为这个 JavaScript 错误而阻塞 morse-pro 库:
语法错误:意外的标记“*”。导入调用期望 正是一个论点。
错误似乎是由 morse-pro-message.js 中的这一行引起的:
import * as Morse from './morse-pro';
我认为这是尝试将所有 morse-pro 文件作为模块导入。
我不熟悉 ES6 模块,但该库似乎可以在普通 JavaScript 上下文中为其他人工作。我在 Swift 中加载库的方式有问题吗?还是模块是 JavaScriptCore 不支持的功能? (documentation 只是说它支持“JavaScript”并且没有更具体。)
如果有任何建议能指引我在 JavaScriptCore VM 中运行这个库,我将不胜感激。
【问题讨论】:
不知道有没有帮助。尝试安装 babel 并将您的 javascript 文件转换为 es5 github.com/scp93ch/morse-pro 仅包含src
,即 ES6。不将 dist
提交到 repo 是很常见的。如果您克隆了它,预计您将使用npm build
自己将其构建为 ES5。为避免这种情况,请根据经验从 NPM 安装包。
我很确定 'import' 在 JavaScriptCore 中没有正确实现
【参考方案1】:
在黑暗中摸索了很多次之后,我找到了一种无需手动更改即可使库可供 Swift 使用的方法。
首先,正如@estus 所建议的,我使用 NPM 安装了该库,该库将其转换为 ES5,但不解决依赖关系。所以它仍然是一堆单独的文件,它们使用浏览器和 JavaScriptCore 都无法理解的 require
和 export
关键字相互调用。
然后我使用Browserify 将所有依赖项捆绑到一个文件中,以便 JavaScriptCore 可以理解它。 Browserify 的正常操作隐藏了所有代码,所以我使用了“--standalone”标志来告诉它使标记的功能可用。如果直接导出 ES5 文件,它会创建一个通用对象并将导出的函数放在.default
下。我希望它们更易于访问,所以我创建了一个新文件来列出导出,然后在上面运行 Browserify。例如,一个名为“morse-export.js”的文件包含:
module.exports.MorseMessage = require('./lib/morse-pro-message.js').default;
然后我像这样在它上面运行 Browserify:
browserify ./morse-export.js --standalone Morse > ./morse-bundle.js
并使用Bundle.main.path(forResource)
在我的 Swift 代码中包含 morse-bundle.js 文件。现在我可以使用Morse.MorseMessage
访问 MorseMessage 类,所以回到 Swift 中:
jsContext?.evaluateScript("var morseMessage = new Morse.MorseMessage()")
print(jsContext!.evaluateScript("morseMessage.translate('abc')"))
打印“.- -... -.-.”如你所料。
这样做的缺点是您必须手动将要导出的任何类和函数添加到导出文件中。不过,这似乎是最简单的方法。如果有更好的方法,我很想听听!
【讨论】:
什么是新的 Morse.MorseMessage() ?你能解释一下js代码吗 @mirhpedanielle 我对此有点生疏,请原谅不准确之处。MorseMessage
是来自 Stephen Phillips 的 morse-pro 的 javascript class。在这个答案中,我使用 Browserify 的 --standalone
标志创建了一个名为 Morse
的 javascript 对象并将 MorseMessage 附加到它,以便轻松导入 Swift 的 Javascript 上下文。一旦完成(并导入包),MorseMessage
就像任何普通的 Javascript 类一样工作。
谢谢哥们!!.. 但我怀疑是否有必要创建一个带有包名的包.. 并且调用 jsContext.evaluateScript 不起作用
它对我有用。如果您想出更好的不捆绑的方法,请发布答案。
我正在尝试在 wkwebview 中使用外部库。我需要遵循同样的方式吗?或者我应该只在路径中加载 bundle.js 文件。请指教。【参考方案2】:
虽然对模块使用import
是不可能的。
您可以支持require('path/filename');
语法。
这是通过向 JS 提供 require 作为函数来完成的。 import
命令(不幸的是)太奇特而无法在JSContext
的给定限制中实现。
查看以下实现
目标-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];
本示例基于凤凰https://github.com/kasper/phoenix中的代码
https://github.com/kasper/phoenix/blob/master/Phoenix/PHContext.m#L195-L206
斯威夫特 4
本示例基于 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()
【讨论】:
【参考方案3】:我在访问我自己的 JS 代码时也有同样的问题,它使用 3 个外部库(Lodash、Moment 和 Moment-range)。
最初我尝试在我的应用程序包中导入所有 4 个 .js 文件(1-我自己的文件和 3-库)。但是当我这样尝试时它不起作用。 我在自己的 JS 代码中导入了这些库时遇到了问题。
解决方法:
最后,我将这些库代码中的所有代码复制并粘贴到我自己的代码中,并在我的应用程序中只导入了一个 .js 文件(我自己的文件)。所以在这种情况下,不需要从任何库中导入任何东西。所以我的代码成功了。
注意:我不确定我的解决方法是否是好方法。我只是想注册我的问题和解决方法,这对我有帮助。
我的代码:
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.
谢谢!!
【讨论】:
以上是关于如何在 Swift 的 JavaScriptCore 中导入模块?的主要内容,如果未能解决你的问题,请参考以下文章
Swift - 如何在 Swift 中获取另一个应用程序的 NSDockTile 对象?