LoginSignup
12

More than 5 years have passed since last update.

RubyのVirtual DOM実装 'Hyalite' について

Last updated at Posted at 2015-12-20

RubyKaigi 2015でRubyのVirtual DOM実装であるHyaliteについて発表しました。
このエントリではHyaliteについて書こうと思います。
このエントリは仮想DOM/Flux Advent Calendar の 20日目の記事として書いています。

Rubyはサーバーサイドの言語という印象があると思いますが、OpalというRubyからJavaScriptへのトランパイラを使うことによって、
Rubyでフロントエンドのコードを書くことができます。
Virtual DOMの実装に関してはReact.jsを参考に実装しました。React.jsの実装を参考にしてRubyで独自に実装していこうと思っていたのですが、React.jsのコードが難しくて私にはとても読み解くことはできませんでした。そこで方針を少し変更してReact.jsのコアのロジックはJavaScriptのコードをそのままRubyに移植して、少しずつRubyらしいコードにリファクタリングしていくことにしました。
Hyaliteの今の状態はやっとサンプルのプログラムが動くという感じです。具体的には、TodoMVCとRubyKaigiでの発表に使ったスライドアプリケーション(https://github.com/youchan/hyaslide )です。
どちらもソースコードを公開していますが、スライドのほうは発表のための間に合わせのコードなのでいろいろ酷い部分があるかと思います。
ここでは、TodoMVCのコードをみながらにHyaliteについて解説しようと思います。

TodoMVC

ソースコードはhttps://github.com/youchan/hyalite-todo にあります。
すべてのコードを説明するのは大変ですので、ここでは

  • config.ru
  • index.html.haml
  • app/application.rb

の3つのファイルについて説明します。

config.ru

config.ruはRubyのWebアプリケーションサーバーのエントリポイントになる設定ファイルです。
ここでは、Opal::Serverの設定をしています。Opal::ServerはサーバーサイドでRubyのコードをJavaScriptにコンパイルしてクライアントサイドに返します。

require 'bundler'
Bundler.require

run Opal::Server.new { |s|
  s.append_path 'app'
  s.append_path 'node_modules'

  s.debug = true
  s.main = 'application'
  s.index_path = 'index.html.haml'
}

index.html.haml

Opalにはopal-hamlというOpalでhamlを使うためのライブラリがあります。

!!!
%html(lang="en" data-framework="hyalite")
  %head
    %meta(charset="utf-8")
    %title Hyalite • TodoMVC
    %link(rel="stylesheet" href="node_modules/todomvc-common/base.css")
    %link(rel="stylesheet" href="node_modules/todomvc-app-css/index.css")

  %body
    %section.todoapp
    %footer.info
      %p Double-click to edit a todo
      %p
        Created by
        %a(href="http://github.com/youchan/") youchan
      %p
        Part of
        %a(href="http://todomvc.com") TodoMVC

    = javascript_include_tag 'application'

%section.todoappの下にアプリケーションが生成するDOMがマウントされます。
application.jsというJavaScriptファイルをインクルードするように記述されています。実際にはapplication.rbというRubyのコードがOpalによってJavaScriptにコンパイルされます。

application.rb

クライアントサイドのエントリポイントになります。

Appクラス

module App
  def self.render
    Hyalite.render(Hyalite.create_element(TodoApp, {model: model}), $document['.todoapp'])
  end

  def self.model
    @model ||= TodoModel.new
  end
end

Hyalite.render()はReactではReactDOM.render()にあたり、Hyalite.create_element()React.createElement()にあたるメソッドです。大体Reactと同じようなコードになることがみてとれます。
$document['.todoapp']としているところはDOMのエレメントを取得しています。Hyaliteではopal-browserというライブラリを利用してDOMの操作を行っています。

$document.ready do
  App.model.subscribe do
    App.render
  end

  App.render
end

ドキュメントが準備できたら、App.renderを呼びだしてVirtual DOMをレンダリングしています。(結果的に実際のDOMがレンダリングされます。)

Hyalite::Componentモジュール

ReactのcreateClass()で作られるコンポーネントに対応するものは、Hyalite::Componentモジュールをincludeしたクラスになります。

class TodoApp
  include Hyalite::Component

  def initial_state
    {
      nowShowing: :all,
      editing: nil,
      newTodo: ''
    }
  end
end

ここにある、initial_stateはReactではgetInitialState()に対応します。

component_did_mountメソッド

component_did_mountメソッドはコンポーネントがマウントされた後に呼ばれます。(そのままですね)
このようなフックはこの他にcomponent_will_mount, component_did_mount, component_will_unmount, component_will_update, component_did_updateなどがあります。

  def component_did_mount
    router = Router.new
    router.route('/') { set_state({nowShowing: :all}) }
    router.route('/active') { set_state({nowShowing: :active}) }
    router.route('/completed') { set_state({nowShowing: :completed}) }
  end

TodoAppではcomponent_did_mountのなかで、ルーターの設定をしています。ルーターには、opal-routerというライブラリを使っています。

TodoApp#render

renderのなかの処理をちょっと覗いてみましょう。

  def render
    # ---- snip ----
    div(nil,
      header({className: 'header'},
        h1(nil, "todos"),
        input({
          className:'new-todo',
          placeholder:'What needs to be done?',
          autofocus:true,
          onKeyDown: -> (event) { handle_new_todo_key_down(event) },
          onChange: -> (event) { handle_change(event) },
          value: @state[:newTodo]
        })),
      main,
      footer)
  end

create_elementの代りにheaderh1などのメソッドの呼びだしがあります。
HyaliteではReactのJSXの代りにショートハンドを用意しました。
Hyalite::Component::ShortHandモジュールをincludeすることでショートハンドを利用することできます。

イベントについて見ていくと、以下のコードがあります。

          onKeyDown: -> (event) { handle_new_todo_key_down(event) },

キーボードイベントを拾って、handle_new_todo_key_downというメソッドを呼びだしています。ここでは、Rubyのラムダをつかってコードブロックを参照しています。

最後に

駆け足でHyaliteについて見ていきました。まだ生まれたばかりのフレームワークなので、いろいろ不備があると思います。不具合などありましたぜひissueでお知らせください。

Happy hacking!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12