This project is still in the development stage and there are still many bugs. It is not recommended to use it in the project. Currently, it only serves as a learning reference
The initial goal of this project was to develop a lightweight dart interpreter on Flutter that could dynamically execute dart code and enable hot updates on Flutter。
I don't want to introduce other languages like Lua, JavaScript, Just use Dart language。
I don't want to introduce some third-party language interpreters, such as LuaVM, JSCore。
I hope the bridging code can be automatically generated。
I hope the solution is lightweight, without so many private APIs, and the old code can be dynamically modified with minimal modifications. The new code can be integrated just like the Flutter plugin。
MicroDart compiler compiles dart code into dynamically runnable bytecode
you can test the compiler : micro_dart_compiler/test/test_all.dart
MicroDart interpreter, which can run bytecode
A code generator that generates MicroDart interpreter bridging code based on the current dart_sdk, giving the interpreter the ability to call non dynamic code, but it is not yet fully developed
The dark SDK bridging code generated by the code generator, and the proxy directory contains manually modified code that is currently not supported for generation
Flutter SDK bridging code, the proxy directory contains manually modified code that is currently not supported for generation
Some Flutter/Dart examples of dynamic execute code use of MicroDart
At present, it seems that there is a certain performance loss when using micro_dart. If the bridge code of the all Flutter SDK is connected, it will increase by about 30M in the software package and 2-50M in terms of running memory. If you want to test it specifically, you can try the examples.
- Before downloading the code, you need to download the dark sdk. The specific download method can be found in the document https://github.com/dart-lang/sdk/wiki/Building I recommend you to download it using dept_tools.
- I am using Dark SDK version 3.0.5. you need checkout the right version and then run gclient sync to download the related package
- Modify the path in micro_dart_compiler/pubspec.yaml to point to the dark SDK you downloaded
You can execute the following commands through command-line tools:
dart run micro_dart.dart.snapshot --verbose examples/flutter_plugin_2/lib/plugin_2.dart --op-out plugin_2_ops.txt --json-ast-out plugin_2_json.txt --ast-out plugin_2_ast.txt --external-methods-out external-methods-out.txt
If it cannot run, check if the dart version is 3.0.5
There are also relevant examples in the test directory of micro_dart_compiler that you can take a look at:
micro_dart_compiler/test/dart_example_1_generate.dart
micro_dart_compiler/test/flutter_plugin_1_generate.dart
micro_dart_compiler/test/flutter_plugin_2_generate.dart
micro_dart_compiler/test/flutter_plugin_animations_generate.dart
micro_dart_compiler/test/flutter_plugin_gallery_generate.dart
It can be partially dynamic, it can be a widget or a logical method. CallStatisticFunction is the entry method that calls dynamic code. It doesn't necessarily need to be called in the main function. For example, if you only want a certain widget to perform a hot update, you can call callStaticFunction to return the desired widget, which can be placed under a certain widget node. such as :
class BankListInfoPage extends StatelessWidget {
const BankListInfoPage({super.key});
@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("我的银行卡"),
),
body: engine.callStaticFunction(packageUri, "getMyBankWidget", [], {});
}
}
Micro_dart_generator is a code generator, which is a build_runner library integrated into the original program:
dev_dependencies:
micro_dart_generator:
path: ../../micro_dart_generator
Then in the command line, dart run build_runner will automatically generate bridging code
If you don't want to use build_runner, or just want to generate bridging code for dark core or flutter SDK, there are some codes below that can be used for reference in micro_dart_generator/bin.
If you don't want to modify it every time, you can modify overwrite_strategy.json. This file can overwrite some erroneous generated code. Or if you want to ignore some global packages, you can configure them in this file
At present, micro_dart_generator can be generated through configuration, and the configuration format can be referred to:
examples/flutter_example_gallery_2/micro_dart_external_methods.json
examples/flutter_animations_2/micro_dart_external_methods.json
examples/flutter_example_2/micro_dart_external_methods.json
According to this file, micro_dart_compiler has the ability to generate its own minimum amount when compiling dynamic code, which represents all external bridges called by the current plugin. The specific generation method can refer to:
micro_dart_compiler/test/flutter_plugin_2_generate.dart:
File("${flutterExamplePath}micro_dart_external_methods.json")
.writeAsStringSync(program.getExternalCallMethods());
The original program is separate from the dynamic code. Dynamic code is in the form of plugins, which depend on the original program。
For example, the example of flutter_gallery:
flutter_plugin_gallery is the code repository that we need to dynamically update
flutter_example_gallery is the original program and a non dynamic code repository
The following needs to be configured in the pubspec.yaml of flutter_plugin_gallery:
dependencies:
flutter:
sdk: flutter
flutter_example_gallery:
path: ../flutter_example_gallery
The following needs to be configured in the pubspec.yaml of flutter_example_gallery:
dependencies:
flutter:
sdk: flutter
micro_dart_flutter:
path: ../../micro_dart_flutter
micro_dart_runtime:
path: ../../micro_dart_runtime
The micro_dart_flutter here is the bridging code, and if you are also using Flutter 3.10.5, you can use it directly. But if it is another version of Flutter, you need to generate your own bridging code through micro_dart_generator, and there are subtle differences in the SDK for different Flutter versions. I also do not recommend introducing global flutter bridging because it still consumes a lot of memory and the compilation will be slow.
Dynamic code is distinguished by package, and the compiler can easily determine whether dynamic code needs to be generated based on the package name. Moreover, non packageName calls can be considered as external calls, which is convenient and simple to do. Another advantage of doing this is that if there is no need for dynamic in the future, this plugin can be directly introduced into the original program without any code modifications.
Sure, a dynamic plugin corresponds to a MicroDartEngine object, which is an interpreter environment. Multiple interpreters MicroDartEngine objects are allowed in the source program。
Not necessarily, you can put multiple pages in one dynamic plugin, it depends on yourself. However, the initialization of MicroDartEngine consumes resources, depending on the size of the dynamic code, which will be imported into memory at once. I understand that in scenarios where multiple MicroDartEngines are sensitive to memory, MicroDartEngines can be initialized when needed and destroyed when used up.