mcpField
P
preview-widget

preview-widget

github.com/chunkytofustudios设计与 UI554 次安装26 周前更新
代码仓库

关于 preview-widget

用于在 Flutter Widget Previewer 中单独渲染组件的技能,适合需要快速迭代单个 Widget 外观的开发者。它提供预览服务器管理、无头截图和现有预览扫描脚本,无需启动完整应用。

主要功能

  1. 01检查 Flutter 版本是否满足要求
  2. 02启动并停止预览服务器
  3. 03通过无头 Chrome 截取预览
  4. 04扫描项目中的预览注解
  5. 05支持单组件多变体预览

适用场景

  1. 01调整单个组件的视觉样式
  2. 02比较浅色、深色或平板尺寸变体
  3. 03在完整应用启动前检查组件渲染

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/chunkytofustudios/flutter-skills --skill preview-widget

安装后在 Claude Code 中输入/preview-widget确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
multi_preview.dart.template
preview_wrapper.dart.template
references/
limitations.md
preview_annotations.md
wrapper_patterns.md
scripts/
check_preview_support.sh
list_previews.sh
screenshot_preview.sh
start_preview.sh
tests/
check.bats
help.bats
helpers.bash
list_previews.bats
screenshot.bats
start.bats
tests/stubs/
chrome
flutter

详细介绍

独立预览 Flutter widget

在 Flutter 的 Widget Previewer 中渲染单个 widget,这样你和用户就可以对它反复调整——修改设计、查看浅色/深色变体、调整平板尺寸等,无需启动完整应用。Agent 会在无头模式下为实时预览截图;用户也可以选择在自己的 Chrome 中打开同一个 localhost URL,实时查看。

适用场景

以下三项必须全部满足——任意一项不满足,这项技能就不适用:

  1. 项目是 Flutter 项目(当前工作目录中有 pubspec.yaml,且存在 lib/)。
  2. Flutter ≥ 3.35 已在 PATH 中(或通过 fvm flutter 调用)。Widget Previewer 在稳定版中仍属实验性功能,但已足以用于设计;更早版本的 Flutter 没有此功能。
  3. 用户想要调整的是单个 widget,而不是整个屏幕或端到端流程。完整屏幕的视觉设计应使用 design-polish + android-emulator。

环境准备(调用者的机器)

要求满足方式
Flutter ≥ 3.35运行 bash scripts/check_preview_support.sh——如果版本过旧,会明确报错并提示升级。使用 FLUTTER_BIN=… 覆盖 Flutter 二进制文件(默认:flutter,找不到时回退到 fvm flutter)。
Google Chrome / Chromiumscreenshot_preview.sh 会使用它在无头模式下截取预览。macOS 会自动找到 /Applications/Google Chrome.app;在 Linux 上,只要 PATH 中有 google-chrome、chromium 或 chromium-browser 即可。使用 CHROME_BIN=/path/to/chrome 覆盖。
bash 3.2+(macOS 默认)、awk、grep、find标准工具。无需 Python 或 Node。

无需为每台机器单独配置身份验证、账号或令牌——所有操作都在 localhost 上运行。

随附脚本

  • scripts/check_preview_support.sh — 验证 Flutter ≥ 3.35,并输出解析后的二进制文件路径和版本。退出码 0 = 已就绪,退出码 1 = 版本过旧,退出码 127 = 未找到 Flutter。
  • scripts/start_preview.sh — 在后台启动 flutter widget-preview start,从日志中解析 URL,并写入一个小型 JSON 状态文件。支持幂等操作:重复运行会返回现有 URL。使用 --stop 关闭服务。默认状态目录:./.preview-widget/(使用 PREVIEW_WIDGET_STATE_DIR 覆盖)。
  • scripts/screenshot_preview.sh — 使用无头 Chrome 截取运行中的预览器。参数:--out <path>、--size WxH(默认 1600x2400)、--wait <ms>(虚拟时间预算,默认 10000)、--url <url>(覆盖状态文件中的 URL)。在标准输出中打印输出文件的绝对路径。
  • scripts/list_previews.sh — 在 lib/ 中搜索 @Preview(...)、@<Custom>Preview(...) 和 extends (Multi)?Preview 定义,避免重复添加已有定义。

这四个脚本都支持 --help。自行尝试参数前,请先查看帮助。

每个 Flutter 项目的一次性设置

