发布时间:2018年10月31日 - 7分钟阅读

在"【第1部分】Dart中的代码生成:基础知识 "中,我们介绍了代码生成背后的动机是什么,并列出了Dart中最重要的工具,让计算机为我们做艰苦的工作。在这篇文章中,我们将介绍如何创建和使用Dart注解,以及如何使用source_gen和build_runner开始处理这些注解。
Dart中的注解
注解是一种表示语法元数据的形式,它可以被添加到我们的Dart代码中;换句话说,是一种向我们的代码中的任何组件(如类或方法)添加额外信息的方式。注解在我们的Dart代码中随处可见:我们使用@required来指定一个命名的参数不是可选的,所以如果被注解的字段不存在,我们的代码就不会被编译,或者我们使用@override来识别那些由父类给出的API在子类中实现。我们怎么知道
它们是注解呢?嗯,它们很容易找到,因为它们的前缀是@。
但是我们如何创建我们的注解呢?
尽管在我们的类中拥有 "元数据 "的想法听起来非常异国情调和复杂,但事实上,注解是Dart中最简单的事情之一。在上面的段落中,我提到注解只是携带额外的信息。它们就像数据类一样。PODO...(Plain Old Dart Objects)。任何类都可以转化为一个注解,只要他们提供一个常量构造函数。
class Todo {
final String name; final String todoUrl; final
final String todoUrl.Const Todo(this.name, {this.todoUrl} : assert(name !
const Todo(this.name, {this.todoUrl}) : assert(name != null);
}
@Todo('hello first annotation', todoUrl: 'https://www.google.com')
class HelloAnnotations {}
正如你所看到的,注解是非常简单的。重要的是我们如何使用这些注解;注解所包含的信息以及我们如何使用这些信息才是它们的特别之处。而这正是source_gen和build_runner会帮助我们的地方。
我们应该如何使用build_runner?
build_runner是一个Dart包,它将帮助我们使用Dart代码生成文件。我们将通过build.yaml来配置Builder文件;一旦配置好了,一旦触发了build,或者文件发生了变化,我们就会收到更新,我们就可以解析那些发生了变化或者符合某个标准的代码。
source_gen来理解Dart的代码
在某种程度上,你可以把 build_runner 看成是回答什么时候需要生成代码的机制,而 source_gen 则回答了需要生成什么代码的问题。source_gen 提供了一个框架来构建 build_runner 期待的 Builders,同时暴露了一个友好的 API 来解析和生成代码。
把所有的部件放在一起:一个TODO报告器。
在文章的其余部分,我们将在一个名为todo_reporter.dart的宠物项目上工作,你可以在这个链接中找到它。
这是一个非书面规则,你可以在所有使用代码生成的项目中找到:你将为你的注解创建一个包,并为生成器创建一个不同的包,为这些增加价值。在Dart/Flutter中创建一个库包所需要的所有信息都可以在这个链接中找到。
因此,我们要做的是创建一个文件夹,我将命名为todo_reporter.dart。在这个文件夹中,我将添加我的todo_reporter,将包含注解,todo_reporter_generator处理代码,最后是一个example包,以演示我的库的功能。
我之所以把根文件夹后缀为.dart,是为了清晰明了;虽然这不是强制性的,但我喜欢遵循这一点,以明确这个包可以在任何Dart项目中使用。相反,如果我想把这个包只标记为Flutter,比如ozzie.flutter,那么我会使用不同的后缀。这不是必须要做的,只是我喜欢遵循的一个命名惯例。
创建todo_reporter,我们的注解包,也是最简单的一个包。
我们将在todo_reporter.dart中创建我们的todo_reporter,添加pubspec.yaml和lib文件夹。pubspec非常简单。
name: todo_reporter
description: Keep track of all your TODOs.
version: 1.0.0
author: Jorge Coca <jcocaramos@gmail.com>
homepage: https://github.com/jorgecoca/todo_reporter.dart
environment:
sdk: ">=2.0.0 <3.0.0"
dependencies:
dev_dependencies:
test: 1.3.4
除了测试包之外,并没有真正的依赖关系,只是用于开发目的。
在lib文件夹中,我们将做以下工作。
-
我们将创建一个
todo_reporter.dart,然后我们将在那里注册所有的类,这些类使用export来暴露我们包的公共API。 这是一个很好的做法,因为我们包中的任何公共类都可以通过import "package:todo_reporter/todo_reporter.dart"来导入。你可以在这里看到这个类的样子:github.com/jorgecoca/t… 。 -
在
lib文件夹内,我们现在要创建一个src文件夹,它将包含所有的代码,公共的或非公共的。
在我们的例子中,我们唯一需要包含的就是注释。让我们在里面创建一个包含这些内容的todo.dart文件。
class Todo {
final String name; final String todoUrl; final
final String todoUrl.Const Todo(this.name, {this.todoUrl} : assert(name !
const Todo(this.name, {this.todoUrl}) : assert(name != null);
}
好了,这就是我们需要的所有注释。我说过这很简单,对吧?好吧,我们还没有完成。让我们在测试包中添加一些单元测试。
import 'package:test/test.dart';
import 'package:todo_reporter/todo_reporter.dart';
void main() {
group('Todo annotation', () {
test('must have a non-null name', () {
expect(() => Todo(null), throwsA(TypeMatcher<AssertionError>()));
});
test('does not need to have a todoUrl', () {
final todo = Todo('name');
expect(todo.todoUrl, null);
});
test('if it is a given a todoUrl, it will be part of the model', () {
final givenUrl = 'http://url.com';
final todo = Todo('name', todoUrl: givenUrl);
expect(todo.todoUrl, givenUrl);
});
});
}
这是我们创建注解所需要的全部内容。你可以在这个链接中找到代码。
现在让我们在代码生成上下功夫。
做很酷的工作:todo_reporter_generator。
现在我们知道了如何创建包,让我们创建一个叫todo_reporter_generator的包。在它里面,你应该找到一个pubspec.yaml,一个build.yaml文件,一个lib文件夹,在lib文件夹里面,有一个src文件夹和一个todo_reporter_generator.dart文件,我们将在其中包含我们的export语句。我们的todo_reporter_generator被认为是一个不同的包,将作为dev_dependency添加到其他项目中。这是有道理的,因为我们只关心开发过程中的代码生成,而这并不包括在生产捆绑包中。
让我们来看看我们的pubspec.yaml应该是怎样的。
name: todo_reporter_generator
description: An annotation processor for @Todo annotations.
version: 1.0.0
author: Jorge Coca <jcocaramos@gmail.com>
homepage: https://github.com/jorgecoca/todo_reporter.dart
environment:
sdk: ">=2.0.0 <3.0.0"
dependencies:
build: '>=0.12.0 <2.0.0'
source_gen: ^0.9.0
todo_reporter:
path: ../todo_reporter/
dev_dependencies:
build_test: ^0.10.0
build_runner: '>=0.9.0 <0.11.0'
test: ^1.0.0
现在,让我们完成build.yaml。这个文件将包含你的Builders所需的配置。你可以在这里找到更多信息:github.com/dart-lang/b…
我们的build.yaml此刻看起来会是这样的。
targets:
$default:
builders:
todo_reporter_generator|todo_reporter:
enabled: true
builders:
todo_reporter:
target: ":todo_reporter_generator"
import: "package:todo_reporter_generator/builder.dart"
builder_factories: ["todoReporter"]
build_extensions: {".dart": [".todo_reporter.g.part"]}
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
我们的import入口应该指向包含Builder的文件,而builder_factories入口应该指向那些将构建代码的方法。
那么让我们继续创建这些文件:让我们在lib里面创建一个builder.dart文件,在src里面让我们添加一个名为todo_reporter_generator.dart的文件,内容如下。
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:todo_reporter_generator/src/todo_reporter_generator.dart';
Builder todoReporter(BuilderOptions options) =>
SharedPartBuilder([TodoReporterGenerator()], 'todo_reporter');
build.dart
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:todo_reporter/todo_reporter.dart';
class TodoReporterGenerator extends GeneratorForAnnotation<Todo> {
@override
FutureOr<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) {
return "// Hey! Annotation found!";
}
}
todo_reporter_generator.dart
我们可以看到,在builder.dart中,我们有一个todoReporter方法,它将创建一个Builder;这个Builder是通过使用一个SharedPartBuilder来提供的,这个SharedPartBuilder接收了我们的TodoReporterGenerator。这就是build_runner和source_gen如何一起工作。
我们的TodoReporterGenerator是GeneratorForAnnotation类型的;也就是说,它只有在找到一段被给定注释的代码时才会执行generateForAnnotatedElement,在我们的例子中就是Todo。
generateForAnnotatedElement的返回值是一个String值,将包含我们生成的代码;如果生成的代码没有编译,我们的构建阶段就会失败,这在避免bug时是非常整洁的。
在我们的todo_repoter_generator项目中使用这些文件,每次当尝试自动生成代码时,它将创建一个带有注释的part文件,写着 // Hey! Annotation found! . 我们将在下一篇文章中学习如何处理注释😉。
把所有的碎片放在一起:使用我们的 todo_reporter
开始使用我们的todo_repoter.dart的最后一块是在一个项目上展示它的功能。这是一个很好的做法,当工作包时,添加一个example项目,所以其他开发人员可以看到API是如何在现实世界的项目中使用。
让我们继续创建一个项目,并在pubspec.yaml文件中添加所需的依赖关系;在我的例子中,我只是在example文件夹内创建了一个Flutter项目,并添加了这些依赖关系。
dependencies:
flutter:
sdk: flutter
todo_reporter:
path: ../todo_reporter/
dev_dependencies:
build_runner: 1.0.0
flutter_test:
sdk: flutter
todo_reporter_generator:
path: ../todo_reporter_generator/
现在,在得到包后(`flutter packages get`),我们使用我们的注解。
import 'package:todo_reporter/todo_reporter.dart';
@Todo('Complete implementation of TestClass')
class TestClass {}
有了所有这些部件,让我们继续运行我们的生成器。
$ flutter packages pub run build_runner build
一旦它完成执行这个命令,你会注意到在你的项目上有一个新文件:todo.g.dart,内容如下。
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'todo.dart';
// *****************************************************************
// TodoReporterGenerator
// ********************************************************************
// Hey! Annotation found!
成功了! 我们已经完成了我们的任务!现在我们可以为每一个在我们的代码中找到的Todo注释生成一个有效的Dart文件。现在我们可以为代码中发现的每一个Todo注释生成一个有效的Dart文件。试试吧,你可以自由地创建你想要的任何数量的注释。
在下一篇文章中...
现在我们已经有了生成文件的正确设置,在下一篇文章中,我们将学习如何利用我们的注释,让我们的生成代码能够真正做一些很酷的事情,毕竟我们现在生成的代码没有任何目的。
你可以关注我 twitter.com/jcocaramos ,也可以在我的公共Github上看到更多的代码 github.com/jorgecoca 。
通过www.DeepL.com/Translator(免费版)翻译