跳转至正文

在 Flutter 与 Dart DevTools 中构建自定义工具

学习如何在 DevTools 中构建自定义开发者工具。

你是否曾想为 Dart 与 Flutter 构建开发者工具却不知从何入手?或者不想花费大量精力去连接正在运行的 Dart 或 Flutter 应用以访问调试数据?即便你创建了开发工具,又如何部署或让用户便捷访问?你可以绕过这些障碍来创建开发者工具。

借助 Dart 与 Flutter DevTools 扩展框架,你可以轻松构建与现有 DevTools 工具套件紧密集成的开发者工具。扩展使用 Flutter web 构建,并复用 DevTools 中的框架与工具,简化开发者工具的编写体验。

Example DevTools extension for package:foo

DevTools 扩展如何工作?

#

扩展作为 pub 包的一部分发布。你可以向现有 pub 包添加 DevTools 扩展,或创建仅提供 DevTools 扩展的新包。在这两种情况下,最终用户必须依赖提供 DevTools 扩展的包,才能在 DevTools 中看到该扩展。

例如,假设有 package:foo,该包提供 DevTools 扩展。当用户在应用中依赖 package:foo 时,会自动获得该包提供的 DevTools 扩展访问权限。当 DevTools 根据用户应用或 IDE 的信息检测到 package:foo 扩展可用时,会在 DevTools 中新增名为 "Foo" 的标签页,其中包含 package:foo 提供的开发者工具。

Diagram showing how a DevTools extension works

已在现有包中添加 DevTools 扩展的包示例包括 package:shared_preferencespackage:providerpackage:patrolpackage:drift

支持哪些类型的工具?

#

借助 DevTools 扩展框架,你可以构建多种工具,包括:

  • Companion tools for existing packages.

  • New tools that are shipped as their own package.

  • Tools that interact with a running application.

  • Tools that interact with project files opened in the IDE.

  • Tools that interact with the Analysis server.

  • 现有包的配套工具。

  • 作为独立包发布的新工具。

  • 与正在运行的应用交互的工具。

  • 与 IDE 中打开的项目文件交互的工具。

  • 与分析服务器(Analysis server)交互的工具。

DevTools 扩展框架提供开箱即用的功能,使向用户分发扩展变得顺畅。例如,用户可以:

  • Use your tool from DevTools in the browser.

  • Use your tool embedded directly in their IDE.

  • Discover and open your tool from Dart and Flutter supported IDEs.

  • 在浏览器的 DevTools 中使用你的工具。

  • 在 IDE 中直接嵌入使用你的工具。

  • 从支持 Dart 与 Flutter 的 IDE 中发现并打开你的工具。

接下来,学习如何编写 DevTools 扩展。


编写 DevTools 扩展

#

开始之前,你需要:

  • Flutter SDK >= 3.17 & Dart SDK >= 3.2.

  • A pub package that (in your opinion) needs a custom DevTools extension.

  • Flutter SDK >= 3.17 与 Dart SDK >= 3.2。

  • 一个(在你看来)需要自定义 DevTools 扩展的 pub 包。

设置包目录结构

#

你将提供独立扩展或配套扩展。

独立扩展

#

对于独立扩展(不作为现有 pub 包的一部分发布),扩展可与扩展同包包含源码,简化开发;由于用户会将你的包添加为 dev_dependency,包体积不会影响用户应用大小。包结构如下所示:

yaml
my_new_tool
  extension/
    devtools/
      build/
        ...  # pre-compiled output of the Flutter web app under lib/
      config.yaml
  lib/  # source code for your extension Flutter web app
    src/
      ...

由于扩展以 Flutter web 应用形式构建,请使用 flutter create 生成包:

flutter create --template app --platforms web my_new_tool

接下来,在下一步中使用 my_new_tool 包配置扩展。

配套扩展

#

对于作为现有 pub 包一部分发布的配套扩展,建议将扩展源码放在 pub 包之外,以尽量减小包体积,避免增大依赖该包的用户应用体积。推荐包结构如下:

yaml
foo/  # formerly the repository root of your pub package
  packages/
    foo/  # your pub package
      extension/
        devtools/
          build/
            ...  # pre-compiled output of foo_devtools_extension/lib
          config.yaml
    foo_devtools_extension/
      lib/  # source code for your extension Flutter web app

配置扩展

#

在向用户提供 DevTools 扩展的 Dart 包中,添加顶层 extension 目录:

yaml
foo/
  extension/
  lib/
  ...

extension 目录下, 完全按所示 创建以下结构:

yaml
extension/
  devtools/
    build/
    config.yaml

config.yaml 文件包含 DevTools 加载扩展所需的元数据:

yaml
name: foo
version: 0.0.1
issueTracker: <link_to_your_issue_tracker.com>
materialIconCodePoint: '0xe0b1'
requiresConnection: true  # optional field - defaults to true

