Flutter 热更新无侵入方案(生成运行时库)
Flutter 热更新无侵入方案(生成运行时库)
导读
已经很久没有写相关的文章了,主要在于每天研究时间很短,二是忙着研究怎么生成运行时库。虽然目前还有很多没有实现,我现在先同步一个大概的完成实现吧。
基本完成对于下面的运行库生成的支持
- 类只读属性的获取
- 类设置属性的设置
- 类初始化方法调用
- 类方法的调用
- 类静态属性的获取
- 类静态属性的设置
- 类方法的调用
- 全局属性的获取
- 全局属性的设置
- 全局方法的调用
- 基础枚举值的获取
分析当前库的所有依赖
pub get
对于需要分析的库中将所有库生成支持运行时支持的库,需要分析出来所有的依赖库列表,这就需要我们提前执行pub get
拉取所有的依赖库。
读取 package_config.json 里面的信息
在~/.dart_tool/package_config.json
这个文件可以获取到最新的依赖的信息,主要包括如下的信息。
- configVersion 当前配置版本号
- generated 配置生成的具体时间
- generator 配置生成者
- generatorVersion 生成者的对应 Dart 版本
- packages 依赖库列表
- name 库名称
- rootUri 库对应的本地地址
- packageUri 库源文件对应的路径
- languageVersion 当前库要求的 Dart 版本
将配置的 JSON 信息转换成对象
import 'package:darty_json_safe/darty_json_safe.dart';
class PackageConfig {
late int configVersion;
late String generated;
late String generator;
late String generatorVersion;
late List<PackageInfo> packages;
PackageConfig.fromJson(Map<String, dynamic> json) {
final jsonValue = JSON(json);
configVersion = jsonValue["configVersion"].intValue;
generated = jsonValue["generated"].stringValue;
generator = jsonValue["generator"].stringValue;
generatorVersion = jsonValue["generatorVersion"].stringValue;
packages = jsonValue["packages"]
.listValue
.map((e) => PackageInfo.fromJson(e))
.toList();
}
}
class PackageInfo {
late String name;
late String rootUri;
late String packageUri;
late String languageVersion;
PackageInfo.fromJson(Map<String, dynamic> json) {
final jsonValue = JSON(json);
name = jsonValue["name"].stringValue;
rootUri = jsonValue["rootUri"].stringValue;
packageUri = jsonValue["packageUri"].stringValue;
languageVersion = jsonValue["languageVersion"].stringValue;
}
}
这里我用到自己之前发布的一个库darty_json_safe来解析就没用到官方解析的库了。
生成运行库
我们将上面分析出来的依赖配置信息放在可以全局访问,用于后续方便我们查找将关联类型的类进行引入。
分析库的路径
我们从 PackageInfo 中字段 rootUri 可以拿到依赖库具体的详细的地址,每个地址都是一个对应版本库的依赖,比如我们拿着 yaml 这个依赖库为例子。
file:///Users/king/.pub-cache/hosted/pub.flutter-io.cn/yaml-3.1.2
对于上面的地址,我们不能直接拿来进行使用,我们需要去掉最开始的字符串file://
运行库生成地址
我们将生成的运行库存放在本机电脑 $HOME/.runtime
目录,这个 $HOME 参数我们可以引用 process_run
这个库通过下面的方法可以获取到
platformEnvironment["HOME"] // 获取本机 HOME 目录
分析代码
具体分析的代码可以参考下面的文件
AnalysisContextCollection
为了可以拿到当前分析库的代码信息,我们需要通过库 analyzer 中 AnalysisContextCollection 类来分析代码信息。
AnalysisContextCollection(
sdkPath: getDartPath(),
includedPaths: [join(packagePath, "lib")],
);
第一个参数是本地 Dart 库的地址,不设置分析就会报错。
第二个参数是分析源代码的文件夹或者具体文件路径
Dart 库的地址
我们可以通过下面的代码进行获取
String getDartPath() {
// 获取当前 Dart 命令的具体路径
String dartCommandPath = whichSync("dart") ?? "";
// 将获取的 Dart 路径获取到当前文件夹 之后添加 cache/dart-sdk
return join(dirname(dartCommandPath), "cache", "dart-sdk");
}
分析当前库的 pubspec 信息
可以引入 pubspec_parse 这个库来分析 pubspec.yaml 的信息。
final sourceFile = join(packagePath, "pubspec.yaml");
pubspec = Pubspec.parse(await File(sourceFile).readAsString());
分析代码文件
我们从提供的库路径获取到目录下面所有的源文件,我们默认为所有的的依赖库的源文件都在 lib 这个目录下。
// 获取到当前需要分析目录下面所有的子元素
List<FileSystemEntity> entitys = await Directory(dir).list(recursive: true).toList();
通过 AnalysisContext 对象拿到对应代码文件的分析结果
AnalysisContext context = analysisContextCollection.contextFor(path);
SomeResolvedLibraryResult result = await context.currentSession.getResolvedLibrary(path);
SomeResolvedLibraryResult 存在有多个子类,我们暂时只分析 ResolvedLibraryResult 这个子类的内容。
分析 Class
获取当前代码文件所有可以被公开访问的类
final classes = result.element.units[0].classes.where((element) {
return !element.name.isPrivate;
});
bool get isPrivate => startsWith("_");
虽然 result.element.units 是一个数组 但是目前还没有遇到存在两个的,所以就暂时用一个座位分析研究
每一个类分析出来都是一个 ClassElement 对象,我们可以从这里面找到需要的信息。
- String name // 获取类名称
- List<FieldElement> fields // 获取类的属性
- PropertyAccessorElement getter // 获取只读的属性
- PropertyAccessorElement setter // 获取可以设置的属性
- List<MethodElement> methods // 获取类的方法
- List<ConstructorElement> constructors // 获取类的构造方法
- Bool isAbstract // 是否是抽象类
分析 PropertyAccessorElement
- String name // 属性名称
- String isStatic // 是否是静态属性
对于属性的名称我们要处理本身就带有 $和=号的特殊字符
对于 $ 我们可以添加 \进行转移
对于尾部带有 = 的我们需要将 = 号进行移除
分析 MethodElement
- String name // 获取方法名称
- List<ParameterElement> parameters // 获取方法参数列表
分析 ConstructorElement
- String name // 构造方法名称可能为空
- List<ParameterElement> parameters // 构造的参数列表
分析 ParameterElement
- String name // 参数名称
- bool isNamed // 是否是名字参数
- bool hasDefaultValue // 是否有默认值
- String defaultValueCode // 默认值
分析 FunctionElement
- String name // 全局方法名称
- List<ParameterElement> parameters // 全局方法参数
分析 EnumElement
- String name // 枚举名称
- List<FieldElementImpl> constructors // 枚举值
- String name 枚举值名称
生成代码
我们已经通过分析代码拿到了基本的信息,剩下的我们通过获取的值通过 Mustache 模版语法来实现我们的生成代码。
对于 Mustache 我们可以通过 mustache_template 这个库来提供支持。
flutter_runtime
flutter_runtime是提供运行时基础能力的,后续会根据需求调整。
abstract class FlutterRuntime<T> {
// 当前运行类的实例
final T runtime;
FlutterRuntime(this.runtime);
// 获取当前类公开只读的属性
dynamic getField(String fieldName);
// 设置类公开的设置属性
void setField(String fieldName, dynamic value);
// 执行当前类的公开方法
dynamic call(String methodName, [Map args = const {}]);
// 根据类名创建当前类实例
dynamic createInstance(
String packageName,
String libraryPath,
String className, [
Map args = const {},
]) {
return null;
}
}
我们生成的每一个运行时对象都要基于这个抽象类来生成。
pubspec.yaml
对于生成的运行时库,我们需要创建一个 pubspec.yaml
name: {{pubName}}_runtime
environment:
sdk: '>=2.18.0 <3.0.0'
dependencies:
flutter_runtime:
path: {{{flutterRuntimePath}}}
{{pubName}}:
path: {{{pubPath}}}
darty_json_safe: ^1.0.1
-
pubName
源包名
-
flutterRuntimePath
flutter_runtime 依赖的地址
-
pubPath
源包依赖的地址
生成运行时代码文件
// ignore_for_file: implementation_imports, unused_import
import 'dart:async';
import 'package:flutter_runtime/flutter_runtime.dart';
import 'package:darty_json_safe/darty_json_safe.dart';
# 根据提供的依赖路径生成需要导入的依赖
{{#paths}}
import '{{{sourcePath}}}';
{{/paths}}
# 生成运行时 Class
{{#classes}}
{{>classMustache}}
{{/classes}}
生成运行时 Class
class \${{className}}\$ extends FlutterRuntime<{{className}}>{
# 生成必要的运行时构造器
\${{className}}\$(super.runtime);
# 生成只读属性的运行时方法
{{>getFieldMustache}}
# 生成设置属性的运行时方法
{{>setFieldMustache}}
# 生成方法运行时调用
{{>methodMustache}}
# 生成构造方法的运行时调用
{{>constructorMustache}}
}
只读属性运行时方法
@override
dynamic getField(String fieldName) {
# 便利只读属性的列表
{{#getFields}}
# 是静态的属性
{{#isStatic}}
if (fieldName == "{{fieldName}}") return {{className}}.{{fieldName}};
{{/isStatic}}
# 不是静态属性
{{^isStatic}}
if (fieldName == "{{fieldName}}") return runtime.{{fieldName}};
{{/isStatic}}
{{/getFields}}
}
设置属性的运行时方法
@override
void setField(String fieldName, dynamic value) {
# 设置属性的列表
{{#setFields}}
{{#isStatic}}
if (fieldName == "{{fieldName}}") {{className}}.{{fieldName}} = value;
{{/isStatic}}
{{^isStatic}}
if (fieldName == "{{fieldName}}") runtime.{{fieldName}} = value;
{{/isStatic}}
{{/setFields}}
}
方法的运行时方法
@override
dynamic call(String methodName,[Map args = const {}]) {
# 类中的方法列表
{{#methods}}
{{>functionMustache}}
{{/methods}}
}
# functionMustache
# 是否是自定义调用代码 针对一些[]这种数组和字典的方式
{{#isCustomCall}}
if (methodName == '{{methodName}}') return {{{customCallCode}}};
{{/isCustomCall}}
{{^isCustomCall}}
if (methodName == '{{methodName}}') return runtime.{{methodName}}(
# 方法的参数列表
{{#parameters}}
# 是名称参数
{{#isNamed}}
{{parameterName}}:{{>createInstanceMustache}}{{>defaultValueMustache}},
{{/isNamed}}
# 不是名称参数
{{^isNamed}}
{{>createInstanceMustache}}{{>defaultValueMustache}},
{{/isNamed}}
{{/parameters}}
);
{{/isCustomCall}}
构造方法的运行时方法
{{className}}? createRuntimeInstance(String constructorName,[Map args = const {},]) {
# 是否是抽象方法 抽象方法不支持构造函数
{{^isAbstract}}
# 构造方法列表
{{#constructors}}
if (constructorName == "{{constructorName}}")
return {{className}}{{#isName}}.{{constructorName}}{{/isName}}(
{{#parameters}}
{{#isNamed}}
{{parameterName}}:{{>createInstanceMustache}}{{>defaultValueMustache}},
{{/isNamed}}
{{^isNamed}}
{{>createInstanceMustache}}{{>defaultValueMustache}},
{{/isNamed}}
{{/parameters}}
);
{{/constructors}}
{{/isAbstract}}
return null;
}
全局调用的运行时
// ignore_for_file: implementation_imports, unused_import
import 'package:flutter_runtime/flutter_runtime.dart';
import 'package:darty_json_safe/darty_json_safe.dart';
{{#paths}}
import '{{{sourcePath}}}';
{{/paths}}
# 因为全局没有一个对应类 所以就直接用 dynamic 后面可以通过 null 进行创建
class \$GlobalRuntime\$ extends FlutterRuntime<dynamic> {
\$GlobalRuntime\$(super.runtime);
# 全局的方法 直接可以调用 不需要 runtime
dynamic call(String methodName, [Map args = const {}]) {
{{#functions}}
if (methodName == '{{methodName}}') return {{methodName}}(
{{#parameters}}
{{#isNamed}}
{{parameterName}}:{{>createInstanceMustache}}{{>defaultValueMustache}},
{{/isNamed}}
{{^isNamed}}
{{>createInstanceMustache}}{{>defaultValueMustache}},
{{/isNamed}}
{{/parameters}}
);
{{/functions}}
}
@override
getField(String fieldName) {
{{#getFields}}
if (fieldName == '{{fieldName}}') return {{fieldValue}};
{{/getFields}}
}
@override
void setField(String fieldName, value) {
{{#setFields}}
if (fieldName == "{{fieldName}}") {{fieldName}} = value;
{{/setFields}}
}
dynamic getEnumValue(String enumName, String constructorName) {
{{#enums}}
if (enumName == "{{enumName}}"){
{{#constructors}}
if (constructorName == '{{constructorName}}') {
return {{enumName}}.{{constructorName}};
}
{{/constructors}}
}
{{/enums}}
return null;
}
}
一些技术点
怎么让生成的代码不报错?
- 让生成的类所属的文件作为依赖引入
- 让默认值关联的对象所属的文件作为依赖引入
对于 [] 和 == 好方法的处理
String? get customCallCode {
if (name == '[]=' && parameters.length == 2) {
return '''runtime[args['${parameters[0].name}']] = args['${parameters[1].name}']''';
} else if (name == '==' && parameters.length == 1) {
return '''runtime == args['${parameters[0].name}']''';
} else {
return null;
}
}
如果判断方法名称为 [] 或者 == 就生成自定义的调用代码生成。
获取默认值对应对象所在的库文件路径
String? get defaultValueImportPath {
if (!hasDefaultValue || this is! DefaultParameterElementImpl) return null;
DefaultParameterElementImpl parameter = this as DefaultParameterElementImpl;
final constantInitializer = parameter.constantInitializer;
if (constantInitializer == null ||
constantInitializer is! PrefixedIdentifier) return null;
return constantInitializer.staticElement?.librarySource?.importPath;
}
extension SourceImport on Source {
String? get importPath {
final packages =
Get.find<HomeController>().packageConfig.value?.packages ?? [];
List<PackageInfo> infos = packages.where((element) {
return fullName.startsWith(element.rootUri.replaceFirst("file://", ""));
}).toList();
if (infos.isEmpty) return null;
PackageInfo info = infos[0];
final path = fullName.split("/lib/").last;
return 'package:${info.name}/$path';
}
}
运行库生成例子
普通类文件
// ignore_for_file: implementation_imports, unused_import
import 'dart:async';
import 'package:flutter_runtime/flutter_runtime.dart';
import 'package:darty_json_safe/darty_json_safe.dart';
import 'package:yaml/src/event.dart';
class $Event$ extends FlutterRuntime<Event> {
$Event$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "type") return runtime.type;
if (fieldName == "span") return runtime.span;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {
if (methodName == 'toString') return runtime.toString();
}
Event? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return Event(
args['type'],
args['span'],
);
return null;
}
}
class $DocumentStartEvent$ extends FlutterRuntime<DocumentStartEvent> {
$DocumentStartEvent$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "span") return runtime.span;
if (fieldName == "versionDirective") return runtime.versionDirective;
if (fieldName == "tagDirectives") return runtime.tagDirectives;
if (fieldName == "isImplicit") return runtime.isImplicit;
if (fieldName == "type") return runtime.type;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {
if (methodName == 'toString') return runtime.toString();
}
DocumentStartEvent? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return DocumentStartEvent(
args['span'],
versionDirective: args['versionDirective'],
tagDirectives: args['tagDirectives'],
isImplicit: args['isImplicit'] ?? true,
);
return null;
}
}
class $DocumentEndEvent$ extends FlutterRuntime<DocumentEndEvent> {
$DocumentEndEvent$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "span") return runtime.span;
if (fieldName == "isImplicit") return runtime.isImplicit;
if (fieldName == "type") return runtime.type;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {
if (methodName == 'toString') return runtime.toString();
}
DocumentEndEvent? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return DocumentEndEvent(
args['span'],
isImplicit: args['isImplicit'] ?? true,
);
return null;
}
}
class $AliasEvent$ extends FlutterRuntime<AliasEvent> {
$AliasEvent$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "span") return runtime.span;
if (fieldName == "name") return runtime.name;
if (fieldName == "type") return runtime.type;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {
if (methodName == 'toString') return runtime.toString();
}
AliasEvent? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return AliasEvent(
args['span'],
args['name'],
);
return null;
}
}
class $ScalarEvent$ extends FlutterRuntime<ScalarEvent> {
$ScalarEvent$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "span") return runtime.span;
if (fieldName == "anchor") return runtime.anchor;
if (fieldName == "tag") return runtime.tag;
if (fieldName == "value") return runtime.value;
if (fieldName == "style") return runtime.style;
if (fieldName == "type") return runtime.type;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {
if (methodName == 'toString') return runtime.toString();
}
ScalarEvent? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return ScalarEvent(
args['span'],
args['value'],
args['style'],
anchor: args['anchor'],
tag: args['tag'],
);
return null;
}
}
class $SequenceStartEvent$ extends FlutterRuntime<SequenceStartEvent> {
$SequenceStartEvent$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "span") return runtime.span;
if (fieldName == "anchor") return runtime.anchor;
if (fieldName == "tag") return runtime.tag;
if (fieldName == "style") return runtime.style;
if (fieldName == "type") return runtime.type;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {}
SequenceStartEvent? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return SequenceStartEvent(
args['span'],
args['style'],
anchor: args['anchor'],
tag: args['tag'],
);
return null;
}
}
class $MappingStartEvent$ extends FlutterRuntime<MappingStartEvent> {
$MappingStartEvent$(super.runtime);
@override
dynamic getField(String fieldName) {
if (fieldName == "span") return runtime.span;
if (fieldName == "anchor") return runtime.anchor;
if (fieldName == "tag") return runtime.tag;
if (fieldName == "style") return runtime.style;
if (fieldName == "type") return runtime.type;
}
@override
void setField(String fieldName, dynamic value) {}
@override
dynamic call(String methodName, [Map args = const {}]) {}
MappingStartEvent? createRuntimeInstance(
String constructorName, [
Map args = const {},
]) {
if (constructorName == "")
return MappingStartEvent(
args['span'],
args['style'],
anchor: args['anchor'],
tag: args['tag'],
);
return null;
}
}
全局运行时
// ignore_for_file: implementation_imports, unused_import
import 'package:flutter_runtime/flutter_runtime.dart';
import 'package:darty_json_safe/darty_json_safe.dart';
import 'package:yaml/src/utils.dart';
import 'package:yaml/src/charcodes.dart';
import 'package:yaml/yaml.dart';
import 'package:yaml/src/equality.dart';
import 'package:yaml/src/yaml_node.dart';
import 'package:yaml/src/event.dart';
import 'package:yaml/src/token.dart';
class $GlobalRuntime$ extends FlutterRuntime<dynamic> {
$GlobalRuntime$(super.runtime);
dynamic call(String methodName, [Map args = const {}]) {
if (methodName == 'loadYaml')
return loadYaml(
args['yaml'],
sourceUrl: args['sourceUrl'],
recover: args['recover'] ?? false,
errorListener: args['errorListener'],
);
if (methodName == 'loadYamlNode')
return loadYamlNode(
args['yaml'],
sourceUrl: args['sourceUrl'],
recover: args['recover'] ?? false,
errorListener: args['errorListener'],
);
if (methodName == 'loadYamlDocument')
return loadYamlDocument(
args['yaml'],
sourceUrl: args['sourceUrl'],
recover: args['recover'] ?? false,
errorListener: args['errorListener'],
);
if (methodName == 'loadYamlStream')
return loadYamlStream(
args['yaml'],
sourceUrl: args['sourceUrl'],
);
if (methodName == 'loadYamlDocuments')
return loadYamlDocuments(
args['yaml'],
sourceUrl: args['sourceUrl'],
);
if (methodName == 'warn')
return warn(
args['message'],
args['span'],
);
if (methodName == 'deepEqualsMap') return deepEqualsMap();
if (methodName == 'deepEquals')
return deepEquals(
args['obj1'],
args['obj2'],
);
if (methodName == 'deepHashCode')
return deepHashCode(
args['obj'],
);
if (methodName == 'setSpan')
return setSpan(
args['node'],
args['span'],
);
}
@override
getField(String fieldName) {
if (fieldName == 'yamlWarningCallback') return yamlWarningCallback;
if (fieldName == '\$plus') return $plus;
if (fieldName == '\$minus') return $minus;
if (fieldName == '\$dot') return $dot;
if (fieldName == '\$0') return $0;
if (fieldName == '\$9') return $9;
if (fieldName == '\$F') return $F;
if (fieldName == '\$N') return $N;
if (fieldName == '\$T') return $T;
if (fieldName == '\$f') return $f;
if (fieldName == '\$n') return $n;
if (fieldName == '\$o') return $o;
if (fieldName == '\$t') return $t;
if (fieldName == '\$x') return $x;
if (fieldName == '\$tilde') return $tilde;
}
@override
void setField(String fieldName, value) {
if (fieldName == "yamlWarningCallback") yamlWarningCallback = value;
}
dynamic getEnumValue(String enumName, String constructorName) {
if (enumName == "EventType") {
if (constructorName == 'streamStart') {
return EventType.streamStart;
}
if (constructorName == 'streamEnd') {
return EventType.streamEnd;
}
if (constructorName == 'documentStart') {
return EventType.documentStart;
}
if (constructorName == 'documentEnd') {
return EventType.documentEnd;
}
if (constructorName == 'alias') {
return EventType.alias;
}
if (constructorName == 'scalar') {
return EventType.scalar;
}
if (constructorName == 'sequenceStart') {
return EventType.sequenceStart;
}
if (constructorName == 'sequenceEnd') {
return EventType.sequenceEnd;
}
if (constructorName == 'mappingStart') {
return EventType.mappingStart;
}
if (constructorName == 'mappingEnd') {
return EventType.mappingEnd;
}
}
if (enumName == "TokenType") {
if (constructorName == 'streamStart') {
return TokenType.streamStart;
}
if (constructorName == 'streamEnd') {
return TokenType.streamEnd;
}
if (constructorName == 'versionDirective') {
return TokenType.versionDirective;
}
if (constructorName == 'tagDirective') {
return TokenType.tagDirective;
}
if (constructorName == 'documentStart') {
return TokenType.documentStart;
}
if (constructorName == 'documentEnd') {
return TokenType.documentEnd;
}
if (constructorName == 'blockSequenceStart') {
return TokenType.blockSequenceStart;
}
if (constructorName == 'blockMappingStart') {
return TokenType.blockMappingStart;
}
if (constructorName == 'blockEnd') {
return TokenType.blockEnd;
}
if (constructorName == 'flowSequenceStart') {
return TokenType.flowSequenceStart;
}
if (constructorName == 'flowSequenceEnd') {
return TokenType.flowSequenceEnd;
}
if (constructorName == 'flowMappingStart') {
return TokenType.flowMappingStart;
}
if (constructorName == 'flowMappingEnd') {
return TokenType.flowMappingEnd;
}
if (constructorName == 'blockEntry') {
return TokenType.blockEntry;
}
if (constructorName == 'flowEntry') {
return TokenType.flowEntry;
}
if (constructorName == 'key') {
return TokenType.key;
}
if (constructorName == 'value') {
return TokenType.value;
}
if (constructorName == 'alias') {
return TokenType.alias;
}
if (constructorName == 'anchor') {
return TokenType.anchor;
}
if (constructorName == 'tag') {
return TokenType.tag;
}
if (constructorName == 'scalar') {
return TokenType.scalar;
}
}
return null;
}
}
备注
目前生成运行时的能力还很弱,比如后续将生成对象整理成注册表。还有其他类型的生成支持,还有将代码转换为可以动态运行的 JSON 文件来运行,包括后续解析运行库来支持闭包,if/for 等。
我非常希望有感兴趣的一起加入这个社区,让 Flutter 动态化的实现方案更多选择,让接入和侵入更低。
转载自:https://juejin.cn/post/7246676109613416503