タイトル通り、KombuというRuby on Railsでjavascriptで描画するcomponentを指定してrenderできるgemをリリースしました💎✨ (コンポーネント・レンダラブルを略してコンブ です)
モチベーション
Ruby on Railsを利用してサービスが成長してくるとVue.jsといったフロントエンドライブラリを利用することになると思いますが、 これがだんだん成長してくるとRailsのViewとフロントエンドフレームワークがどんどん密結合になっていき、 見通しが悪くなったり適切な境界を持たないことによるE2Eテストの肥大化及び明示的な検証が困難といった課題が出てくるなぁと思っていたので、 以下の記事に書いたようなControllerから直接ComponentをrenederすることでRailsのView層を隠蔽し一定の境界を手軽に作れるのでは?という感じで作りました。
似たようなライブラリだと以下のようなものがありますが、React-Railsの場合はReactを採用してないと採用できないのとInertia.js Rails Adapterはinertia.jsを導入しないと採用できず境界を分けたいだけのニーズではtoo muchかなと思い作成に至った次第です。(Vue.jsでの使用を主に想定してします)
使い方
使い方は割と簡単で以下をGemfileに追加 + bundle install
して
gem "kombu"
以下の通りに自身のアプリケーションに合わせて、jsからmountする要素のid、css/jsの読み込み用のタグの生成ロジック、隠蔽するview templateを設定して
Rails.application.configure do # NOTE: (OPTIONAL) id of the element (div) to mount (default: `vue-root`) # config.kombu.default_mount_element_id = 'vue-root' # NOTE: (REQUIIRED) Specify a proc that generates a tag that reads a javascript entry. # See `lib/kombu/renderable.rb` for instance variables provided by kombu that can be used within proc. config.kombu.javascript_entry_tag_proc = -> { helpers.javascript_pack_tag(@entry, defer: true) } # NOTE: (REQUIIRED) Specify a proc that generates a tag that reads a css entry. # See `lib/kombu/renderable.rb` for instance variables provided by kombu that can be used within proc. config.kombu.stylesheet_entry_tag_proc = -> { helpers.stylesheet_pack_tag(@entry) } # NOTE: (OPTIONAL) template of the view to render that contains the component. (default: See below) # config.kombu.default_entry_view_template = <<~ERB # <div id="<%= @kombu_mount_element_id %>"> # <%= kombu_component_tag %> # </div> # <% content_for :stylesheet do %> # <%= kombu_stylesheet_entry_tag %> # <% end %> # <% content_for :javascript do %> # <%= kombu_javascript_entry_tag %> # <% end %> # ERB end
任意のControllerでKombu::Renderable
をincludeするとkombu_render_component
を使って任意のComponentを含んだhtmlタグをrenderすることが出来ます。renderする際にViewを必要としないので、layouts以外のViewファイルを削除することが可能になり、フロントエンドとサーバーサイドの境界を作ることが出来ます。
class ArticlesController < ApplicationController include Kombu::Renderable def index @title = "Articles" @articles = [{id: 1, title: "artile1", body: "body1"}, {id: 2, title: "artile2", body: "body2"}] kombu_render_component("article-index-page", attributes: {title: @title, ":articles": @articles.to_json}) # NOTE: The following html is rendered. # <div id="vue-root"><artile-index-page title="Articles" :articles="[{"id":1,"title":"artile1","body":"body1"},{"id":2,"title":"artile2","body":"body2"}]"></div> end end
またkombu_render_component
に渡された値を明示的に検証できるRSpecのmatcherを用意しているのでrequest specでサーバーサイドから渡す値を明示的に検証することが出来ます🙆♂️
describe "GET /articles", type: :request do before { get articles_path } it "Specific arguments must be passed to kombu_render_component." do title = "Articles" articles = [{id: 1, title: "artile1", body: "body1"}, {id: 2, title: "artile2", body: "body2"}] expect(controller).to kombu_component_rendered("article-index-page", attributes: {title: title, ":articles": articles.to_json}) end end
仕組み
基本的にはkombu_component_rendered
で指定した名称とattiributesを持つタグとjavascript_entry_tag_proc
、stylesheet_entry_tag_procで生成したタグを
config.kombu.default_entry_view_template`で指定したtemplateに埋め込んで返却しているだけです🧑🏭
詳しくはこちらの該当コードを参照してください。
おわりに
実際に自分の個人サービスで利用してるのですがapp/views
配下がlayouts
以外消せてスッキリでした👍