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`

两个附加说明

  1. 使用 release 配置构建的 quickjs 在解析 Promise 时存在错误。如果您知道解决方案,请告知我。

  2. ios/make.sh 限制了构建架构,以避免合并冲突。请修改 make.sh 以支持其他架构。

GitHub

https://github.com/ekibun/flutter_qjs