按所示复制 config.yaml 内容,粘贴到你刚在包中创建的 config.yaml 文件中。 务必使用所示的确切文件名和字段名,否则扩展可能无法在 DevTools 中加载

为每个键填写适合你包的值。

  • name: The package name for this DevTools extension. The value of this field is used in the extension page title bar. [required]

  • version: The version of your DevTools extension. This version number should evolve over time as you ship new features for your extension. The value of this field is used in the extension page title bar. [required]

  • issueTracker: The URL for your issue tracker. When a user clicks the Report an issue link in the DevTools UI, they are directed to this URL. [required]

  • name:此 DevTools 扩展的包名。该字段值用于扩展页面标题栏。[required](必填)

  • version:DevTools 扩展的版本号。随你发布新功能应逐步演进。该字段值用于扩展页面标题栏。[required](必填)

  • issueTracker:问题跟踪器 URL。用户在 DevTools UI 中点击 Report an issue(报告问题)链接时会跳转到此 URL。[required](必填)

DevTools extension screen title bar

  • materialIconCodePoint: Corresponds to the codepoint value of an icon from material/icons.dart. This icon is used for the extension’s tab in the top-level DevTools tab bar. [required]

DevTools extension tab icon

  • materialIconCodePoint:对应 material/icons.dart 中图标的 codepoint 值。该图标用于顶层 DevTools 标签栏中的扩展标签。[required](必填)

DevTools extension tab icon

  • requiresConnection: Indicates whether the extension requires a connected Dart or Flutter app to use. This is an optional field that will default to true if unspecified. [optional]

  • requiresConnection:指示扩展是否需要已连接的 Dart 或 Flutter 应用才能使用。此为可选字段,未指定时默认为 true。[optional](可选)

有关 config.yaml 规范的最新文档,请访问 extension_config_spec.md

构建扩展

#

按以下步骤构建扩展。

创建 Flutter web 应用

#

在希望放置扩展源码的目录中运行以下命令,将 foo_devtools_extension 替换为 <your_package_name>_devtools_extension

flutter create --template app --platforms web foo_devtools_extension

添加 package:devtools_extensions 依赖

#
flutter pub add devtools_extensions

你可能还需要依赖 package:devtools_app_shared,其中包含构建扩展时可用的共享服务、工具与 UI 组件。示例用法见 devtools_app_shared/example

flutter pub add devtools_app_shared

添加 DevToolsExtension widget

#

lib/main.dart 中添加以下导入:

dart
import 'package:devtools_extensions/devtools_extensions.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const FooDevToolsExtension());
}

class FooDevToolsExtension extends StatelessWidget {
  const FooDevToolsExtension({super.key});

  @override
  Widget build(BuildContext context) {
    return const DevToolsExtension(
      child: Placeholder(), // Build your extension here
    );
  }
}

DevToolsExtension widget 会自动初始化与 DevTools 交互所需的全部扩展。在扩展 web 应用的任意位置,可访问以下全局对象:

  • extensionManager: a manager for interacting with DevTools or the extensions framework.

  • serviceManager: a manager for interacting with the connected vm service, if present.

  • dtdManager: a manager for interacting with the Dart Tooling Daemon, if present.

  • extensionManager:用于与 DevTools 或扩展框架交互的管理器。

  • serviceManager:用于与已连接的 vm service 交互的管理器(若存在)。

  • dtdManager:用于与 Dart Tooling Daemon 交互的管理器(若存在)。

调试扩展

#

开发与维护 DevTools 扩展时,你需要运行、调试和测试扩展的 Flutter web 应用。下面概述了几种可选方式。

#

调试时,你可能希望使用「simulated DevTools environment」(模拟 DevTools 环境)。该模拟环境允许你在不将扩展作为 DevTools 内嵌 iFrame 开发的情况下构建扩展。以此方式运行会用模拟 DevTools 与扩展连接的环境包裹扩展,并提供热重启与更快的开发周期。

Debugging an extension with the Simulated DevTools Environment

  1. Your DevTools extension.

  2. The VM service URI for a test app that your DevTools extension will interact with. This app should depend on your extension’s parent package (package:foo in this example).

  3. Buttons to perform actions that a user may trigger from DevTools.

  4. Logs showing the messages that will be sent between your extension and DevTools.

  5. 你的 DevTools 扩展。

  6. 扩展将与之交互的测试应用的 VM service URI。该应用应依赖扩展的父包(本例为 package:foo)。

  7. 用于执行用户可能从 DevTools 触发的操作的按钮。

  8. 显示扩展与 DevTools 之间将发送消息的日志。

模拟环境由环境参数 use_simulated_environment 启用。要在启用该标志的情况下运行扩展 web 应用,请在 VS Code 的 launch.json 中添加配置:

json
{
    ...
    "configurations": [
        ...
        {
            "name": "foo_devtools_extension + simulated environment",
            "cwd": "packages/foo_devtools_extension",
            "request": "launch",
            "type": "dart",
            "args": [
                "--dart-define=use_simulated_environment=true"
            ],
        },
    ]
}

