flutter_qjs
该插件是使用 quickjs 项目和 dart:ffi 的一个简单的 Flutter JS 引擎。目前该插件支持除 Web 以外的所有平台!
入门
基本用法
首先,创建一个 FlutterQjs 对象,然后调用 dispatch 来调度事件循环。
final engine = FlutterQjs()
engine.dispatch();
使用 evaluate 方法运行 JS 脚本,现在您可以同步使用它,或者使用 await 来解析 Promise。
try {
print(engine.evaluate(code ?? ''));
} catch (e) {
print(e.toString());
}
close 方法可以销毁 quickjs 运行时,如果您调用 evaluate,它可以再次被重新创建。参数 port 应该被关闭以停止 dispatch 循环,当您不再需要它时。
engine.port.close(); // stop dispatch loop
engine.close(); // close engine
engine = null;
Dart 和 JS 之间的数据转换实现如下:
| dart | js |
|---|---|
| 布尔值 | boolean |
| Int(整数) | 数字 |
| Double(双精度浮点数) | 数字 |
| 字符串 | string |
| Uint8List | ArrayBuffer |
| 列表 | Array |
| 地图 | Object |
| JSFunction(...args) IsolateJSFunction(...args) |
function(....args) |
| Future | Promise |
注意: function 只能从 JS 发送到 Dart。
调用 Dart 函数
提供了一个全局 JavaScript 函数 channel 来调用 Dart 函数。
在构造函数中,传递处理程序函数来管理 JavaScript 调用。例如,您可以使用 Dio 来在 JavaScript 中实现 http。
final engine = FlutterQjs(
methodHandler: (String method, List arg) {
switch (method) {
case "http":
return Dio().get(arg[0]).then((response) => response.data);
default:
throw Exception("No such method");
}
},
);
然后,在 JavaScript 中,您可以使用 channel 函数来调用 methodHandler,确保第二个参数是一个列表。
channel("http", ["http://example.com/"]);
使用模块
支持 ES6 模块的 import 函数,并可以在 Dart 中使用 moduleHandler 进行管理。
final engine = FlutterQjs(
moduleHandler: (String module) {
if(module == "hello")
return "export default (name) => `hello \${name}!`;";
throw Exception("Module Not found");
},
);
然后在 JavaScript 中,使用 import 函数来获取模块。
import("hello").then(({default: greet}) => greet("world"));
注意: 每个模块名称的 Module handler 应该只调用一次。要重置模块缓存,请调用 FlutterQjs.close 然后再次调用 evaluate。
要在模块处理程序中使用异步函数,请尝试 在隔离线程上运行。
在隔离线程上运行
创建一个 IsolateQjs 对象,传递处理程序来实现 js-dart 交互和解析模块。methodHandler 在隔离中使用,所以处理程序函数必须是顶层函数或静态方法。现在可以使用 rootBundle.loadString 等异步函数来获取模块。
dynamic methodHandler(String method, List arg) {
switch (method) {
case "http":
return Dio().get(arg[0]).then((response) => response.data);
default:
throw Exception("No such method");
}
}
final engine = IsolateQjs(
methodHandler: methodHandler,
moduleHandler: (String module) async {
return await rootBundle.loadString(
"js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
},
);
// not need engine.dispatch();
与在主线程上运行相同,使用 evaluate 来运行 js 脚本。这样,evaluate 返回的 Promise 将被自动跟踪并返回解析后的数据。
try {
print(await engine.evaluate(code ?? '', "<eval>"));
} catch (e) {
print(e.toString());
}
close 方法可以销毁 quickjs 运行时,如果您调用 evaluate,它可以再次被重新创建。
这个例子 包含了一个如何使用此插件的完整演示。
对于 macOS 和 iOS 开发者
我刚开始接触 Xcode 和 iOS 开发,找不到更好的方法来支持模拟器和真实设备而无需合并二进制框架。为了减小构建尺寸,请在 ios/flutter_qjs.podspec 中将 s.vendored_frameworks 更改为特定的框架。
对于模拟器,使用
s.vendored_frameworks = `build/Debug-iphonesimulator/ffiquickjs.framework`
对于真实设备,使用
s.vendored_frameworks = `build/Debug-iphoneos/ffiquickjs.framework`
两个附加说明
-
使用
release配置构建的 quickjs 在解析Promise时存在错误。如果您知道解决方案,请告知我。 -
ios/make.sh限制了构建架构,以避免合并冲突。请修改make.sh以支持其他架构。