釋出時間: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(免費版)翻譯