在为任何 widget 添加注解之前,项目需要一个预览包装器——一个顶层的 Widget previewWrapper(Widget child) 函数,用于重建真实 widget 所依赖的全局祖先链(theme cubit / ProviderScope / MaterialApp / 本地化 / MediaQuery 覆盖等)。对于任何非简单应用,仅靠预览器的 theme: 参数并不足够:如果 widget 在其树中没有 cubit,直接读取 BlocBuilder<ThemeCubit, …> 或 Theme.of(context) 的 widget 会抛出异常或渲染错误。

步骤:

  1. 阅读项目的 lib/main.dart 和 lib/app.dart(或等效根文件)。找出所有包裹 MaterialApp 的内容——MultiBlocProvider、ProviderScope、MultiRepositoryProvider、自定义 InheritedWidget、MediaQuery 覆盖以及 locale/router 配置。
  2. 将 assets/preview_wrapper.dart.template 复制到 lib/preview_wrapper.dart。
  3. 填入 imports 和 provider 列表,使其与真实根树相符,并为需要运行时状态的 provider 提供桩实现(例如,UxCubit 使用默认的 DeviceType.mobile;repository cubit 使用虚假/空状态)。包装器是一个编译期常量 tearoff,因此所有回调都必须是顶层公开函数。
  4. 配方请参阅 references/wrapper_patterns.md(BLoC、Riverpod、GoRouter、Localizations、MediaQuery、字体)。

如果不确定 widget 实际需要哪些 provider,请在 widget 主体中搜索 Theme.of、BlocBuilder<、context.watch<、context.read<、ProviderScope.containerOf、MediaQuery.of、Localizations.of、GoRouter.of——这些都是承载依赖的祖先节点。

单个 widget 的工作流

1. 选择注解目标

@Preview 和 @MultiPreview 可用于:

  • 返回 Widget 或 WidgetBuilder 的顶层函数
  • 类的公开静态方法
  • 没有必需参数的公开 widget 构造函数 / 工厂方法

传给注解的所有值都必须是编译期常量,任何回调(wrapper:、theme:、localizations:)都必须是公开的顶层(或静态)函数——预览器不接受闭包捕获私有状态。

2. 添加注解

使用项目的包装器进行单个预览:

import 'package:flutter/material.dart';
import 'package:flutter/widget_previews.dart';
import '../preview_wrapper.dart';

@Preview(name: 'MyButton — default', wrapper: previewWrapper)
Widget myButtonPreview() => const MyButton();

通过内置的 assets/multi_preview.dart.template → lib/preview_variants.dart 创建多预览矩阵(浅色 + 深色 + 平板):

@StandardPreview()
Widget myButtonPreview() => const MyButton();

完整的 @Preview 参数列表和目标限制规则,请参阅 references/preview_annotations.md。

3. 启动预览服务
bash scripts/start_preview.sh
# → Preview: http://localhost:51530

操作具有幂等性——如果服务已运行,再次执行只会重新打印 URL。.preview-widget/server.json 中的状态文件包含 { "url", "pid", "log" }。将 .preview-widget/ 添加到 .gitignore。

4. 为运行中的预览截图
bash scripts/screenshot_preview.sh
# → /tmp/preview-widget/preview-001.png

然后用 Read 查看 PNG 中的实时预览。默认截图视口为 1600×2400(可容纳约 3 个堆叠的预览平铺);传入 --size 1920x4000 可截取更高画面,或传入 --out /tmp/foo.png 控制路径。如果首次绘制结果为空白,请增大 --wait 20000(20 秒)——这是 canvas/Skia 的启动等待时间。

5. 迭代
edit Dart → sleep 2 → screenshot → read → reason → repeat

预览器会在保存文件时自动热重启,因此无需手动重启。等待 2 秒可让热重启完成,然后再截取下一张截图。

6. 每次操作结束时始终打印 URL

每条涉及预览器的助手消息都必须以这一行结束:

Preview: http://localhost:<port>

这样人类可以随时进入查看,也可以稍后再回来——服务会一直运行,直到执行 start_preview.sh --stop。

7. 完成后清理
bash scripts/start_preview.sh --stop

此操作会终止服务并移除状态文件。此步骤可选——在会话间让服务继续运行也没问题。

查找现有预览

添加新的 @Preview 之前,先查看项目中已有的预览:

