Flutter -根据Dart构建系统来自定义代码生成库

文章目录[x]
  1. 1:Dart构建系统
  2. 2:自定义Dart包来生成代码
  3. 2.1:创建Dart package
  4. 2.2:buildExtensions
  5. 2.3:创建flutter项目来引用该库

在平时flutter开发过程中,我们经常会需要写一些构建脚本来批量生成代码或者批量处理一些功能,就像json_serializable一样,可以自动去生成Dart代码。

Dart构建系统

Dart构建系统主要包含如下几个库:

  • build  这个库主要定义构建如何发生以及它们如何交互的基本部分:如Builder、BuildStep等。
  • build_config  这个库主要是自定义包的构建行为,通过创建 build.yaml 文件来完成的build的相关配置。
  • build_runner  这个库主要是运行我们自定义的build。 文件始终直接在磁盘上生成,并且重建是增量式的。

其他还包含build_modules、build_resolvers等可以在其仓库中查看。

自定义Dart包来生成代码

在上面的代码中,我们简单介绍了Dart build构建系统,我们可以先简单创建一个自动生成代码库来熟悉Dart build构建系统。在平时开发过程中,我们经常使用Image.asset来加载图片,我们需要手动来敲对应的文件夹和路径。

Image.asset('images/ic_logo.png');

 

现在,我们可以编写一个库来从images文件夹中图片的名字自动生成lib/generate/R.dart,就跟安卓一样的R类一样来引用资源。

// Generated, do not edit 
 
class R{
 static String ic_anim_man = "images/ic_anim_man.png"; 
 static String ic_anim_woman = "images/ic_anim_woman.png"; 
 static String ic_cat = "images/ic_cat.png"; 
}

 

创建Dart package

可以直接通过VSCode来自动生成r_generate包,在生成成功后添加build系统的依赖:

name: r_generate
description: Rely on the Dart build system to generate references to image resources.
version: 1.0.0

environment:
sdk: ^3.3.4

dependencies:
build:
build_config:
dev_dependencies:
build_runner:

生成的包结构主要如下:

- r_generate
    - lib
      - r_generate.dart
        - src
          - builder.dart
          - r_generate_base.dart
    - pubspec.yaml
    - build.yaml

 

  • 在r_generate.dart中,主要是定义lib并导出r_generate_base.dart文件:
library;

export 'src/r_generate_base.dart';
  • r_generate_base.dart文件主要是编写自定义Builder的逻辑代码
 

import 'package:build/build.dart';
import 'package:glob/glob.dart';

 

class ResBuilder implements Builder {
@override
Map<String, List<String>> get buildExtensions => const {
r'lib/$lib$': ['lib/generated/R.dart'],
};

 

@override
Future<void> build(BuildStep buildStep) async {
final List<String> imageName = [];

await for (final input in buildStep.findAssets(Glob('images/**'))) {
imageName.add(input.path);
}

// 构建输出 Dart 文件
final outputId = AssetId(buildStep.inputId.package, 'lib/generated/R.dart');
final outputBuffer = StringBuffer('// Generated, do not edit \n');
outputBuffer.write("// ignore_for_file: non_constant_identifier_names\n \n");

outputBuffer.write("class R{\n");
imageName.forEach((element) {
final name = element.split("/");
outputBuffer.write(" static String ${name[1].split(".")[0]} = \"$element\"; \n");
});
outputBuffer.write("}");

 

// 将生成的 Dart 代码写入输出文件
await buildStep.writeAsString(outputId, outputBuffer.toString());
}
}

编写的ResBuilder继承Builder,重写了一个属性buildExtensions 和一个方法。

buildExtensions

这个地方主要是定义源文件和要生成的目标文件,比如下面的代码就像json_serializable一样,此构建器的目标是将源文件.dart结尾的文件生成一份.g.dart结尾的文件。

Map<String, List<String>> get buildExtensions {
 return const {
 '.dart': const ['.g.dart'],
 };
}

 

捕获组

在上面的例子中,从源文件生成目标文件都是在同一个文件夹中构建的如果想要源文件和目标文件之间文件夹不同,则需要 用到捕获组{{}}。

