LoginSignup
8
3

More than 5 years have passed since last update.

OpalでVirtualDOMを実装して自作フロントエンドフレームワークを作ろうとしてる話

Last updated at Posted at 2018-12-16

Opal (Ruby-to-JavaScript compiler) Advent Calendar 2018 16日の記事です

最近のフロントエンドでは、React.js, Vue.js, Angular.js, Riot.js, Elm, Hyperapp.....などなど多様なフレームワークが使われていますね。

このようなフレームワークはVirtualDOMという技術があってこそなんです。

私は、VirtualDOMに興味が出たことや300行で実装されてるHyperappのコードを見たりして、自分でVirtualDOMを実装したいという気持ちが湧いてきました。
そして、自作のフロントエンドフレームワークを作って見たいなぁと思いました。

VirtualDOMって何?

JavaScriptから効率よくDOMを扱ってよりモダンな動きを実現できる。

Webでの流れは大体このようになっている

①Webに表示するためにVDOMをRealDOMへ
VDOMはJavaScriptのObjectである。
このようなVDOMがあれば

{
  "nodeName": "div",
  "attributes": {},
  "children": []
}

下のようなHTMLが吐かれるだろう。

<div></div>

②ステートの変更があれば、変更する部分の差分をとって、RealDOMへ

という感じですね。(ざっくりしすぎた)

詳しい説明は
https://postd.cc/the-inner-workings-of-virtual-dom/
が参考になりました。

OpalでVirtualDOMを実装しようとしている

https://github.com/anharu2394/rubelm で開発中です。
Rubelmという名前は最初RubyでもElmっぽいことできたらいいなぁと思ったからなのですが今は別にそこはどうでもよくなりましたww今はHyperappっぽくしたいなって感じです。
Preactの実装だとVNode Classがありますが、Hyperappの実装では純粋なObjectを使っています。
なので現状Rubelmでも純粋なHashで実装してます(Rubyとして良いのか?)

コードはこんな感じです。

module Rubelm::Vdom 
    require "opal-browser"
  def self.render(view,root)
    self.create(view,root)
  end
  def self.create(vdom,parent)
    element = Browser::DOM::Element.create(vdom[:nodeName])
    vdom[:attributes].each do |name, val|
      element.set(name,val)
    end
    vdom[:children].each do |child|
        child.instance_of?(Hash) ? create(child, element) : element.text = child
    end
    parent << element
  end
  def self.recycle(ele)
    attributes = {}
    children = []
    ele.attributes.each  do |name, value|
      attributes[name] = value
    end
    ele.children.to_ary.each do |node|
      node.instance_of?(Browser::DOM::Element) ? children << recycle(node) : children << node.whole
    end
    node = {
      nodeName: ele.name.downcase,
      attributes: attributes,
      children: children
    }
  end
  # patchは実装途中
  def self.patch(old_node,new_node,root)
    ele = root.elements.to_ary[0]
    if old_node[:nodeName] != new_node[:nodeName]
      ele.remove
      create(new_node,root)
    elsif ele.attributes.namespace != new_node[:attributes]
      ele.attributes.merge!(new_node[:attributes])
      root
    end
    new_node[:children].each_with_index do |child, i|
      child.instance_of?(Hash) ? patch(old_node[:children[i], child, ele]) : ele.text = child
    end
    root
  end
end

createは①の役割(VDOMを画面へ表示させる)
recycleはステートが変更された時点でのRealDOMをVdomにする関数
patchはrecycleされたVDOMとステート変更後のVDOMを比較して差分を表示させる関数(まだ実装途中)

このような感じです。

Opal-rspecでTDDしてる

Rspecというテストフレームワークは誰でもご存知ですよね。
それのOpal版があるんです。
opal-rspecはブラウザーで走らせることもできるので、Vdomのテストもできます!!
重いですがw
スクリーンショット 2018-12-16 13.55.28.png

Rubelmで現状できること

この通り、patch関数が途中のため、ステートの変更などができず、画面に何かを表示するだけですww

https://github.com/anharu2394/rubelm に詳しく書いてありますが

gem 'opal-sprockets'
gem 'rubelm'

をインストールしていただき、

require "rubelm"
require "opal-browser"
include Rubelm::Html
view = div({class: "hello"},[
    div({class:"world"}, "hello, world!")
  ])
Rubelm::main(view,$document.body)

こんな感じで書くと

できますねって感じです。

8
3
0

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
8
3