bash scripts/list_previews.sh
# lib/widgets/foo.dart:42  @Preview(name: 'Foo - light', wrapper: previewWrapper)
# lib/widgets/bar.dart:17  @StandardPreview()
# lib/preview_variants.dart:9  final class StandardPreview extends MultiPreview {

传入明确的根目录以限定范围:bash scripts/list_previews.sh lib/src/widgets。

注意事项

  • 包装器是关键所在。 如果没有使用 wrapper: previewWrapper,简单的 @Preview 在任何会在 widget 内容中读取 providers/cubits 的应用里都会崩溃或渲染错误。如果 widget 使用了 BlocBuilder<ThemeCubit>,而包装器没有在它上方提供 ThemeCubit,预览就会抛出 ProviderNotFoundException——这不是预览器的 bug。
  • size: 限制的是 widget,而不是画布。 未受约束且没有设置 size: 的 widget 会自动缩放到预览器画布的大约一半大小,这对于单个组件来说通常不是理想效果。按钮大小的组件可传入 size: Size(360, 80),平板布局可传入 size: Size(1024, 1366)。
  • 所有注解值都必须是 const。 包括 wrapper:、theme: 和 localizations: 回调——它们必须是公开的顶层(或静态)函数 tear-off,不能是 lambda。任何捕获外部内容的函数都会导致编译失败。
  • 预览 widget 中的资源路径必须是 packages/<name>/...。在实际应用中,使用 Image.asset('assets/foo.png') 的 widget 可以正常工作,但在预览器中会出错;要在两种环境中都正常工作,请改用 Image.asset('packages/<your_pkg>/assets/foo.png')。
  • 不能使用 dart:io、dart:ffi 或原生插件。 预览器运行在 Chrome 中——任何访问 File、Platform、原生通道或平台插件的代码,都会在预览尝试渲染时抛出异常。条件导入(如 if (dart.library.html) 等)可以避开此问题。
  • 首次绘制较慢。 Flutter web 首次加载预览时需要引导 JS/Skia,通常需要 5–10 秒,磁盘较慢时会更久。默认的 --wait 10000 通常足够;在认定截图出错之前,可以调高此值(例如 --wait 20000)。
  • 只能截取整页。 预览器不会公开每个预览的 URL,因此 screenshot_preview.sh 会截取整个页面(所有预览卡片依次排列)。Agent 会根据卡片上方显示的 name: 标签判断每张卡片的内容。如果只想显示一个预览,可以暂时注释掉其他预览;也可以使用较大的 --size,并依靠 group: 参数将相关变体排在一起。
  • 热重启会自动进行,但并非即时完成。 编辑 .dart 文件后,等待预览器约 1–2 秒再截图。修改包装器本身需要更长时间;如果 sleep 5 后截图仍显示旧状态,请重启服务(先运行 --stop,再重新运行)。
  • 每个服务只支持一个项目。 截至 Flutter 3.35,IDE 工作流中的预览器不支持多项目工作区;CLI 服务只识别当前项目。如果项目使用 Pub 工作区,请从工作区根目录运行服务。
  • flutter widget-preview 是实验性的。 不同 Flutter 版本中的 API 和 CLI 标志可能会变动。如果此技能使用的标志不再有效,请检查 flutter widget-preview --help,并与 start_preview.sh 传入的参数进行对照。

快速参考

# 0. (one-time) Verify Flutter is recent enough.
bash skills/preview-widget/scripts/check_preview_support.sh

# 1. (one-time per project) Copy the wrapper template, fill in providers.
cp skills/preview-widget/assets/preview_wrapper.dart.template lib/preview_wrapper.dart
# …edit lib/preview_wrapper.dart, then add `.preview-widget/` to .gitignore

# 2. Annotate the widget (in its own file or alongside it).
#    @Preview(name: 'MyWidget', wrapper: previewWrapper)
#    Widget myWidgetPreview() => const MyWidget();

# 3. Start server, capture URL.
bash skills/preview-widget/scripts/start_preview.sh
# → http://localhost:51530

# 4. Iterate: edit → sleep 2 → screenshot → read → repeat.
bash skills/preview-widget/scripts/screenshot_preview.sh

# 5. Stop when done.
bash skills/preview-widget/scripts/start_preview.sh --stop

每次助手回合中只要使用过预览器,都要以 Preview: http://localhost:<port> 结尾,方便用户跟进。

评论

支持 Markdown