Flutter JS 插件

一个用于 Flutter 的 Javascript 引擎。现在它通过 Dart ffi 在 Android 上使用 QuickJS,在 iOS 上也通过 Dart ffi 使用 JavascriptCore。Javascript 运行时通过 Dart ffi 同步运行。所以现在你可以在你的 Flutter 移动应用(Android、iOS、Windows、Linux 和 MacOS 都支持)中将 Javascript 代码作为原生组件运行。

在之前的版本中,我们只能将求值表达式的结果作为字符串获取。

但现在我们可以用 flutter_js 做更多的事情,比如通过 Dart http 库运行 xhr 和 fetch http 调用。我们也支持 Promises。

有了 flutter_js,Flutter 应用程序可以利用优秀的 Javascript 库,如 ajv(json schema 验证)、moment(DateTime 解析器和操作),在移动设备(Android 和 iOS)上原生运行(无需 PlatformChannels)。

在 iOS 上,此库依赖于 iOS SDK 提供的原生 JavascriptCore。在 Android 上,它使用非常棒且小巧的 Javascript 引擎 QuickJS https://bellard.org/quickjs/(Fabrice Bellard 和 Charlie Gordon 的精彩之作)。

在 Android 上,您也可以使用 JavascriptCore。您只需要添加 Android 依赖项 implementation "com.github.fast-development.android-js-runtimes:fastdev-jsruntimes-jsc:0.1.3",并将 forceJavascriptCoreOnAndroid: true 传递给 getJavascriptRuntime 函数。

在 MacOS 上,使用 OSX 提供的 JavascriptCore。在 Windows 和 Linux 上,使用的引擎是 QuickJS。在 0.4.0 版本中,我们从 flutter_qjs 库中借用了 dart ffi 源代码。flutter_qjs 是一个非常棒的包,它们在构建 Dart 和 JS 之间的良好 ffi 桥梁方面做了出色的工作,还对 quickjs 源代码进行了修改,使其能够在 Windows 上运行。但是,flutter_js 采取使用 JavascriptCore 的方法(主要是在 iOS 上),以避免在 Apple Store 中被拒绝,Apple Store 的规定是,应用程序可以包含或运行未嵌入在二进制文件中的代码(例如,基于 HTML5 的游戏、机器人等),只要代码分发不是应用程序的主要目的。它还规定,您的应用程序必须使用 WebKit 和 JavaScript Core 来运行第三方软件,并且不应尝试将原生平台 API 扩展或暴露给第三方软件;参考:https://developer.apple.com/app-store/review/guidelines/ [ Session 4.7]。因此,我们避免在 iOS 应用程序中使用 quickjs,所以 flutter_js 提供了一个名为 JavascriptRuntime 的抽象,该抽象在 Apple 设备和桌面设备上通过 JavascriptCore 运行,而在 Android、Windows 和 Linux 上通过 QuickJS 运行。

FLutterJS 允许使用 Javascript 来执行 TextFormField 的验证逻辑,我们还可以执行从 Web 应用程序共享的规则引擎或 redux 逻辑。机会非常多。

该项目是开源的,采用 MIT 许可证。

用于通过 dart:ffi 与 JavascriptCore 通信的绑定是我们从 flutter_jscore 包中借用的。

Flutter JS 提供了 QuickJS Dart ffi 绑定的实现,并构建了一个 Dart 的包装器 API,它提供了一个统一的 API 来评估 Javascript,并通过 QuickJS 和 Javascript Core 以统一的方式在 Dart 和 Javascript 之间进行通信。

此库还允许通过 Dart Http 调用在 Javascript 中调用 xhr 和 fetch。我们还提供了允许评估 promises 的实现。

flutter_js

功能

安装

dependencies:
  flutter_js: 0.1.0+0

iOS

由于 flutter_js 使用原生 JavascriptCore,因此无需采取任何操作。

Android

在您的 android/app/build.gradle 文件中将最低 Android sdk 版本更改为 21(或更高)。

minSdkVersion 21

发布部署

Android

为发布构建设置 proguard:设置您的 android/app/proguard-rules.pro 文件
包含以下内容。

请记住与其他配置合并,这些配置是您应用程序使用的
其他插件所必需的。

#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
-keep class de.prosiebensat1digital.** { *; }

还将这些行添加到您的 android -> buildTypes -> release 部分的 android/app/build.gradle 文件中

 minifyEnabled true
  useProguard true

  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

示例

这是一个简单的 Flutter 应用程序,展示了如何在 Flutter 应用中评估 Javascript 代码。

