Flutter JavaScript引擎插件flutter_qjs的使用

Flutter JavaScript引擎插件flutter_qjs的使用

flutter_qjs

Pub Test

中文

该插件是基于quickjs项目和dart:ffi构建的一个简单的Flutter JavaScript引擎。目前该插件支持所有平台,除了web!

开始使用

基本用法

首先,创建一个FlutterQjs对象,然后调用dispatch来建立事件循环:

final engine = FlutterQjs(
  stackSize: 1024 * 1024, // 更改堆栈大小
);
engine.dispatch();

使用evaluate方法运行JavaScript脚本,它会同步执行,你可以使用await来解析Promise

try {
  print(engine.evaluate(code ?? ''));
} catch (e) {
  print(e.toString());
}

方法close可以销毁quickjs运行时,如果再次调用evaluate,它可以被重新创建。参数port应该关闭以停止dispatch循环,当不再需要它时。自v0.3.3版本起,将抛出引用泄漏异常。

try {
  engine.port.close(); // 停止事件循环
  engine.close();      // 关闭引擎
} on JSError catch(e) { 
  print(e);            // 捕获引用泄漏异常
}
engine = null;

Dart与JavaScript之间的数据转换实现如下:

Dart JavaScript
Bool boolean
Int number
Double number
String string
Uint8List ArrayBuffer
List Array
Map Object
Function(arg1, arg2, …, {thisVal})
JSInvokable.invoke([arg1, arg2, …], thisVal)
function.call(thisVal, arg1, arg2, …)
Future Promise
JSError Error
Object DartObject

使用模块

支持ES6模块并使用import函数管理模块,可以通过moduleHandler在Dart中进行处理:

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"));

注意: 模块处理器对于每个模块名称只能调用一次。要重置模块缓存,调用FlutterQjs.close然后再评估一次。

要在模块处理器中使用异步函数,请尝试在隔离线程上运行。

在隔离线程上运行

创建一个IsolateQjs对象,并传递处理程序来解析模块。现在可以使用异步函数如rootBundle.loadString来获取模块:

final engine = IsolateQjs(
  moduleHandler: (String module) async {
    return await rootBundle.loadString(
        "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
  },
);
// 不需要调用engine.dispatch();

与在主线程上运行一样,使用evaluate来运行JavaScript脚本。在隔离线程中,一切都返回异步结果,使用await获取结果:

try {
  print(await engine.evaluate(code ?? ''));
} catch (e) {
  print(e.toString());
}

方法close可以销毁隔离线程,如果再次调用evaluate,它将被重新创建。

使用Dart函数(v0.3.0中的破坏性更改)

JavaScript脚本返回的函数将被转换为JSInvokable它不扩展Function,使用invoke方法来调用它

(func as JSInvokable).invoke([arg1, arg2], thisVal);

注意: 评估返回JSInvokable可能会导致引用泄漏。 你应该手动调用free来释放JS引用。

(obj as JSRef).free();
// 或 JSRef.freeRecursive(obj);

传递给JSInvokable的参数将自动释放。使用dup来保持引用。

(obj as JSRef).dup();
// 或 JSRef.dupRecursive(obj);

自v0.3.0版本起,你可以在JSInvokable参数中传递一个函数,默认情况下不再包含channel函数。你可以使用JavaScript函数设置全局Dart对象。 例如,使用Dio实现qjs中的HTTP请求:

final setToGlobalObject = await engine.evaluate("(key, val) => { this[key] = val; }");
await setToGlobalObject.invoke(["http", (String url) {
  return Dio().get(url).then((response) => response.data);
}]);
setToGlobalObject.free();

在隔离线程中,传递到JSInvokable的顶级函数将在隔离线程中调用。使用IsolateFunction传递一个即时函数:

await setToGlobalObject.invoke([
  "http",
  IsolateFunction((String url) {
    return Dio().get(url).then((response) => response.data);
  }),
]);

示例代码

以下是一个完整的示例代码,展示了如何使用flutter_qjs插件:

/*
 * [@Description](/user/Description): example
 * [@Author](/user/Author): ekibun
 * [@Date](/user/Date): 2020-08-08 08:16:51
 * [@LastEditors](/user/LastEditors): ekibun
 * [@LastEditTime](/user/LastEditTime): 2020-12-02 11:28:06
 */
import 'package:flutter/material.dart';

import 'package:flutter/services.dart';
import 'package:flutter_qjs/flutter_qjs.dart';

import 'highlight.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'flutter_qjs',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        appBarTheme: AppBarTheme(brightness: Brightness.dark, elevation: 0),
        backgroundColor: Colors.grey[300],
        primaryColorBrightness: Brightness.dark,
      ),
      routes: {
        'home': (BuildContext context) => TestPage(),
      },
      initialRoute: 'home',
    );
  }
}

