Flutter 自定义 build_runner 的 build

3,872 阅读2分钟

Flutter 自定义 build_runner 的 build

背景

在使用 json_serializable 库的过程,感觉还是要写很多的重复代码,然后去 github 搜索了一波有没有简单的工具可以替代我这些重复工作的过程,一找还发现真有: json_model。但是在使用这个的过程中,发现这个库是先根据json 生成类,然后在把 dart-lang/build 库里面很多代码搬过来使用 json_serializable 来生成对应的 json 序列化类。我觉得这种方式并不优雅,因为如果 json_serializable 升级了,那么 json_model 对于的代码也需要升级,而且这个库本来可以不依赖于 json_serializable 库的。

探索

于是我去看了 dart-lang/build 的文档,发现是可以自定义 build_runner 的。而且可以订阅 build_runner 中各个 build 间的依赖关系,那应该可以先利用一个 build 将json 转换成 Mode 类,然后在利用 json_serializable 生成 json 序列化类。

实施

  1. 引入依赖; 必须要依赖于 build 和 build_runner。
dependencies:
  build: ">=1.0.0 <1.2.0"

dev_dependencies:
  build_runner: ^1.3.1
  1. 因为我是直接把 json 文件转换为 dart 文件,在 json 文件中不能使用注解。因此直接写了一个类继承于 Builder 。
// file json_builder.dart
import 'dart:async';
import 'dart:convert';
import 'package:build/build.dart';
import './create_template_class.dart';

class JsonBuilder implements Builder {

  final CreateTemplateClass _createTemplateClass = CreateTemplateClass();

  @override
  Future build(BuildStep buildStep) async {
    var inputId = buildStep.inputId;

    var originalContents = await buildStep.readAsString(inputId);

    var copy = inputId.changeExtension('.dart');

    Map<String, dynamic> jsonInfo;
    try {
      jsonInfo = json.decode(originalContents) as Map<String, dynamic>;
    } catch (e) {
      log.warning('json decode json failed, please checked json file');
      return;
    }
    var content = _createTemplateClass.createContent(inputId.path, jsonInfo);
    await buildStep.writeAsString(copy, content);
  }

  @override
  final buildExtensions = const {
    '.json': ['.dart']
  };
}
// file create_template_class.dart
import 'package:path/path.dart' as path;

typedef String HandlerFun(dynamic v);

class CreateTemplateClass {

  final String withPrefiex = '@with';
  final String extendsPrefix = '@extends';
  final String importPrefix = '@import';
  Map<String, HandlerFun> _typeHandlers;

  CreateTemplateClass() {
    _typeHandlers = {
      importPrefix: (imports) {
        if (imports is List) {
          return imports
            .map((v) => "import '$v';")
            .join("\r\n");
        }
        return "import '$imports';";
      },
      extendsPrefix: (v) => "extends $v",
      withPrefiex: (v) => (v is List) ? 'with ${v.join(" ")}' : "with $v"
    };
  }

  String pascalClassName(String fileName) {
    return fileName?.split(RegExp(r'_|-'))?.map((word) => '${word[0].toUpperCase()}${word.substring(1)?.toLowerCase()}')?.join();
  }

  String handlerFiled(key, v) {
    if (v is String) {
      return (
            """
  final $v $key;

"""
      );
    } else {
      Map<String, dynamic> fieldInfo = v as Map<String, dynamic>;
      return (
            """
  ${fieldInfo['pre'] ?? ''}
  final ${fieldInfo['type']} $key;

"""
      );
    }
  }

  String createDartClass({
    String name,
    String import,
    String className,
    String attrs,
    String extendClass,
    String withClass,
    List<String> fieldNames,
  }) {
    return """
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// JsonModel Builder
// **
import 'package:json_annotation/json_annotation.dart';
$import
part '$name.g.dart';

@JsonSerializable()
class $className $extendClass $withClass{
  $className({
    ${fieldNames.map((field) => 'this.$field').join(',')}
  });

$attrs
  factory $className.fromJson(Map<String,dynamic> json) => _\$${className}FromJson(json);

  Map<String, dynamic> toJson() => _\$${className}ToJson(this);
}""";
  }

  // 根据Json生成Dart Class
  String createContent(String filePath, Map<String, dynamic> classConfigs) {
    var paths = path.basename(filePath).split(".");
    String name=paths.first;
    StringBuffer attrs = StringBuffer();
    var classInfos = {
      withPrefiex: '',
      importPrefix: '',
      extendsPrefix: '',
    };
    var fieldNames = List<String>();
    classConfigs.forEach((key, v) {
      if (_typeHandlers.containsKey(key)) {
        classInfos[key] = _typeHandlers[key](v);
      } else {
        attrs.write(handlerFiled(key, v));
        fieldNames.add(key);
      }
    });
    return createDartClass(
      name: name,
      import: classInfos[importPrefix],
      className: pascalClassName(name),
      attrs: attrs.toString(),
      extendClass: classInfos[extendsPrefix],
      withClass: classInfos[withPrefiex],
      fieldNames: fieldNames,
    );
  }
}
  1. 导出 Builder
// file builder.dart
import 'package:build/build.dart';
import 'json_builder.dart';

Builder jsonBuilder(BuilderOptions options) => JsonBuilder();

  1. 添加 build.yml 文件
builders:
  
  jsonBuilder:
    import: "package:json_to_dart_model/builder.dart"
    builder_factories: ["jsonBuilder"]
    build_extensions: {".json": ["**.dart"]}
    build_to: source
    auto_apply: root_package
    runs_before: ["json_serializable|json_serializable"]
    defaults:
      generate_for:
        # Only apply files in the lib/model_jsons directory
        include:
        - lib/model/**
  1. 在其他库中使用 参考:这里
  2. 源代码都在这里

参考资料

  1. Flutter 注解处理及代码生成
  2. Flutter-一行注解直接编译生成资源配置文件
  3. dart-lang/build