Flutter JavaScript执行插件flutter_js_stable的使用

Flutter JavaScript执行插件flutter_js_stable的使用

Flutter JS Stable

这是flutter_js的一个稳定版本,其中包含了针对Android QuickJS运行时的修复。

Flutter JS 插件

这是一个用于在Flutter中使用的JavaScript引擎。目前在Android上通过Dart ffi使用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模式验证)、moment(日期时间解析和操作),这些库可以直接在移动设备上运行,无需PlatformChannels。

在iOS上,此库依赖于iOS SDK提供的原生JavaScriptCore。在Android上,它使用了Fabrice Bellard和Charlie Gordon令人惊叹且小巧的JavaScript引擎QuickJS(https://bellard.org/quickjs/)。

要在iOS上调试JS代码,你需要设置javascriptRuntime.setInspectable(true); 并传递sourceUrl给evaluate(例如:sourceUrl: ‘script.js’)。

在Android上,你也可以使用JavaScriptCore。你只需要添加一个Android依赖implementation "com.github.fast-development.android-js-runtimes:fastdev-jsruntimes-jsc:0.3.4" 并传递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的策略,主要是为了防止苹果商店拒绝,因为苹果商店规定:

Apps may contain or run code that is not embedded in the binary (e.g. HTML5-based games, bots, etc.), as long as code distribution isn’t the main purpose of the app。 你的应用必须使用WebKit和JavaScript Core来运行第三方软件,并不应试图扩展或暴露本机平台API给第三方软件。

因此,我们避免在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绑定的实现,并且还构造了一个包装API,该API向Dart提供了统一的API来评估JavaScript并通过对QuickJS和JavaScript Core的统一方式在Dart和JavaScript之间进行通信。

此库还允许通过Dart Http调用在JavaScript中调用xhr和fetch。我们也提供了实现,允许评估Promises。

安装

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('点击大黄色JS按钮使用flutter_js插件评估以下表达式')),
              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('错误: ${e.details}');
            }
          },
        ),
      ),
    );
  }
}

如何从JavaScript调用Dart

你可以在JavascriptRuntime对象上添加一个通道以接收来自JavaScript引擎的调用:

在Dart端:

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

现在,如果您的JavaScript代码调用sendMessage('someChannelName', JSON.stringify([1,2,3]);,上述作为第二个参数提供的Dart函数将被调用,并带有包含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上使用JavaScriptCore。我们从这个惊人的包中得到了JavaScriptCore绑定。但是,默认情况下我们在Android上提供QuickJS作为JavaScript运行时,因为它提供了更小的体积。此外,我们的库还支持ConsoleLog、SetTimeout、Xhr、Fetch和Promises,以便在脚本评估中使用,并允许您的Flutter应用通过onMessage函数提供dartFunctions作为通道,供JavaScript代码内部调用。

https://pub.dev/packages/flutter_qjs

一个令人惊叹的包,通过Dart ffi实现了QuickJS JavaScript引擎。唯一不同的是它也在iOS设备上使用QuickJS,我们认为这可能会导致苹果商店审核过程出现问题。在flutter_js 0.4.0版本中,我们增加了对桌面的支持并改进了Dart/Js集成,我们从flutter_qjs源代码中借用了C函数绑定和Dart/JS转换及集成。我们只是对其进行调整以支持xhr、fetch,并保持flutter_js通过JavascriptRuntime类提供的相同接口。

小APK大小

根据Flutter文档,一个简单的Hello World Flutter应用大约有4.2 MB或4.6 MB。

|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

我们刚刚添加了一个使用amazing js库的示例,该库允许将最先进的JSON模式验证功能带入Flutter世界。我们可以在https://github.com/abner/flutter_js/blob/master/example/lib/ajv_example.dart 中看到Ajv示例。

以下是我们在示例应用中添加的屏幕:

iOS

[ios_form]

[ios_ajv_result]

Android

[android_form]

[android_ajv_result]

MacOS

解决 Command Line Tool - Error - xcrun: error: unable to find utility “xcodebuild”,not a developer tool or in PATH

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

在Catalina中使用XCode 12时,我需要安装ruby 2.7.2才能安装cocoapods(也需要Flutter在iOS上运行)。所以我安装了brew和rbenv。

要启用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>

Windows 和 Linux

C包装库托管在GitHub仓库:https://github.com/abner/quickjs-c-bridge

我们只是分离了代码以允许构建它,而在这个仓库中只有已发布的共享库,因此每个使用flutter_js的应用程序不需要每次都重新编译它。

QuickJS Android 共享库

库包装器,包括QuickJS和JavaScriptCore,也在一个单独的仓库中编译:https://github.com/fast-development/android-js-runtimes

随着库被编译并发布到jitpack,通过flutter_js使用这些包装器的应用程序不需要使用Android NDK编译共享库。

单元测试JavaScript评估

我们可以通过桌面平台(Windows、Linux 和 MacOS)使用flutter_js单元测试JavaScript表达式的评估。

对于Windows和Linux,您需要先构建您的应用程序桌面可执行文件:flutter build -d windowsflutter build -d linux

在Windows上,首次构建您的应用程序后,至少需要将路径build\windows\runner\Debug(绝对路径)添加到您的环境变量中。

在PowerShell中,只需运行 $env:path += ";${pwd}\build\windows\runner\Debug"。现在您可以在添加了\build\windows\runner\Debug到路径的命令行会话中运行测试。

对于Linux,您需要导出一个名为LIBQUICKJSC_TEST_PATH的环境变量,指向build/linux/debug/bundle/lib/libquickjs_c_bridge_plugin.so。例如:export LIBQUICKJSC_TEST_PATH="$PWD/build/linux/debug/bundle/lib/libquickjs_c_bridge_plugin.so"

要将测试集成到Visual Studio Code中,您需要在.vscode/launch.json文件中设置一个启动器,以便您可以填写Windows上的PATH和Linux上的LIBQUICKJSC_TEST_PATH

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "test-with-flutterjs",
            "type": "dart",
            "program": "test/flutter_js_test.dart",
            "windows": {
                "env": {
                    "PATH": "${env:Path};${workspaceFolder}\\example\\build\\windows\\runner\\Debug"
                }
            },
            "linux": {
                "env": {
                    "LIBQUICKJSC_TEST_PATH": "${workspaceFolder}/example/build/linux/debug/bundle/lib/libquickjs_c_bridge_plugin.so"
                }
            },
            "request": "launch"
        }
    ]
}

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