class TestPage extends StatefulWidget {
  [@override](/user/override)
  State<StatefulWidget> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  String resp;
  IsolateQjs engine;

  CodeInputController _controller = CodeInputController(
      text: 'import("hello").then(({default: greet}) => greet("world"));');

  _ensureEngine() async {
    if (engine != null) return;
    engine = IsolateQjs(
      moduleHandler: (String module) async {
        return await rootBundle.loadString(
            "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js");
      },
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("JS引擎测试"),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: [
                  TextButton(
                      child: Text("评估"),
                      onPressed: () async {
                        await _ensureEngine();
                        try {
                          resp = (await engine.evaluate(_controller.text ?? '',
                                  name: "<eval>"))
                              .toString();
                        } catch (e) {
                          resp = e.toString();
                        }
                        setState(() {});
                      }),
                  TextButton(
                      child: Text("重置引擎"),
                      onPressed: () async {
                        if (engine == null) return;
                        await engine.close();
                        engine = null;
                      }),
                ],
              ),
            ),
            Container(
              padding: const EdgeInsets.all(12),
              color: Colors.grey.withOpacity(0.1),
              constraints: BoxConstraints(minHeight: 200),
              child: TextField(
                  autofocus: true,
                  controller: _controller,
                  decoration: null,
                  expands: true,
                  maxLines: null),
            ),
            SizedBox(height: 16),
            Text("结果:"),
            SizedBox(height: 16),
            Container(
              width: double.infinity,
              padding: const EdgeInsets.all(12),
              color: Colors.green.withOpacity(0.05),
              constraints: BoxConstraints(minHeight: 100),
              child: Text(resp ?? ''),
            ),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter JavaScript引擎插件flutter_qjs的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter JavaScript引擎插件flutter_qjs的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_qjs 是一个 Flutter 插件,它允许在 Flutter 应用中嵌入 QuickJS JavaScript 引擎。QuickJS 是一个轻量级、高效的 JavaScript 引擎,由 Fabrice Bellard 开发。使用 flutter_qjs 可以在 Flutter 应用中执行 JavaScript 代码,实现与 JavaScript 的互操作。

主要功能

  • 在 Flutter 应用中执行 JavaScript 代码。
  • 在 Dart 和 JavaScript 之间传递数据。
  • 支持异步 JavaScript 执行。
  • 支持 ES2020 标准。

安装

首先,在 pubspec.yaml 文件中添加 flutter_qjs 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_qjs: ^0.5.0  # 请使用最新版本

然后运行 flutter pub get 来安装依赖。

基本用法

  1. 初始化引擎

    首先,需要初始化 Qjs 实例:

    import 'package:flutter_qjs/flutter_qjs.dart';
    
    final engine = Qjs();
    
  2. 执行 JavaScript 代码

    可以使用 evaluate 方法来执行 JavaScript 代码:

    void main() async {
      final engine = Qjs();
      final result = await engine.evaluate('1 + 2');
      print(result);  // 输出: 3
    }
    
  3. 在 Dart 和 JavaScript 之间传递数据

    可以在 Dart 和 JavaScript 之间传递基本数据类型(如 int, double, String, List, Map 等)。

    void main() async {
      final engine = Qjs();
      await engine.evaluate('''
        function add(a, b) {
          return a + b;
        }
      ''');
      final result = await engine.call('add', [1, 2]);
      print(result);  // 输出: 3
    }
    
  4. 异步 JavaScript 执行

    flutter_qjs 支持在 JavaScript 中使用 Promise 进行异步操作。你可以使用 await 关键字来等待 Promise 的结果。

    void main() async {
      final engine = Qjs();
      final result = await engine.evaluate('''
        new Promise((resolve) => {
          setTimeout(() => resolve("Hello, World!"), 1000);
        });
      ''');
      print(result);  // 输出: Hello, World!
    }
    
  5. 在 JavaScript 中调用 Dart 函数

    你可以将 Dart 函数传递给 JavaScript,并在 JavaScript 中调用它们。

    void main() async {
      final engine = Qjs();
      engine.setProperty('dartFunction', (args) {
        print('Called from JavaScript: $args');
        return 'Dart response';
      });
    
      final result = await engine.evaluate('''
        dartFunction("Hello from JavaScript");
      ''');
      print(result);  // 输出: Dart response
    }
    
  6. 销毁引擎

    当不再需要引擎时,可以调用 dispose 方法来释放资源:

    engine.dispose();
回到顶部