Edited at

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

More than 3 years have passed since last update.

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!