@override
 Map<String, List<String>> get buildExtensions => const {
 '^assets/{{}}.json': ['lib/generated/{{}}.dart'],
 };

 

上面的代码为了实现目录移动,构建器使用捕获组{{}}。 捕获组可以匹配输入路径中的任何内容,包括子目录。 开头的^assets确保仅考虑顶级assets/文件夹下的json。

捕获组有如下特点:

  • 捕获组至少匹配输入路径中的一个字符,但也可以匹配任意多个字符。
  • 当输入使用捕获组时,每个输出也必须引用该捕获组。
  • 有关构建扩展匹配后缀的一般规则仍然适用。使用捕获组时,后缀通常跨越多个路径组件,这就是启用目录移动的原因。
  • 使用捕获组构建扩展可以首先^对整个输入强制匹配。

聚合构建器

在上面介绍的例子中,都是1对1或者1对多去生成1份或者多份代码,输入数量是固定1个。我们现在的需求是需要根据images文件夹下面的所有图片文件来生成一份代码。我们抽象地将其称为聚合构建器,或具有多个输入和一个(或更少)输出的构建器

聚合构建器中有一个输出文件。因此,我们将构建一个合成输入 ,是用作构建扩展的标识符。目前,支持以下合成文件用于此目的:

  • lib/$lib$
  • $package$

我们在使用这两个标识符的时候主要考虑如下几点:

  • 它们应该写入哪个目录
    • 如果想输出到除lib之外的目录,应该使用 $package$.
    • 如果只想输出到lib文件夹之下,则使用lib/$lib$.
  • 该构建器将在哪些包上运行(仅限根包或依赖关系树中的任何包)。
    • 如果想在根目录以外的任何包上运行,则必须使用lib/$lib$因为只能lib能从依赖项访问根目录下的文件 。

在我们最开始的代码中就使用了聚合构建器,生成的目标文件只为一个lib/generated/R.dart文件:

@override
 Map<String, List<String>> get buildExtensions => const {
 r'lib/$lib$': ['lib/generated/R.dart'],
 };

 

可以通过buildStep的findAssets方法来找到源文件中匹配的Steam,我们匹配的是images目录下所有的文件:

buildStep.findAssets(Glob('images/**'))

 

builder.dart

import 'package:build/build.dart';
import 'r_generate_base.dart';

Builder resBuilder(BuilderOptions options) => ResBuilder();

在builder.dart文件中的代码很简单,就是添加了一个构建方法。

build.yaml

在添加好如上代码后,就可以创建build.yaml构建脚本配置:

builders:
resBuilder:
import: "package:r_generate/src/builder.dart"
builder_factories: ["resBuilder"]
build_extensions: {"lib/$lib$": ["lib/generated/R.dart"]}
auto_apply: root_package
build_to: source

上面的信息表示:

  • import  该构建器构建方法所在的dart文件
  • builder_factories 构建器构建方法的名字
  • build_extensions 自定义构建器buildExtensions相应的属性,必须一致
  • auto_apply 针对根包自动运行
  • build_to  构建到源码中

具体的配置信息可以参考官方文档

创建flutter项目来引用该库

通过VSCode在r_generate包同一目录创建flutter项目,引入改构建器包,并在image文件夹下添加多张图片,并打开flutter assest下的图片配置:

dependencies:
flutter:
sdk: flutter
r_generate:
path: ../r_generate

assets:
- images/

添加目标源

我们编写的构建的目标源为lib/$lib$,buildStep.findAssets(Glob('images/**'))去匹配的时候只会lib文件夹下的内容,如果想让images/文件夹也添加到源文件扫描路径中则需要添加一个根目录下build.ymal配置:

targets:
 $default:
 sources:
 - images/**
 # Note that it is important to include these in the default target.
 - pubspec.*
 - $package$
 - lib/$lib$

在配置完成后,就可以运行dart run build_runner build来自动生成R.dart文件了,当然也可以运行dart run build_runner watch来监听images文件夹的内容,删除或者添加都会重新构建新的文件。

使用:

Image.asset(R.ic_anim_man);

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00