ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Flutter鸿蒙化组件库托管:widgetbook_cli云端自动化实践

2026/10/6 3:50:51 拓冰建站 浏览量
Flutter鸿蒙化组件库托管:widgetbook_cli云端自动化实践 做 Flutter 组件库的同学应该都有这种体会组件越写越多demo 越堆越乱产品、测试、甚至隔壁组同事想看一眼组件长什么样都得来问你。widgetbook_cli 就是为解决这件事来的它把 UI 组件库变成可自动托管、可云端浏览、可精准检索的“组件台账”。我在给鸿蒙化项目做适配时把整套 Widgetbook 流程接进了 OpenHarmony 环境的 Flutter 工程里踩了几个坑也沉淀了一套可复用的做法这篇就完整展开。整个方案的核心链路并不复杂用 Widgetbook 在 Flutter 工程里维护组件用例useCase用 widgetbook_cli 把用例目录打包上传到云端再通过自动化流水线保证每次代码变更后组件库都能自动刷新。鸿蒙化则意味着 Flutter SDK 要使用支持 OpenHarmony 的分支构建环境、平台通道、资源路径都要重新校准。这篇文章会从方案设计、环境准备、实操命令到排查记录逐个讲适合正在做 Flutter 鸿蒙化、又想顺手把组件治理起来的团队参考。1. 项目整体设计与核心链路1.1 为什么要做组件库自动托管而不是继续用 Demo 列表很多 Flutter 工程里并不是没有组件库而是组件库散落在各个页面里。A 页面有一个按钮B 页面又有一个类似的按钮两处样式略微不同谁也不敢轻易去统一。要改变这种局面第一步就是把公共组件从“页面中的偶然存在”变成“目录中的正式成员”。Widgetbook 解决的是“展示和整理”widgetbook_cli 解决的是“发布和更新”。这两者合起来组件库才真正进入自动托管状态每次代码合入主干后构建任务会自动扫描最新组件、生成预览产物、上传到云端。团队成员不需要拉代码、跑 Flutter也不用问“这个 disabled 态怎么触发”打开浏览器就能看。这个价值在鸿蒙化场景里更明显。鸿蒙设备上的 Flutter 工程往往要同时兼顾手机、平板和未来可能新增的设备类型组件数量增长很快。如果还靠手动截图、PPT、Figma 连接来同步一周能更新两次就算不错了。而 widgetbook_cli 这种方式是“代码一变云端就变”。1.2 widgetbook_cli 的工作链路拆解从数据流来看widgetbook_cli 的链路可以拆成四段组件注册。开发者在widgetbook.dart或注解文件中声明组件、分组、useCase。元数据生成。CLI 扫描注册信息生成一份结构化 JSON描述每个组件的位置、名称、标签、相关入口。预览产物构建。把 widgetbook 应用编译成 Flutter Web 产物这样云端可以直接用浏览器渲染真实组件。云端上传。CLI 把产物和元数据推送到 Widgetbook Cloud形成可检索的组件目录。理解这条链路后你会发现“鸿蒙化适配”其实有两个落点。第一个落点是 Flutter 工程本身它必须能在鸿蒙工具链下正常构建第二个落点是 widgetbook 入口所用的 Flutter 能力在 OpenHarmony 分支下不能出现不兼容的 API。至于 CLI 本身它更接近一个 Dart 工具对平台的感知主要停留在构建目标上。1.3 鸿蒙化适配到底在适配什么先说结论widgetbook 和 widgetbook_cli 这两个包没法直接在鸿蒙设备上“跑出”一个系统级调试工具。但它们在鸿蒙化 Flutter 工程里是完全可用的前提是把“预览”和“运行”分离。通常的用法是鸿蒙设备上正常跑业务 Appwidgetbook 入口只作为独立构建目标生成 Web 预览然后通过 CLI 上传云端。这样做的原因是widgetbook 需要浏览器环境来承载交互式目录在鸿蒙原生界面里嵌入 Flutter 的 widgetbook 虽然技术上可以但体验并不好也没有必要。所以真正的适配点有三个Flutter SDK 要切换到支持 OpenHarmony 的分支并且与本地鸿蒙 SDK 版本匹配。业务组件里如果有平台通道调用MethodChannel、EventChannel在 widgetbook 用例里必须有 Mock否则预览时会出现 MissingPluginException。资产路径要统一。组件用到的图片、字体、JSON 配置需要保证 Flutter Web 构建和鸿蒙构建都能取到同一份资源。这三个点处理干净widgetbook_cli 的云端交付就能顺畅跑起来。2. 鸿蒙化适配前置环境准备2.1 Flutter SDK 分支与版本匹配如果你之前的项目一直用官方 Flutter第一次切到鸿蒙分支时最容易犯的错是直接下载一个“看起来能用”的 SDK 就开跑。鸿蒙场景下的 Flutter 分支通常在 OpenHarmony 社区维护默认分支名包含 ohos 字样它会对接鸿蒙的图形栈和 Ability 生命周期。我的建议是准备一个独立的 Flutter SDK 目录不要把官方 Flutter 覆盖掉。切换命令大致如下git clone -b ohos OpenHarmony Flutter 仓库地址 flutter_ohos export PATH$PWD/flutter_ohos/bin:$PATH flutter doctor跑完flutter doctor后你大概率会看到多出来的鸿蒙检查项。这里要留意 SDK 版本对应关系Flutter 分支里的 SDK 版本需要和 DevEco Studio 里安装的 HarmonyOS SDK 版本匹配。如果版本差距过大编译时会出现 hvigor 相关错误而不是 Dart 语法错误排查起来非常绕。flutter --version的输出建议存一份到文档里。后续 CI 配置、依赖版本选择、组件库构建参数都得以这个版本为基准。2.2 安装鸿蒙工具链与配置环境变量光有 Flutter 分支还不够鸿蒙原生构建需要 DevEco Studio 配套的 SDK、toolchains、hvigor。环境变量DEVECO_SDK_HOME要指向鸿蒙 SDK 的根目录。Flutter 工程里如果存在local.properties还需要配置对应的sdk.dir。我之前遇到过一种诡异情况本地命令行构建能过但 Android Studio 里打开工程后flutter run -d ohos一直报找不到 SDK后来发现是环境变量没被 IDE 继承。建议在项目的.bashrc或者.zshrc中统一写死然后在 IDE 里重新登录一次用户会话。如果你还要同时构建 Flutter Web 产物尽量把 Web 构建和鸿蒙构建放到不同目录甚至不同 CI job。因为 hvigor 和 Gradle 的缓存目录混在一起时经常出现“上次构建产物影响本次编译”的问题。分离构建目录让每个目标有独立工作区能省掉很多脏缓存麻烦。2.3 依赖版本与 pubspec.yaml 调整widgetbook 相关依赖建议全部放在dev_dependencies中。业务包不用引入 widgetbook只有负责维护组件目录的 package 才需要。dev_dependencies: widgetbook: ^3.0.0 widgetbook_cli: ^3.0.0 widgetbook_annotation: ^3.0.0 build_runner: ^2.4.0这里要特别小心 SDK 约束。OpenHarmony 分支的 Dart 版本和官方 Flutter 不完全同步有时会晚一个 minor 版本。widgetbook 的 pub 包如果要求更高 Dart SDKpub get会直接失败。我踩过一次坑后养成了一个习惯新建空 Flutter 工程只加 widgetbook 依赖确认能跑通后再往业务工程里引。依赖加好后还需要在工程的根目录创建widgetbook.dart入口。这个入口是独立的 Flutter 应用不应该依赖 App 的 main.dart 和启动逻辑。这样做既能让 CLI 快速构建又能避免把整个业务初始化逻辑带进预览。3. 实操从组件注册到云端托管3.1 搭建 widgetbook.dart 入口先从一个最小入口开始。假设你有一个AppButton公共组件想展示默认态、加载态、禁用态三个场景import package:flutter/material.dart; import package:widgetbook/widgetbook.dart; import app_button.dart; void main() { runApp( const Widgetbook( directories: [ WidgetbookComponent( name: AppButton, useCases: [ WidgetbookUseCase( name: Default, builder: (context) const AppButton( label: 确认, ), ), WidgetbookUseCase( name: Loading, builder: (context) const AppButton( label: 提交中, loading: true, ), ), WidgetbookUseCase( name: Disabled, builder: (context) const AppButton( label: 不可点击, disabled: true, ), ), ], ), ], ), ); }组件多了以后手写这种注册代码会非常痛苦。此时可以引入注解UseCase(name: Dark Theme, type: CheckoutCard) Widget checkoutCardDark(BuildContext context) { return const CheckoutCard(amount: 0.00, error: true); }然后跑 build_runner 自动生成目录代码dart run build_runner build --delete-conflicting-outputs每次新增或修改 useCase 后都要重新执行一次生成命令。我会把这条命令写进产物构建脚本里保证 CI 构建的是最新目录。3.2 用 CLI 构建预览产物入口准备好之后执行 widgetbook_cli 的构建命令。以我使用的 3.x 版本为例dart run widgetbook_cli build这条命令本质上会先调用 Flutter Web 构建然后生成组件元数据。第一次跑会很慢因为它要编译完整 Web 产物。执行完成后输出目录里通常会出现widgetbook_metadata.json之类的文件。这个 JSON 是云端的“索引命脉”它记录了每个组件、每个 useCase、每个标签对应的资源路径。如果之后你在云端搜索某个组件时找不到大概率是这个 JSON 没生成完整或者是 useCase 名称有重复CLI 做了去重导致搜索条件被吞掉。为了提高检索质量我会在构建命令后面加--report参数输出一份组件覆盖报告。报告里能看到哪些组件缺少“空态”或“异常态”用例这样就能在评审前把基础用例补齐。3.3 上传到 Widgetbook Cloud 并验证可访问构建通过后上传命令很简单dart run widgetbook_cli upload --token $WIDGETBOOK_TOKENWIDGETBOOK_TOKEN需要提前申请不建议使用交互式登录 token。CI 环境没有浏览器没法完成“扫码确认”式登录必须用环境变量注入。上传成功后你会得到一个云端地址。我强烈建议在验证环节输出访问地址并保存到 CI 的日志中。因为组件库上传成功不代表页面一定能打开Web 产物如果资源路径写死上传到云端后可能白屏。验证时至少要检查组件树是否完整、点击 useCase 是否能切换、图片字体是否正常。4. 自动化交付与“精密组件”管理4.1 组件命名与标签体系让精密检索成为可能云端组件库最怕的是“能打开但搜不到”。要避免这种问题必须建立一套命名规范。我在项目中定了几条规则目录按业务域分不按组件类型分。用accounts/、checkout/而不是buttons/、inputs/。useCase 名称必须包含状态和场景例如Default on white background、Loading with custom icon。每个业务组件至少补三个用例默认态、空态、异常态。标签体系和命名同样重要。比如给组件打上platform:ohos、usage:high、state:deprecated这样的标签后云端可以把“鸿蒙端高频使用但已废弃”的组件自动筛选出来。这对后续组件迁移和 UI 一致性治理非常有帮助。4.2 CI/CD 接入让云端组件库永不落后手动上传只能算“半自动”真正的自动托管要接入 CI。以 GitHub Actions 为例关键步骤大概是这样- name: Setup Flutter uses: subosito/flutter-actionv2 with: flutter-version: 3.x - name: Install dependencies run: flutter pub get - name: Generate widgetbook directories run: dart run build_runner build --delete-conflicting-outputs - name: Build widgetbook run: dart run widgetbook_cli build - name: Upload widgetbook env: WIDGETBOOK_TOKEN: ${{ secrets.WIDGETBOOK_TOKEN }} run: dart run widgetbook_cli upload --token $WIDGETBOOK_TOKEN如果不同分支需要独立预览可以为每个 feature 分支指定独立 project。合并到主干后再把该分支的标签合并到主干 project。这样既保留了历史快照也不会让云端目录越来越乱。CI 里还要加上“变更检测”只有当widgetbook.dart或组件代码发生变化时才执行构建上传。否则每笔 MR 都上传一次云端会堆满没有意义的版本。4.3 版本管理与 UI 评审机制上传到云端后版本管理就是新课题。Widgetbook Cloud 会保留历史版本也能对比两个版本之间的组件差异。我在团队里把云端地址放到 MR 模板里要求涉及组件变更的 MR 必须附带新的组件预览地址。评审者不需要拉分支直接打开链接就能看按钮状态、文案、间距是否和设计稿一致。代码审查和 UI 审查从此可以在同一个 MR 里完成。更进一步我们还在 CI 里把 widgetbook 生成的元数据和鸿蒙端自动化测试的截图做比对。组件尺寸、圆角、字号、颜色一旦出现偏差就报警。widgetbook_cli 提供的是“组件画像”自动化测试提供的是“运行证据”两者合在一起才能真正做到精密的组件管控。5. 常见问题与排查技巧实录5.1 鸿蒙设备上组件预览白屏Widgetbook 在鸿蒙真机上直接跑我试过能跑但体验一般。更常见的做法是把预览看作 Web 产物。如果你在鸿蒙设备上用内置浏览器打开云端地址发现白屏先检查 DevTools 控制台是否有资源加载失败。如果是在鸿蒙原生容器里嵌入 Flutter 的 widgetbook白屏大概率来自平台通道不兼容。打开鸿蒙侧日志搜MissingPluginException。组件里如果有MethodChannel调用在 widgetbook 用例里没有原生端响应就会一直卡在加载状态。解决办法是把平台能力抽象成接口在用例里注入 Mock。另一个容易被忽略的是字体族。鸿蒙系统默认字体和 Android 不完全一样如果组件里直接依赖fontFamily: HarmonyOS Sans而 Web 产物里没打包这个字体就会静默回退成默认字体。建议在 widgetbook 入口里显式加载自定义字体并设置 fallback。5.2 上传失败token 和偶发网络中断上传失败最常见的原因就是 token 无效或过期。CI 环境下没有浏览器交互必须用环境变量注入 token。如果出现 401第一件事不是重新申请 token而是检查环境变量是否真的传到了命令行里。我见过有人在 shell 脚本里写了--token $WIDGETBOOK_TOKEN但 secret 名字拼错结果 token 为空排查了很久。上传到一半连接断开CLI 不支持断点续传最简单的方式是重试。建议在 CI 脚本里加循环for i in 1 2 3; do dart run widgetbook_cli upload --token $WIDGETBOOK_TOKEN break sleep 10 done这样偶发网络问题不会让整条流水线变红。5.3 图片与字体资源路径不对组件里常用的Image.asset在 widgetbook 构建时资源路径以当前包为基准。如果 widgetbook 入口放在一个独立的 package 里而组件放在另一个 package很容易出现找不到资源的错误。我最后的解决方案是把需要预览的公共组件收敛到同一个 package 中所有资源都用asset: packages/foo/...的完整路径。这样无论 Flutter Web 构建还是鸿蒙 App 构建都能命中同一份资源。如果组件实在无法收敛就在 widgetbook 入口里手动注册所有依赖包的 asset 路径不要偷懒。5.4 组件通信在 Story 里失效在 widgetbook 用例里最容易被忽略的是状态管理。很多组件依赖Provider、Riverpod或GetIt直接放进 useCase builder 会报 “No Provider found”。解决办法是在 useCase builder 外层包上 ProviderScopeWidgetbookUseCase( name: Error with amount, builder: (context) ProviderScope( overrides: [ orderStateProvider.overrideWith(() mockOrderState), ], child: const CheckoutCard(), ), )组件间通过事件总线通信时要注意用例之间的隔离。如果多个 useCase 共用了同一个全局单例切换 useCase 后状态会串。我习惯给每个 useCase 提供一个独立的内存存储实例并在 tearDown 里清理。这样做的代价是多写一点样板代码但比“这个用例如预期另一个却拿到脏数据”要划算得多。5.5 构建产物体积过大widgetbook 的 Web 产物通常比普通 Flutter Web 要大因为所有组件、所有依赖都会被打包。组件一多首屏加载就会变慢。我建议按业务域拆多个 widgetbook 入口比如widgetbook_accounts.dart、widgetbook_checkout.dartCI 里并行构建、并行上传。虽然构建总时间会长一点但使用者在云端打开某个业务域的组件库时加载速度会明显提升。如果团队的组件数量已经过百还可以考虑在 useCase 中使用Theme局部覆盖避免为了展示一个按钮引入整个业务主题文件。6. 我的一点体会这次鸿蒙化适配做下来最深的感受是widgetbook_cli 本身不复杂复杂的是工程基建。组件命名、目录结构、状态管理边界、资源路径约束提前定好规则云端托管才能真正有价值。否则托管出来的只是一堆“能看不能用”的页面甚至会因为索引混乱放大团队里的沟通问题。有一点想单独说Widgetbook 的云端托管不是把截图扔上去而是把“组件在不同上下文里的真实行为”托管出来。和鸿蒙的适配让我意识到跨端组件库最难的不是代码差异而是“同一个组件在两个端上的表现如何保持一致”。把 widgetbook_cli 生成的元数据接回自动化测试是值得长期投入的方向。如果你也正在做 Flutter 鸿蒙化我建议先从小范围试点挑 3 到 5 个公共组件搭好 widgetbook 入口跑通 CLI 上传再考虑铺开。不要一上来就想建一个涵盖所有组件的大平台否则很容易被依赖问题淹没。先把一条链路走通后面扩大就是复制经验的事。