或在命令行带该标志启动应用:

flutter run -d chrome -dart-define=use_simulated_environment=true

选项 B:使用真实 DevTools 环境

#

当扩展开发到可在真实 DevTools 环境中测试变更的阶段时,需要执行一系列设置步骤:

  1. Develop your extension to a point where you are ready to test your changes in a real DevTools environment. Build your flutter web app and copy the built assets from `your_extension_web_app/build/web` to your pub package's `extension/devtools/build directory`.

    将扩展开发到可在真实 DevTools 环境中测试变更的阶段。构建 Flutter web 应用,并将构建产物从 your_extension_web_app/build/web 复制到 pub 包的 extension/devtools/build 目录。

    可使用 package:devtools_extensionsbuild_and_copy 命令完成此步。

    console cd your_extension_web_app; flutter pub get; dart run devtools_extensions build_and_copy --source=. --dest=path/to/your_pub_package/extension/devtools

    为确保扩展在 DevTools 中正确加载,请运行 package:devtools_extensionsvalidate 命令。 --package 参数应指向将与此扩展一起发布的 Dart 包根目录。

    console cd your_extension_web_app; flutter pub get; dart run devtools_extensions validate --package=path/to/your_pub_package

  2. Prepare a test environment with a dependency on your pub package that is providing the extension.

    准备测试环境,并依赖提供该扩展的 pub 包。

    在要添加对你包依赖的 Dart 或 Flutter 项目中,添加指向本地包源码的 path 依赖(包含带扩展资源的 extension/devtools/ 目录的包)。完成后在该包上运行 pub get

    * 若扩展需要正在运行的应用,则需运行依赖该扩展的应用。 * 若扩展不需要正在运行的应用,则需在支持的 IDE(VS Code 或 IntelliJ / Android Studio)中打开依赖你包的测试 Dart 或 Flutter 项目。

  3. Start DevTools

    启动 DevTools

    可使用以下任一方式启动 DevTools:

    * 若扩展需要正在运行的应用,可从运行测试应用时命令行打印的 URI 打开 DevTools,或从运行测试应用的 IDE 打开。 * 若扩展不需要正在运行的应用,可在支持的 IDE 中打开依赖你包的 Dart 或 Flutter 项目,从 IDE 打开 DevTools 在浏览器中查看扩展。 * 若需要 DevTools 的本地或未发布变更,需从源码构建并运行 DevTools。请参阅 DevTools CONTRIBUTING.md。需同时构建服务器与前端以测试扩展(instructions)。

  4. Connect your test app to DevTools if it is not connected already, and you should see a tab in the DevTools app bar for your extension. The enabled or disabled state of your extension is managed by DevTools, which is exposed from an **Extensions** menu in DevTools, available from the action buttons in the upper right corner of the screen.

    若测试应用尚未连接,请将其连接到 DevTools,你应能在 DevTools 应用栏中看到扩展标签页。扩展的启用/禁用状态由 DevTools 管理,可通过屏幕右上角操作按钮中的 Extensions(扩展)菜单访问。

打开 DevTools 后,应用栏中应出现扩展标签页。扩展的启用/禁用状态由 DevTools 管理,可通过屏幕右上角操作按钮中的 "Extensions"(扩展)菜单访问。

DevTools Extensions menu button

DevTools Extensions menu

发布带有 DevTools 扩展的包

#

包要向用户提供 DevTools 扩展,必须在 your_pub_package/extension/devtools/ 目录中包含预期内容后发布(如前述设置说明所述)。

  1. Ensure the `extension/devtools/config.yaml` file exists and is configured per the specifications above. You can run the `validate` command from `package:devtools_extensions` to verify.

    确保 extension/devtools/config.yaml 存在且按上述规范配置。可运行 package:devtools_extensionsvalidate 命令验证。

    cd your_extension_web_app;
    flutter pub get;
    dart run devtools_extensions validate --package=path/to/pkg_providing_your_extension_assets
    
  2. Use the `build_and_copy` command provided by `package:devtools_extensions` to build your extension and copy the output to the `extension/devtools` directory:

    使用 package:devtools_extensions 提供的 build_and_copy 命令构建扩展并将输出复制到 extension/devtools 目录:

    console cd your_extension_web_app; flutter pub get; dart run devtools_extensions build_and_copy --source=. --dest=path/to/your_pub_package/extension/devtools

然后在 pub.dev 上发布包:

flutter pub publish

运行 pub publish 时,若缺少必需的 config.yaml 或非空的 build 目录,会看到警告。

有关发布包的更多说明,请参阅 package:devtools_extensionspublishing guide(发布指南)。


就是这样!当用户依赖你包的最新版本时,会自动获得你在 DevTools 扩展中提供的工具。

以下链接可能对你有帮助: