Flutter代码生成插件source_gen的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter代码生成插件source_gen的使用

Overview

source_gen 提供了用于Dart的自动源代码生成工具:

  • 框架:用于编写消费和生产Dart代码的Builder。
  • 约定:为人类和工具生成的Dart代码提供清晰分离,以及在同一项目中集成多个代码生成器。

其主要目的是在较低级别的包(如analyzerbuild)之上暴露一个开发者友好的API。虽然不是必须使用source_gen来生成源代码,但它提供了一套可能对你的生成器有用的库API。

Quick Start Guide for writing a Generator

添加依赖

在你的pubspec.yaml文件中添加source_gen的依赖项。如果你只在自己的项目中使用source_gen生成代码,并且不会将生成器发布给其他人使用,则可以将其作为dev_dependency添加:

dev_dependencies:
  source_gen: ^1.1.0

编写生成器

通过继承GeneratorGeneratorForAnnotation类并让source_gen调用你的生成器来生成Dart库或库中带有你感兴趣的注解的每个元素。

示例:简单的注解生成器

下面是一个简单的示例,展示了如何创建一个基于注解的生成器。这个生成器会根据自定义注解@JsonSerializable来自动生成JSON序列化代码。

首先,在lib/json_serializable.dart中定义注解:

library json_serializable;

class JsonSerializable {
  const JsonSerializable();
}

然后,在lib/src/json_serializable_generator.dart中实现生成器:

import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';

class JsonSerializableGenerator extends GeneratorForAnnotation<JsonSerializable> {
  @override
  generateForAnnotatedElement(element, constantReader, BuildStep buildStep) {
    // 在这里实现你的生成逻辑
    return '''
      String toJson() => '{"name": "\$name"}';
      factory ${element.name}.fromJson(String jsonString) => null;
    ''';
  }
}

最后,在lib/builder.dart中配置生成器:

import 'package:build_runner_core/build_runner_core.dart' as _i1;
import 'package:source_gen/source_gen.dart' as _i2;
import 'package:json_serializable/src/json_serializable_generator.dart' as _i3;
import 'dart:isolate' as _i4;
import 'package:build_runner/build_runner.dart' as _i5;
import 'dart:io' as _i6;

final List<_i1.BuilderApplication> builders = [
  _i1.apply(
    r'source_gen:combining_builder',
    [_i2.SharedPartBuilder([_i3.JsonSerializableGenerator()], 'json_serializable')],
    _i1.toPart,
    hideOutput: true,
    appliesBuilders: ['source_gen:combining_builder']
  ),
];

配置和运行生成器

source_gen基于build包,并暴露选项以在Builder中使用你的Generator。选择一个Builder取决于你想让生成的代码放在哪里:

  • 如果你想写入.g.dart文件,这些文件将作为原始源文件中的part引用,使用SharedPartBuilder
  • 如果你想写入.some_name.dart文件,这些文件将作为原始源文件中的part引用,使用PartBuilder。你应该选择一个对你包唯一的扩展名。
  • 如果你想写入独立的Dart库文件,可以被import,使用LibraryBuilder。只能使用单个Generator作为LibraryBuilder

为了使Builderbuild_runner一起使用,它必须在build.yaml文件中配置。参见build_config了解更多信息。每当你要发布包含build.yaml文件的包时,应该在pubspec中包含对build_config的依赖。

当使用SharedPartBuilder时,它应该始终配置为build_to: cache(隐藏文件),并应用此包提供的combining_buildercombining_builder读取由不同共享部分构建器写入的所有片段,并将它们写入用户源目录中的最终.g.dart输出。你不应该使用任何其他Builder使用.g.dart扩展名。

build.yaml配置示例

builders:
  some_cool_builder:
    import: "package:this_package/builder.dart"
    builder_factories: ["someCoolBuilder"]
    # The `partId` argument to `SharedPartBuilder` is "some_cool_builder"
    build_extensions: {".dart": [".some_cool_builder.g.part"]}
    auto_apply: dependents
    build_to: cache
    # To copy the `.g.part` content into `.g.dart` in the source tree
    applies_builders: ["source_gen:combining_builder"]

Configuring combining_builder

ignore_for_file

有时生成的代码不支持目标包中指定的所有lints。当你使用基于package:source_genBuilder应用combining_builder时,设置ignore_for_file选项为一个你希望在所有生成库中忽略的lint列表。

preamble

当你使用基于package:source_genBuilder应用combining_builder时,设置preamble选项为你希望预先添加到所有生成库的字符串。

Generating files in different directories

你可以更改输入文件的输出位置:

  • 使用PartBuilderLibraryBuilder时。
  • 使用SharedPartBuilder并应用combining_builder作为构建的一部分时。

默认情况下,.g.dart.some_name.dart文件会生成在输入文件旁边。要更改这一点,可以在相应的构建器上设置build_extensions选项。在选项中,build_extensions是从StringString的地图,其中键匹配输入,值是单个构建输出。

例如,你可以使用这些选项在lib/generated下生成文件,如下所示的构建配置:

targets:
  $default:
    builders:
      # A SharedPartBuilder which uses the combining builder
      source_gen:combining_builder:
        options:
          build_extensions:
            '^lib/{{}}.dart': 'lib/generated/{{}}.g.dart'

      # A PartBuilder or LibraryBuilder
      some_cool_builder:
        options:
          build_extensions:
            '^lib/models/{{}}.dart': 'lib/models/generated/{{}}.foo.dart'

记得更改输入中的part语句以引用其他目录中的正确输出文件。

注意,构建器选项是source_gen的公共API!当你在构建配置中使用它们时,始终添加对source_gen的依赖。

FAQ

What is the difference between source_gen and build?

Build是一个平台无关的Dart资产或代码生成框架,可插入构建系统(包括bazel),以及像build_runner这样的独立工具。你也可以构建自己的。

同时,source_gen提供了易于使用的API和工具,用于简化基于build的常见任务。例如,PartBuilder类包装了一个或多个Generator实例,以创建part of文件,而LibraryBuilder类包装了一个Generator以创建Dart库文件。


更多关于Flutter代码生成插件source_gen的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter代码生成插件source_gen的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用source_gen插件的代码示例。source_gen是一个Dart包,用于在构建时生成源代码文件,通常用于生成与注解相关的代码。在Flutter项目中,你可以利用它来自动生成一些样板代码,如JSON序列化代码、数据模型代码等。

首先,你需要在你的pubspec.yaml文件中添加source_gen和相关的依赖,比如json_serializable(用于生成JSON序列化和反序列化的代码)。

dependencies:
  flutter:
    sdk: flutter
  json_annotation: ^4.0.1  # 这是必须的,因为json_serializable依赖于它

dev_dependencies:
  build_runner: ^2.1.4  # Flutter构建工具,用于运行代码生成器
  json_serializable: ^6.1.4  # 用于生成JSON序列化和反序列化的代码
  source_gen: ^1.0.0  # 源代码生成插件

然后,你需要一个Dart文件,比如models/user.dart,用于定义你的数据模型,并使用@JsonSerializable()注解。

// models/user.dart
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';  // 这行告诉构建工具生成的代码应该放在哪个文件里

@JsonSerializable()
class User {
  final String name;
  final int age;

  User({required this.name, required this.age});

  // 生成的fromJson方法会放在user.g.dart文件中
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  // 生成的toJson方法会放在user.g.dart文件中
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

接下来,你需要在项目根目录下运行flutter pub get来获取依赖项,然后运行flutter pub run build_runner build来生成代码。这个命令会读取注解并生成相应的代码文件。

flutter pub get
flutter pub run build_runner build

运行上述命令后,你应该会在models/目录下看到一个名为user.g.dart的文件,这个文件包含了User类的fromJsontoJson方法的实现。

// models/user.g.dart (自动生成)
part of 'user.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

User _$UserFromJson(Map<String, dynamic> json) {
  return User(
    name: json['name'] as String,
    age: json['age'] as int,
  );
}

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
  'name': instance.name,
  'age': instance.age,
};

现在,你可以在项目的其他地方使用User类,并轻松地进行JSON序列化和反序列化。

import 'package:flutter/material.dart';
import 'models/user.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 创建一个User对象
    User user = User(name: 'John Doe', age: 30);

    // 将User对象序列化为JSON
    Map<String, dynamic> userJson = user.toJson();
    print('User JSON: $userJson');

    // 从JSON反序列化为User对象
    User userFromJson = User.fromJson(userJson);
    print('User from JSON: ${userFromJson.name}, Age: ${userFromJson.age}');

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Source Gen Example'),
        ),
        body: Center(
          child: Text('Check console for JSON serialization/deserialization output'),
        ),
      ),
    );
  }
}

这个示例展示了如何使用source_gen插件和json_serializable注解来生成JSON序列化和反序列化的代码。你可以根据需要扩展这个示例,以生成更多类型的代码。

回到顶部