flutter_qjs
A quickjs engine for flutter.
Feature
This plugin is a simple js engine for flutter using the quickjs project with dart:ffi. Plugin currently supports all the platforms except web!
Event loop of FlutterQjs should be implemented by calling FlutterQjs.dispatch().
ES6 module with import function is supported and can be managed in dart with setModuleHandler.
A global function channel is presented to invoke dart function. Data conversion between dart and js are implemented as follow:
| dart | js |
|---|---|
| Bool | boolean |
| Int | number |
| Double | number |
| String | string |
| Uint8List | ArrayBuffer |
| List | Array |
| Map | Object |
| JSFunction(...args) IsolateJSFunction(...args) |
function(....args) |
| Future | Promise |
notice: function can only be sent from js to dart. IsolateJSFunction always returns asynchronously.
Getting Started
Run on main thread
- Create a
FlutterQjsobject, pass handlers to implement js-dart interaction and resolving modules. For example, you can useDioto implement http in js:
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");
}
},
moduleHandler: (String module) {
if(module == "hello")
return "export default (name) => `hello \${name}!`;";
throw Exception("Module Not found");
},
);
in javascript, channel function is equiped to invoke methodHandler, make sure the second parameter is a list:
channel("http", ["http://example.com/"]);
import function is used to get modules:
import("hello").then(({default: greet}) => greet("world"));
notice: To use async function in module handler, try Run on isolate thread
- Then call
dispatchto dispatch event loop.
engine.dispatch();
- Use
evaluateto run js script, now you can use it synchronously, or use await to resolvePromise:
try {
print(engine.evaluate(code ?? ''));
} catch (e) {
print(e.toString());
}
- Method
closecan destroy quickjs runtime that can be recreated again if you callevaluate.
Run on isolate thread
- Create a
IsolateQjsobject, pass handlers to implement js-dart interaction and resolving modules. ThemethodHandleris used in isolate, so the handler function must be a top-level function or a static method. Async function such asrootBundle.loadStringcan be used now to get module:
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();
- Same as run on main thread, use
evaluateto run js script. In this way,Promisereturn byevaluatewill be automatically tracked and return the resolved data:
try {
print(await engine.evaluate(code ?? '', "<eval>"));
} catch (e) {
print(e.toString());
}
- Method
closecan destroy quickjs runtime that can be recreated again if you callevaluate.
This example contains a complete demonstration on how to use this plugin.
For Mac & IOS developer
I am new to Xcode and iOS developing, and I cannot find a better way to support both simulators and real devices without combining the binary frameworks. To reduce build size, change the s.vendored_frameworks in ios/flutter_qjs.podspec to the specific framework.
For simulator, use:
s.vendored_frameworks = `build/Debug-iphonesimulator/ffiquickjs.framework`
For real device, use:
s.vendored_frameworks = `build/Debug-iphoneos/ffiquickjs.framework`
Two additional notes:
-
quickjs built with
releaseconfig has bug in resolvingPromise. Please let me know if you know the solution. -
ios/make.shlimit the build architectures to avoid combine conflicts. Change themake.shto support another architectures.