目录

在 Dart 生态系统中使用 packages 实现共享软件,比如一些库和工具。本章将通过最常见的 Package 来介绍如何创建一个 Package。

创建一个新的 package

若要为 package 创建一个初始化的目录和结构,使用 dart create 命令,并加入 package 作为命令参数来创建:

$ dart create -t package <PACKAGE_NAME>

Package 的组成

下图展示了最简单的 Package 的结构:

root directory contains pubspec.yaml and lib/file.dart

Package 的最基本要求包括:

pubspec 文件
Package 的 pubspec.yaml 文件与应用程序的 pubspec.yaml 文件相同— pubspec.yaml 文件中并没有特别的指出这个 Package 是一个库。

lib 目录
如你所料,库的代码位于 lib 目录下,且对于其他 Package 是公开的。你可以根据需要在 lib 下任意创建组织文件结构。按照惯例,实现代码会放在 lib/src 目录下。 lib/src 目录下的代码被认为是私有的。其他 Package 应该永远不需要导入 src/... 目录下代码。通过导出 lib/src 目录的文件到一个 lib 目录的文件,实现对 lib/src 目录中 API 的公开。

组织 Package 的代码结构

在创建一个小的,独立的 Package 时(称之为 Mini Library),它非常容易维护,扩展和测试。大多数情况下,除非存在两个类紧密耦合的情况,否则每个类都应该将自己视为一个 Mini Library 。

直接在 lib 目录下创建“主” Library 文件,lib/<package-name>.dart,该文件导出所有的公开的 API 。这样就可以允许使用者导入单个文件就能够获得 Library 的所有功能。

lib 目录还可能包含其他可导入的非 src 代码。例如,主 Library 可能是跨平台的,但创建的独立 Library 依赖于 dart:io 或 dart:html 。 Some packages have separate libraries that are meant to be imported with a prefix, when the main library is not.(无法确切理解含义,暂未翻译)

这里让我们来看下一个真实 Library Package 的组织结构:shelf 。 shelf Package 提供了一种使用 Dart 创建 Web 服务器的简便方法,它是一种 Dart Package 的常用结构:

shelf root directory contains example, lib, test, and tool subdirectories

主 Library 文件 shelf.dart 在 lib 目录下,通过 shelf.dart 文件导出 lib/src 目录下的若干文件。为了不导出过多的 API,并且为开发者提供公开的 API 的概览, shelf.dart 使用了 show 来指定哪些内容需要导出:

export 'src/cascade.dart' show Cascade;
export 'src/handler.dart' show Handler;
export 'src/hijack_exception.dart' show HijackException;
export 'src/middleware.dart' show Middleware, createMiddleware;
export 'src/middleware/add_chunked_encoding.dart' show addChunkedEncoding;
export 'src/middleware/logger.dart' show logRequests;
export 'src/middleware_extensions.dart' show MiddlewareExtensions;
export 'src/pipeline.dart' show Pipeline;
export 'src/request.dart' show Request;
export 'src/response.dart' show Response;
export 'src/server.dart' show Server;
export 'src/server_handler.dart' show ServerHandler;

在 shelf Package 中同样包含了 Mini Library:shelf_io 。适配器用来处理来自 dart:io 的 HttpRequest 对象。

导入库文件

在从其他 package 导入库文件时,使用 package: 命令来指定文件的 URI 。

import 'package:utilities/utilities.dart';

在两个文件都在 lib 目录中,或两个文件都在 lib 目录外,我们都可以使用相对路径的方式导入 Library 。但是,如果两个文件不都在 lib 目录中,需要对 lib 内或者 lib 外进行查找,那么此时必须要使用 package: 导入。如果对当前使用存在疑惑,那么直接 package:package: 满足所有情况。

下面图片展示分别从 lib 和 web 目录中导入 lib/foo/a.dart

lib/bar/b.dart uses a relative import; web/main.dart uses a package import

条件导入或条件导出 Library 文件

