flujs
js engine binding for flutter, powered by JavaScriptCore and QuickJS.
js engine | android | iOS | windows | linux | darwin | web |
---|---|---|---|---|---|---|
QuickJS | ✅ | ✅ | ✅ | ✅ | ✅ | 🔳 |
JavaScriptCore | 🔳 | ✅ | 🔳 | 🔳 | ✅ | 🔳 |
✅ : Support
🔳 : InProgress
features
- easily to use: js call native or native call js.
- supply xhr、promis、fetch、setTimeout、setInterval、console intrinsic functions.
- friendly dart api.
- support multiple JSRuntime concurrently running.
Getting Started
add flujs
、flujs_jsc
or flujs_qjs
dependences
flutter pub add flujs flujs_qjs flujs_jsc
1、get JSFRuntime
import 'package:flujs_qjs/qjs.dart' as qjs;
import 'package:flujs_jsc/jsc.dart' as jsc;
var rt = qjs.getJSFRuntime();
get different js engine implement as needed.
the jsc engine's advantage is mature
the qjs engine's advantage is small size
2、get JSFContext
var ctx = rt.newContext();
3、load intrinsic functions.
ctx.loadExtension();
requires an explicit call. you can also load different built-in functions on demand.
4、execute js code
var res = ctx.eval('''eval('2+2')''');
print(res.toString());
currently only support gloabl variable、global function.
5、js call native
// common function
ctx.addInterface('run', (args) {
debugPrint('[native] $args');
return 1;
});
// async function
ctx.addInterface('runAsync1', (args) async {
var completer = Completer<int>();
Timer(const Duration(seconds: 1), () {
completer.complete(1);
});
return completer.future;
});
ctx.addInterface('runAsync2', (args) async {
var r = await asyn2(1);
return r;
});
Future<int> asyn2(int delaySecs) async {
var completer = Completer<int>();
Timer(const Duration(seconds: delaySecs), () {
completer.complete(1);
});
return completer.future;
}
var res = ctx.eval('''
run('from js', 2);
runAsync1().then(() => runAsync2()).then(console.log);
3;
''');
debugPrint('runAsync: $res');
6、native call js
function injs(time) {
console.log('injs -> ', time);
}
var injs = ctx.globalObject.getProperty('injs');
assert(injs.isFunction());
injs.callAsFunction(arguments: [
JSFValueFactory.makeNumber(ctx, id)
]);
get global variable、global function in js with
context.globalContext
.if you want to isolate environment, just create different JSFContext
7、custom extension
class CustomExtension extends JSFExtension {}
the main purpose of extensions is to inject custom logic in advance in the context.
built-in xhr relies on the http library, and supports custom HttpClient so that other capabilities such as http cache, rpc, etc. can be implemented
class DioAdapter extends http.BaseClient {
final dio = Dio();
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
final response = await dio.requestUri(
request.url,
options: Options(headers: request.headers),
);
return http.StreamedResponse(response.data, response.statusCode ?? 400);
}
}
class LoggingInterceptor implements InterceptorContract {
@override
Future<http.BaseRequest> interceptRequest({
required http.BaseRequest request,
}) {
print('[Logging] 😯 request: make cache or something.');
return Future.value(request);
}
@override
Future<http.BaseResponse> interceptResponse({
required http.BaseResponse response,
}) {
print('[Logging] 😊 response: make cache or something.');
return Future.value(response);
}
@override
Future<bool> shouldInterceptRequest() {
return Future.value(true);
}
@override
Future<bool> shouldInterceptResponse() {
return Future.value(true);
}
}
var ctx = rt.newContext()..loadExtension(xhr: client == null);
final client = InterceptedClient.build(
interceptors: [LoggingInterceptor()],
);
final client = DioAdapter();
final client = HttpPackageAdapter();
ctx.extension
?.chain(XhrExtension(ctx, client_: client).chain(FetchExtension(ctx)))
.load();
Debug
you can use your own compiled qjs engine for debugging by manually setting dynamic library files or custom environment variables
# Android
replace libqjs.so in the /flujs_qjs/android/src/main/jniLibs directory
or
specify flujs.qjs_baseUrl=https://xxxx in the gradle.properties configuration file or environment variables, and libqjs.so package it as qjs_android_[abi].tar.gz
# iOS & macOS
copy libqjs.dylib to the /flujs_qjs/[ios|macos]/Frameworks/ directory
or
set QJS_BASEURL=https://xxx, libqjs.dylib to be packaged as qjs_iphoneos.tar.gz/qjs_macosx.tar.gz
# windows & linux
set QJS_BASEURL=https://xxx, libqjs.dll/libqjs.so to package as qjs_linux_abi.tar.gz/qjs_mingw_abi.tar.gz
specify the environment variable QJS_FORCE_DOWNLOAD=true to force the download from the remote end and not read the local cache dynamic library
quickJS Cross-Compilation Reference Documentation XMAKE QuickJS-ng Cross-Compilation Practices
(https://blog.dang8080.cn/2024/11/17/09/)
Work In Progress
x
esm module support.x
arrayBuffer/Blob support.fetch multiple response type support.
Known Bugs
x
toDart isArray cause crash.x
QJS xhr addInterface decode HttpHeader failed.x
QJS JSContext.create(rt,intrinsic: true) will cause crash.