import 'package:flutter/material.dart';
import 'dart:async';

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

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _jsResult = '';
  JavascriptRuntime flutterJs;
  @override
  void initState() {
    super.initState();
    
    flutterJs = getJavascriptRuntime();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FlutterJS Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('JS Evaluate Result: $_jsResult\n'),
              SizedBox(height: 20,),
              Padding(padding: EdgeInsets.all(10), child: Text('Click on the big JS Yellow Button to evaluate the expression bellow using the flutter_js plugin'),),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text("Math.trunc(Math.random() * 100).toString();", style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic, fontWeight: FontWeight.bold),),
              )
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          backgroundColor: Colors.transparent, 
          child: Image.asset('assets/js.ico'),
          onPressed: () async {
            try {
              JsEvalResult jsResult = flutterJs.evaluate(
                  "Math.trunc(Math.random() * 100).toString();");
              setState(() {
                _jsResult = jsResult.stringResult;
              });
            } on PlatformException catch (e) {
              print('ERRO: ${e.details}');
            }
          },
        ),
      ),
    );
  }
}

如何从 Javascript 调用 Dart

您可以在 JavascriptRuntime 对象上添加一个通道来接收来自 Javascript 引擎的调用。

在 Dart 端

javascriptRuntime.onMessage('someChannelName', (dynamic args) {
     print(args);
});

现在,如果您的 Javascript 代码调用 sendMessage('someChannelName', JSON.stringify([1,2,3]);,上面作为第二个参数提供的 Dart 函数将被调用。
List 中包含 1、2、3 作为其元素。

替代方案(以及为什么我们认为我们的库更好)

已经有其他包提供了在 Flutter 项目中评估 Javascript 的替代方案。

https://pub.dev/packages/flutter_liquidcore

好,它基于 https://github.com/LiquidPlayer/LiquidCore

它基于 V8 引擎,所以可执行库非常大(20Mb)。因此最终的应用程序也会很大。

https://pub.dev/packages/interactive_webview

允许在隐藏的 Webview 中评估 Javascript。它不会增加应用程序的大小,但 Webview 意味着整个浏览器仅用于评估 Javascript 代码。所以我们认为嵌入式引擎是一个好得多的解决方案。

https://pub.dev/packages/jsengine

基于 jerryscript,比 quickjs 慢。jsengine 包没有为 iOS 提供实现。

https://pub.dev/packages/flutter_jscore

在 Android 和 IOS 上使用 Javascript Core。我们从这个很棒的包中获取了 JavascriptCore 绑定。但是,默认情况下
我们提供 QuickJS 作为 Android 上的 Javascript 运行时,因为它提供的占位空间更小。另外
我们的库增加了对 ConsoleLog、SetTimeout、Xhr、Fetch 和 Promises 的支持,以便在脚本评估中使用
并允许您的 Flutter 应用通过 onMessage 函数提供 dartFunctions 作为通道,以便在
您的 Javascript 代码中调用。

https://pub.dev/packages/flutter_qjs

一个很棒的包,它通过 Dart ffi 使用 quickjs 实现 Javascript 引擎。
唯一的区别是它也在 iOS 设备上使用 quickjs,我们认为这在通过 Apple Store 审查过程时会很麻烦。在 flutter_js 0.4.0 版本中,我们
增加了对 Desktop 的支持,并改进了 Dart/Js 集成,我们从 flutter_qjs 源代码中借用了 C 函数绑定和 Dart/JS 转换及集成。我们只是对它进行了调整,以支持 xhr、fetch 并保持通过 JavascriptRuntime 类提供的与 flutter_js 相同的接口。

APK 体积小

根据 flutter 文档,一个 hello world Flutter 应用的大小为 4.2Mb 或 4.6Mb。

https://flutterdart.cn/docs/perf/app-size#android

下面您可以看到使用 flutter_js 生成的 示例应用程序 的 APK 大小。


|master ✓| → flutter build apk --split-per-abi

✓ Built build/app/outputs/apk/release/app-armeabi-v7a-release.apk (5.4MB).
✓ Built build/app/outputs/apk/release/app-arm64-v8a-release.apk (5.9MB).
✓ Built build/app/outputs/apk/release/app-x86_64-release.apk (6.1MB).

Ajv

我们刚刚添加了一个使用很棒的 js 库 Ajv 的示例,该库允许将最先进的 json schema 验证功能引入
到 Flutter 世界。您可以在此处查看 Ajv 示例:https://github.com/abner/flutter_js/blob/master/example/lib/ajv_example.dart

请看下面我们添加到示例应用程序中的屏幕截图。

iOS

ios_ajv_form

ios_ajv_result

Android

android_ajv_form

android_ajv_result

MACOS

要启用 http 调用,请将此添加到您的文件中

  • DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.client</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
</dict>
</plist>

  • Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.network.client</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
</dict>
</plist>

GitHub

https://github.com/abner/flutter_js