如果你的 library 支持多平台,那么你应该会用到条件导入或条件导出 library 文件。常见的用例是,一个库同时支持 Web 和 Native 平台。

为了使用条件导入或条件导出,你需要检查是否存在 dart:* 库。下面是一个条件导出代码的样例,它将检查是否存在 dart:io and dart:html 库:

export 'src/hw_none.dart' // Stub implementation
    if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
    if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation
lib/hw_mp.dart

该代码的作用如下:

  • 在一个可以使用 dart:io 的 app 中(例如一个命令行应用),导出 src/hw_io.dart

  • 在一个 web 应用中可以使用 dart:html 导出 src/hw_html.dart

  • 若是其他情况,则导出 src/hw_none.dart

要条件导入一个文件可以使用和上面一样的方式,仅需将 export 改为 import 即可。

所有条件导出的库必须实现相同的 API。下面是 dart:io 实现的一个例子:

import 'dart:io';

void alarm([String? text]) {
  stderr.writeln(text ?? message);
}

String get message => 'Hello World from the VM!';
lib/src/hw_io.dart

这是一个默认实现,它会导致抛出 UnsupportedErrors

void alarm([String? text]) => throw UnsupportedError('hw_none alarm');

String get message => throw UnsupportedError('hw_none message');
lib/src/hw_none.dart

在任何平台上,你都可以导入具有条件导出代码的库:

import 'package:hw_mp/hw_mp.dart';

void main() {
  print(message);
}

提供额外文件

一个设计良好的 Package 很容易被测试。我们建议使用 test Package 编写测试用例,并将测试代码放到 Package 根目录的 test 目录中。

如果要创建一个公用的命令行工具,应该将这些工具放到公共目录 bin 中。使用 dart pub global activate 命令行来运行工具。在 pubspec 的 executables 部分 列出的工具允许用户直接运行它而无需调用 dart pub global run

在 Library 中包含一个 example 程序演示如何使用 Library 是非常有用的。 example 程序在 Package 根目录的 example 目录中。

在开发过程中任何非公开的工具或可执行程序,应该放到 tool 文件夹。

如果要将 Library 发布到 Pub 网站还要求一些其他的文件来描述 发布的 Package ,例如:README.mdCHANGELOG.md 文件。更多关于如何组织 Package 目录的内容,参见 Pub Package 布局约定

为 Library 制作文档

使用 [dartdoc][] 可以为 Library 生成 API 文档。 dartdoc 解析源文件去查找使用 /// 语法标注的 文档注释

/// The event handler responsible for updating the badge in the UI.
void updateBadge() {
  ...
}

文档生成示例,参见 shelf 文档

若要自动生成任何库级别的文档,请添加一个 library 指令并直接在其上方附加注释。更多详情,请参阅文档 Effective Dart: Documentation

分发开源 Library

如果 Library 是开源的,我们建议将他共享到 Pub 网站。使用 pub publish 来发布或者更新 Library,该命令将会上传 Package 并创建或更新其页面。示例参见 shelf Package 页面。有关如何准备发布 Package 的详细内容,参见 发布 Package

Pub.dev 网站不仅仅是 Dart Packages 的发布网站, Pub 网站不仅用于托管 Package ,还能够生成托管 Package 的 API 参考文档。最新生成的文档的链接位于 Package 的 About 选项卡中; 示例参见 shelf Package 的 API 文档。链接到以前版本的文档,位于 Package 页面的 Versions 选项卡中。

为了确保 Package 的 API 文档在 Pub 网站上看起来更美观,请遵循以下步骤:

  • 在发布 Package 前,请通过执行 dart doc 工具确保文档能够生成成功且符合预期。

  • 在发布 Package 后,请检查 Versions 选项卡,确保文档生成成功。

  • 如果文档没有生成,点击 Versions 选项卡中的 failed 查看 dartdoc 的输出。

资源

通过运用以下资源来了解学习更多关于 Library Package 的内容: