(個人的な観測範囲からですが)React界隈を中心にデザインシステムなどとともに、Storybookを導入している企業が増えてきているように思います。
自分は直近では、Ruby on RailsでSSRをメインに開発をしていることが多いため、導入がやや難しいと感じていました。
しかし、ViewComponentを導入していると比較的簡単に導入可能であることを知ったので、今回試してみます。
今回は bootstrapが入っている状態で検証をしているので、予め承知ください。
検証したバージョン
ViewComponent、ViewComponent::Storybook の導入
そもそもですが、ViewComponentは、GitHubの開発者がメインとなって開発したOSSです。
コンポーネントをこれまでのRailsでは、View部分のHTMLテンプレート(ERb、HAMLなど)のみでしか扱えなかったのですが、テンプレートとRubyオブジェクトのセットとして書くことができるようになります。
今回は、ViewComponentの紹介ではないので、メリットは以下の公式ドキュメントを読んでもらえればと思います。
個人的に導入、活用していて、「Single responsibility(単一責任)」「Testing(テスト)」あたりの見通しの良さは、これまでのRailsにはなかったように感じます。
開発も精力的に行われているため、目立ったバグなどは今のところなさそうです。またパフォーマンス自体も通常のpartialよりも早いと謳われており、その点もそこまで気にせずに使えるのもメリットかと思います。
さて、ViewComponent、ViewComponent::Storybookの導入ですが、gemなので、Gemfileに以下を追加します。
gem 'view_component', require: 'view_component/engine' group :development do gem 'view_component_storybook' end
Storybookを本番で使わない場合は、group以下にしておくとよいかと思います。
あとはgemをインストールしておきます。
$ bundle install
最後に、必要なnpmパッケージを追加します。今回はyarnを利用しています。
$ yarn add @storybook/server @storybook/addon-controls --dev
ViewComponentを作成する
まずはViewComponentを作成します。
今回は preview機能を使いたいため、previewオプションを付けて作成します。
$ bundle exec rails generate component Button type --preview
これにより、以下の4つのファイルが作成されます。 button_component_preview.rb
は --preview
をつけていないと作成されません。
app/components/button_component.html.erb app/components/button_component.rb test/components/button_component_test.rb test/components/previews/button_component_preview.rb
コンポーネントの実装
コンポーネントを実装していきます。
# frozen_string_literal: true class ButtonComponent < ViewComponent::Base def initialize(type: :primary, large: false) @type = type @large = large end def classes cls = ['btn', "btn-#{@type}"] cls += ['btn-lg'] if @large cls.join(' ') end end
<button class="<%= classes %>"> <%= content %> </button>
プレビューの実装
ViewComponentにはプレビュー機能が実装されており、プレビューのクラスを実装した上で、特定のパスにアクセスするとそのコンポーネントの表示を確認することができます。
# frozen_string_literal: true class ButtonComponentPreview < ViewComponent::Preview def default(type: :primary, large: false) type = type.to_sym if type large = large == 'true' render(ButtonComponent.new(type: type, large: large)) { type.to_s } end end
この状態で、 bundle exec rails s
などでサーバーを起動して、 /rails/view_components/button_component/default?type=primary&large=true
にアクセスすると以下のようにコンポーネントの表示を確認することができます。
Storybookで表示できるようにする
ViewComponentのプレビュー機能で、特定のパスにアクセスすることでコンポーネントを表示させることができるようになりました。
ここから、ViewComponent::Storybook でStorybookを表示します。
ViewComponent:Storybook を使えるように準備する
ViewComponent::Storybook を使うためにはもう少し準備が必要です。
まずは、以下を config/application.rb
に追加します。
module RailsViewComponentsStorybook class Application < Rails::Application config.load_defaults 6.1 require "view_component/storybook/engine" config.view_component.show_previews = true end end
次に、 .storybook/main.js
、 .storybook/preview.js
でStorybook自体の構成を設定しておきます。
module.exports = { stories: ["../test/components/**/*.stories.json"], addons: ["@storybook/addon-controls"], };
export const parameters = { server: { url: `${location.protocol}${location.hostname}${location.port !== "" ? ":3000" : ""}/rails/view_components`, }, }
ここでサーバーを再起動して、 /_storybook/index.html
にアクセスすれば、Storybook自体にはアクセスできます。
Storyを作成する
この時点では、Storyを作成していないので、何も表示されません。
そのため、作成したコンポーネントをStoryで表示できるようにします。
class ButtonComponentStories < ViewComponent::Storybook::Stories title 'ボタン' story(:default) do constructor(type: select(%i[primary secondary success danger warning info light dark link], :primary), large: boolean(false)) end end
ViewComponent:Storybook でのStoryの書き方は以下の公式ドキュメントを参考にしてください。
jonspalmer.github.io
このままではまだ表示できません。
このRubyクラスから、Storybookのjsonファイルを作成して、そのjsonファイルから、Storybookの最終的なアセットを出力します。
$ bundle exec rake view_component_storybook:write_stories_json $ yarn build-storybook -o public/_storybook
これで /_storybook/index.html
にアクセスしてStorybookを表示すると、ボタンのStoryが追加されています。
Rakeタスクにして、一発で出力する
Storyの変更があるたびに、コマンドを2回叩くのは面倒なので、Rakeタスクにしてしまいます。
namespace :storybook do desc 'rebuild storybook' task rebuild: :environment do Rake::Task['view_component_storybook:write_stories_json'].invoke sh 'yarn build-storybook -o public/_storybook' end end
まとめ
Ruby on Rails 単体でSSRメインで開発していると、なかなかStorybookを導入しにくいと感じていましたが、ViewComponentを利用している場合は、比較的簡単に導入することができました。
デザイナーなどとの協業の場合にも、どのようなコンポーネントのデザインになるかを簡単に動く形で共有できるようになるので、便利そうですね。