1 回复

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


flutter_js_stable 是一个 Flutter 插件,允许你在 Flutter 应用中执行 JavaScript 代码。它基于 quickjs 引擎,提供了一个轻量级且高效的 JavaScript 执行环境。以下是如何在 Flutter 项目中使用 flutter_js_stable 插件的详细步骤。

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 flutter_js_stable 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_js_stable: ^0.3.0  # 请使用最新版本

然后,运行 flutter pub get 来获取依赖。

2. 初始化 JavaScript 引擎

在你的 Dart 代码中,首先需要初始化 JavaScript 引擎。你可以在 initState 或其他合适的地方进行初始化。

import 'package:flutter_js_stable/flutter_js_stable.dart';

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late FlutterJs flutterJs;

  [@override](/user/override)
  void initState() {
    super.initState();
    flutterJs = FlutterJs();
  }

  [@override](/user/override)
  void dispose() {
    flutterJs.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter JS Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              runJavaScript();
            },
            child: Text('Run JavaScript'),
          ),
        ),
      ),
    );
  }

  void runJavaScript() {
    String jsCode = '''
      function add(a, b) {
        return a + b;
      }
      add(2, 3);
    ''';

    var result = flutterJs.eval(jsCode);
    print('JavaScript Result: $result');  // 输出: JavaScript Result: 5
  }
}

3. 执行 JavaScript 代码

在上面的代码中,runJavaScript 函数演示了如何执行一段 JavaScript 代码。flutterJs.eval(jsCode) 方法用于执行 JavaScript 代码并返回结果。

4. 处理 JavaScript 错误

在实际应用中,JavaScript 代码可能会抛出错误。你可以通过捕获异常来处理这些错误。

void runJavaScript() {
  String jsCode = '''
    function add(a, b) {
      return a + b;
    }
    add(2, '3');  // 这里会返回字符串 '23'
  ''';

  try {
    var result = flutterJs.eval(jsCode);
    print('JavaScript Result: $result');  // 输出: JavaScript Result: 23
  } catch (e) {
    print('JavaScript Error: $e');
  }
}

5. 与 Dart 交互

你可以通过 flutterJs.callFunction 方法来调用 JavaScript 中的函数,并传递参数。

void runJavaScript() {
  String jsCode = '''
    function add(a, b) {
      return a + b;
    }
  ''';

  flutterJs.eval(jsCode);  // 先定义函数

  var result = flutterJs.callFunction('add', [2, 3]);
  print('JavaScript Result: $result');  // 输出: JavaScript Result: 5
}

6. 释放资源

在使用完 JavaScript 引擎后,记得调用 flutterJs.dispose() 来释放资源。

[@override](/user/override)
void dispose() {
  flutterJs.dispose();
  super.dispose();
}
回到顶部