[Widgetbook]Flutter版Storybookで社内デザインパッケージを運用する
はじめに
Flutter版のStorybookであるWidgetbookをWebで社内誰でも見れるようにしたので、その記録を記事にしました。
Widgetbook とは
ドイツのスタートアップが作っているFlutter向けに開発されたカスタムUIカタログライブラリです。
Storybookを知っている人ならイメージがつきやすいと思いますし、実際にWidgetbook公式もStorybook.jsにインスパイアされていると記載されています。
Discover Widgetbook, a powerful Flutter package inspired by Storybook.js that simplifies the process of cataloging widgets, testing them across various devices and themes, and sharing them effortlessly with designers and clients.
実際に動くものを見た方が早いと思うので、公式が出しているWidgetbookを触ってみてください。
Widgetbook 導入の背景
所属している会社ではデザイナーがFigma上に親コンポーネントとなるマスターデータを作ってくれています。
イメージとしてはマテリアルデザインのFigmaみたいな感じです。
Figmaのコンポーネントは使い回す前提で作られているため、実装も同じ粒度でWidgetを実装していきます。
しかし、コードだけではコンポーネントを探しづらいですし、下手したら同じWidgetを2つ作ってしまう可能性があります。
この課題はメンバーが増えるとより深刻化していくと思います。
そこで実装済みのWidgetをUIカタログとして見ることで開発効率をあげたいと考えました。
Widgetbook の導入方法
導入方法について軽く触れたいと思います。
スニダンでは /snkrdunk_design
というディレクトリを切って、UIコンポーネント用のマルチパッケージを作っています。
/snkrdunk_design
は--template=plugin
として作られているためflutter build web
はできません。
そのため、/snkrdunk_design/widgetbook
というWidgetbook用の新しいFlutterプロジェクトを作成しています。
みやすくするとこんな感じです
.
├── lib
├── pubspec.yml
└── snkrdunk_design # 共通コンポーネント用のplugin
├── lib
├── pubspec.yml
├── test
└── widgetbook # Widgetbook用のFlutterプロジェクト
├── lib
│ ├── main.dart
│ ├── main.directories.dart
│ └── src
│ └── button.dart
└── pubspec.yml
こうすることで、それぞれのディレクトリ(パッケージ)は必要なだけのパッケージをpubspec.yml
に書けるので依存関係がはっきりしていて綺麗だと思います。
また、後述するWidgetbookのホスティングの際も/snkrdunk_design
に変更がある時のみ行えば良いので効率が良いです。
pubspec.yml
packageは3つ入れます。本来はwidgetbook
だけでも開発可能ですが、開発効率のためにwidgetbook_annotation
とwidgetbook_generator
を導入します。
dependencies:
widgetbook: ^3.7.1
widgetbook_annotation: ^3.1.0
snkrdunk_design:
path: ../
dev_dependencies:
widgetbook_generator: ^3.7.0
build_runner:
最新バージョンはこちらから
snkrdunk_design/widgetbook/main.dart
次にWidgetbookのvoid main()
関数を記述するmain.dart
の中身です。
見やすいために、後述するaddons
を空にしていますがとりあえず動きます。
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
import 'widgetbook.directories.g.dart';
void main() {
runApp(const WidgetbookApp());
}
.App()
class WidgetbookApp extends StatelessWidget {
const WidgetbookApp({super.key});
Widget build(BuildContext context) {
return Widgetbook.material(
// Use the generated directories variable
directories: directories,
addons: [],
integrations: [
// To make addons & knobs work with Widgetbook Cloud
WidgetbookCloudIntegration(),
],
);
}
}
また、UIカタログとして表示したいコンポーネントは以下のように記述します。
今回はFilledButton
の例です。
import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
.UseCase(
name: 'FilledButton',
type: FilledButton,
Widget filledButtonUseCase(BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 100),
width: double.infinity,
alignment: Alignment.center,
child: Column(
children: [
FilledButton(
onPressed: () {},
child: const Text("Enabled"),
),
const SizedBox(height: 16),
const FilledButton(
onPressed: null,
child: Text('Disabled'),
),
],
),
);
}
最後にbuild_runnerを回しましょう!
$ flutter pub run build_runner build --delete-conflicting-outputs
flutter run -d chrome -t 該当のmain.dart
これで以下のような画面が見れるはずです。
それぞれの詳しい説明などは公式が分かりやすいので是非読んでください。
Widgetbook を活用する(Addon)
このままでも便利ですが、Addonという機能を使ってより便利にすることができます。
Addonとは画面右側のサイドバーに出ている部分のことで、ダークモードを表示したり、テキストサイズを2倍に変更したりなどができます。
おすすめのAddonsはここら辺です
- DeviceFrameAddon
- TextScaleAddon
- MaterialThemeAddon
- InspectorAddon
Widgetbook をみんなに見てもらう
Widgetbookを閲覧したい時に、都度flutter run
していては開発効率は上がりませんし、Flutterエンジニア以外が見ることができません。
開発者、または社内の開発者以外の人が簡単に閲覧できるために、今回はホスティング環境を3つ検討しました。
Widgetbook cloud
公式が出しているWidgetbook cloudを使えば簡単にホスティングができそうです。
しかし、現在招待制のため申請したのですがwaiting listに登録したよと言われてから2ヶ月が経ちそうです。
GitHub pages
Flutter WebもWebのため、GitHub pagesが使えます。ただし閲覧権限等でGitHubはめんどそうだなぁーと思いやめました。
publicにしている個人開発のリポジトリなどは相性良さそうです。
実際にこちらのプロジェクトでGitHub pagesにデプロイするGitHub Actionsを組んでみました。
AWS s3 ←これにした
ほかに考えられるのはs3などの外部のホスティング環境ですが、ありがたいことに社内では誰でも使えるs3環境がありました。
閲覧に関してもVPNを繋いでいたら誰でも閲覧可能な環境です。
そのため、今回はs3を採用しました。
s3もGitHub Actionsでデプロイ可能で、以下のようなActionを組んでいます。
トリガーはdevelop
ブランチにマージされた時で、"snkrdunk_design/**"
に変化があった時のみ走らせています。
また、build時に--base-href
オプションでパスを指定しないとs3で見ることができません。
name: deploy widgetbook for s3 only develop branch and widgetbook directory changes
on:
push:
branches:
- develop
paths:
- "snkrdunk_design/**"
jobs:
deploy_widgetbook:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3.3.0
- name: Install dependencies
uses: ./.github/actions/install_dependencies
- uses: aws-actions/configure-aws-credentials@v4.0.1
with:
role-to-assume: arn:aws:iam::xxxxxxxxxx:role/xxxx
aws-region: ap-northeast-1
role-duration-seconds: 3600
role-session-name: xxx
# --base-hrefには、s3のバケット名を指定する
- name: build web
run: |
cd snkrdunk_design/widgetbook
flutter build web --base-href=/xxx/xxx/
- name: Copy files to s3
run: |
cd snkrdunk_design/widgetbook
aws s3 cp ./build/web/ s3://xxx/xxx/xxx/ --acl public-read --recursive
Widgetbook 発展のために
最後に、まだ取り組めていないですが、今後Widgetbookを使ってやりたいことを書きます。
Knobs
UIカタログ内のテキストや数値、カラーを右サイドバーから変更できる機能です。
ボタンのテキストなどは可変なことが多く、その時々で文字を設定して検証したいケースも多いかと思いますが、そういう時に役立ちそうです。
Goldenテストに再利用できるのでは?
スニダンでは最近Goldenテストを取り入れ始めています。
例えば先ほど紹介したこちらのコードですが、filledButtonUseCase
の部分をGoldenテストに再利用できるような気がしました。
しかし、Knobを使っている場合エラーになってしまうようです。
そんな中公式のリポジトリを漁っていると、widgetbook_4というリポジトリでゴールデンテストの再利用をしやすくするためについて書かれていました。
詳細は省きますが、将来取り入れられる可能性が高そうです。
import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
.UseCase(
name: 'FilledButton',
type: FilledButton,
Widget filledButtonUseCase(BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 100),
width: double.infinity,
alignment: Alignment.center,
child: Column(
children: [
FilledButton(
onPressed: () {},
child: const Text("Enabled"),
),
const SizedBox(height: 16),
const FilledButton(
onPressed: null,
child: Text('Disabled'),
),
],
),
);
}
さいごに
最後まで読んでいただきありがとうございます。
仕事でやってること、個人開発でやってることなど色々発信していこうと思うので良かったらフォローしてください!
Discussion