找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2799

积分

0

好友

357

主题
发表于 昨天 03:48 | 查看: 0| 回复: 0

Flutter 里跑 JavaScript,这需求听起来就挺怪的。

但实际上,JS 引擎在移动应用里用处挺大的。

比如你需要从网页抓取内容——网站的图片链接加密了一层,得用 JS 解密。你用 Dart 去硬写那些加密逻辑,一是麻烦,二是一旦网站换了算法,你就得重新发版。如果用 JS 呢?直接把网站的 JS 拿过来跑就完事了。

再比如规则引擎、数据清洗、过滤逻辑这些经常变动的业务,写成 JS 脚本动态加载,改规则不用发版。甚至可以通过 JavaScript引擎 实现某种程度的“热更新”——经常变动的业务逻辑用 JS 写,从服务器下发脚本就行。

这个 JS 引擎我几年前就实现了,一直在自己项目里用着。

去年给开源了,想着需要有个“真实项目”的示例,最近把“蜜柑计划”的 HTML 解析改造了一下 —— 从 isolate + Dart 手动解析,换成了 FJS + JS 在 Rust 线程执行。换用 fjs 实现之后,各种 HTML 操作和正则写起来顺手,Rust 又保证了内存安全和性能,算是顺便做了个优化,有兴趣的可以看一下怎么实现和bundle的:https://github.com/iota9star/mikan_flutter/tree/main/api

为什么不用现成的?

我调研了 pub.dev 上的几个方案。

flutter_js 是最火的,352 likes,25 万下载。但问题很明显——Android 用 QuickJS,iOS 用 JavaScriptCore,两个引擎行为还不一样。而且 QuickJS 的版本太老了,很多新特性都没有。

flutter_qjs 的 API 更现代一些,但用起来很繁琐——得手动调用 dispatch() 建立事件循环,引用管理也要手动 free()/dup(),稍微不注意就内存泄漏。

jsf 开箱即用,支持 Web,作者写得不错。但它只支持同步执行,作者也明确说了不打算加异步功能。这对需要网络请求的场景来说基本没法用。

想了想,算了,自己造一个吧。

怎么造的

底层用 QuickJS,这个没跑——轻量、快速,就是给嵌入式场景用的。但直接用 FFI 调 C API 太痛苦了,内存管理、生命周期、异步回调,全是坑。

所以我用了 Rust + FRB。

Rust 有个 rquickjs 库,是 QuickJS 的 bindings,有了别人封装好的东西,现在用起来舒服多了。再加上 flutter_rust_bridge 自动生成 FFI 代码,最后加上直接 AWS LLRT 封装好的一大堆 Node.js 兼容的模块。 就这么搞定了一个高性能、异步、ES6模块支持的 JS 引擎。实际代码量不多,但能做到什么程度,全看用户想象力。

实际上去年我用 fjs 实现了几种不同实现机制的热更框架,可以写 TSX + 编译器完成 Flutter 组件和业务的热更,算是一个比较有趣的尝试。

用起来怎么样

先说最重要的:异步是原生支持的。JS 的 Promise 会自动转成 Dart 的 Future,async/await 开箱即用。

然后它内置了一堆 Node.js 的模块——console、fetch、crypto、timers、buffer、url、path、fs 等等,基本上你需要的都有了。

Dart 端的 API 是类型安全的,用了 sealed class,编译期就能检查类型。ES6 的模块支持也很完整,import/export 随便用。

内存控制也暴露出来了,你可以设置内存上限、GC 阈值,手动触发 GC。双向桥接也有,Dart 调 JS、JS 调 Dart 都可以。

怎么用

基本的使用就这样:

final runtime = await JsAsyncRuntime.withOptions(
  builtin: JsBuiltinOptions(
    console: true,
    fetch: true,
    timers: true,
  ),
);
final context = await JsAsyncContext.from(runtime);
final engine = JsEngine(context);
await engine.initWithoutBridge();

final result = await engine.eval(source: JsCode.code('''
  console.log('Hello from FJS!');
  1 + 2
'''));
print(result.value); // 3

如果你需要用模块,可以这样:

await engine.declareNewModule(
  module: JsModule.code(module: 'math', code: '''
    export const add = (a, b) => a + b;
    export const multiply = (a, b) => a * b;
  '''),
);

await engine.eval(source: JsCode.code('''
  const { add, multiply } = await import('math');
  console.log(add(2, 3));        // 5
  console.log(multiply(4, 5));   // 20
'''));

双向桥接也支持,Dart 可以调 JS,JS 也能调 Dart:

await engine.init(bridge: (jsValue) async {
final data = jsValue.value;
if (data is Map && data['action'] == 'fetchUser') {
final user = await fetchUser(data['id']);
return JsResult.ok(JsValue.from(user));
  }
return JsResult.ok(JsValue.none());
});

await engine.eval(source: JsCode.code('''
  const user = await fjs.bridge_call({ action: 'fetchUser', id: 123 });
  console.log(user);
'''));

更多的实现可以参考 GitHub 上的示例应用,示例应用也做的比较完整,有各种 API 的使用示例。下面是一些贴图:

FJS JavaScript运行时主界面截图

JavaScript Playground界面截图

FJS API参考界面截图

FJS示例界面与日志输出截图

性能如何

在“蜜柑计划”里面,执行一个复杂的 HTML 解析脚本(大约 100 行 JS,包含正则、DOM 解析、网络请求),大概只需要 100-200ms。内存占用级别可以忽略不计,一个 runtime 大概 2-3MB。


如果你在 Flutter 里也有执行 JS 的需求,可以试试 FJS。代码完全开源,欢迎在 云栈社区 与大家分享你的实践经验或提出问题。

项目地址:https://github.com/fluttercandies/fjs




上一篇:C语言状态机实战:嵌入式开发中告别复杂if-else的清晰之道
下一篇:DDR5插4根内存不稳定的核心原因:拓扑、频率与信号完整性分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-6 07:20 , Processed in 0.285181 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表