[Flutter翻译]【第2部分】在Dart中生成代码:注解、source_gen和build_runner

原文地址:medium.com/flutter-com…git

原文做者:medium.com/@jcocaramosgithub

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

在"【第1部分】Dart中的代码生成:基础知识 "中,咱们介绍了代码生成背后的动机是什么,并列出了Dart中最重要的工具,让计算机为咱们作艰苦的工做。在这篇文章中,咱们将介绍如何建立和使用Dart注解,以及如何使用source_genbuild_runner开始处理这些注解。bash

Dart中的注解

注解是一种表示语法元数据的形式,它能够被添加到咱们的Dart代码中;换句话说,是一种向咱们的代码中的任何组件(如类或方法)添加额外信息的方式。注解在咱们的Dart代码中随处可见:咱们使用@required来指定一个命名的参数不是可选的,因此若是被注解的字段不存在,咱们的代码就不会被编译,或者咱们使用@override来识别那些由父类给出的API在子类中实现。咱们怎么知道 它们是注解呢?嗯,它们很容易找到,由于它们的前缀是@。app

可是咱们如何建立咱们的注解呢?

尽管在咱们的类中拥有 "元数据 "的想法听起来很是异国情调和复杂,但事实上,注解是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_genbuild_runner会帮助咱们的地方。async

咱们应该如何使用build_runner

build_runner是一个Dart包,它将帮助咱们使用Dart代码生成文件。咱们将经过build.yaml来配置Builder文件;一旦配置好了,一旦触发了build,或者文件发生了变化,咱们就会收到更新,咱们就能够解析那些发生了变化或者符合某个标准的代码。ide

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.yamllib文件夹。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_runnersource_gen如何一块儿工做。

咱们的TodoReporterGeneratorGeneratorForAnnotation类型的;也就是说,它只有在找到一段被给定注释的代码时才会执行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(免费版)翻译

相关文章
相关标签/搜索