Slide 1

Slide 1 text

Rustで自作しながら学ぶ 仮想DOM 0Yu@yud0uhu - DOMDOMトークス #1 - 1

Slide 2

Slide 2 text

自己紹介 ● 名前:0yu(おゆ) ● 領域:Webフロントエンド ● Like:猫、甘いもの 2 っっっz yud0uhu

Slide 3

Slide 3 text

仮想DOMを理解したい 3

Slide 4

Slide 4 text

DOMとはなにか ● JavaScriptを使ってインタラクティブにHTMLを操作す るためのブラウザAPI ● 木構造をしている ○ HTMLのドキュメントを「ノードツリー(DOMツ リー)」で表現する 4 document.xxx.xxx.element.innerHTML = "Hello World!" ;

Slide 5

Slide 5 text

DOMとはなにか:DOMツリー構造 < html > < head > < title >私のウェブサイト title > head > < body > < h1 >ようこそ h1 > < p >これは私のウェブサイトです。 p > body > html > 5 - ドキュメント(ルート) - html - 頭 - タイトル - "私のウェブサイト" - 体 - h1 - "いらっしゃいませ" -p - 「これは私のウェブサイトです。」

Slide 6

Slide 6 text

DOM操作の課題 ● 子ノードが複数あった親ノードが変更された場合、全体 の親ノード全体を再構築し、全てのコンテンツを再度レ ンダリングする ● Webページが多くのノードを持つほどDOMの更新頻度が 増え、そのたびにレンダリング処理が走る ○ パフォーマンスの懸念 6

Slide 7

Slide 7 text

仮想DOMとはなにか ● リアルDOMツリーの一部をJavaScriptのオブジェクト (仮想DOMツリー)として表現したもの ● 仮想DOMの変更前後で生じた差分を検知し、最低限の変 更のみ実際のリアルDOMに適用させて、DOMの更新頻度 を減らす ○ コンセプトはDOM操作を最小限に抑え、レンダリング コストを下げること 7

Slide 8

Slide 8 text

仮想DOMを自作する 8

Slide 9

Slide 9 text

仮想DOMを自作する ● なぜ作る? ○ 実際に自分の手を動かすことで、仕組みの理解が深ま るため 9

Slide 10

Slide 10 text

仮想DOMを自作する ● DOM は、特定のプログラミング言語から独立し、文書の 構造表現を単一の一貫した API から利用できるように設 計されています。 ほとんどのウェブ開発者が JavaScript でしか DOM を使用しないとしても、この Python の例 が示すように、 DOM の実装はどの言語でも構築するこ とができます。 ○ DOM の紹介 - Web API | MDN 10

Slide 11

Slide 11 text

作ったもの 11

Slide 12

Slide 12 text

作ったもの ● Rust製のミニマムな仮想DOM API ● 入力フォームに入力された値を、仮想DOMを介してリア ルタイムにプレビューに同期させる ● チェックボックスでプレビューの表示をクリアできる ● HTTPサーバーのクレートはseanmonstar/warpを使用 ○ 非同期処理はtokio、HTTPはhyperがベース 12

Slide 13

Slide 13 text

作ったもの 13

Slide 14

Slide 14 text

実装について 14

Slide 15

Slide 15 text

render 15

Slide 16

Slide 16 text

render ● 仮想DOMの要素をHTMLドキュメントに変換 16

Slide 17

Slide 17 text

render pub fn virtual_dom_to_html (node: &ElementType) -> String { match node { ElementType::Text(text) => text.clone(), ElementType::Element(tag, attrs, children) => { let attrs_str = attrs . iter() . map(|(key, value)| 17 format!("{}=\"{}\"", key, value)) . collect::>() . join(" "); let children_str = children . iter() . map(|child| virtual_dom_to_html (child)) . collect::>() . join(""); format!("<{} {}>{}{}>", tag, attrs_str, children_str, tag) } } }

Slide 18

Slide 18 text

diff&patch 18

Slide 19

Slide 19 text

diff ● 2つの仮想DOMツリーの差分を検出し、更新の必要があ るノードを特定 19 https://engineering.monstar-lab.com/jp/post/2022/05/27/Is-Vir tual-DOM-Outdated/

Slide 20

Slide 20 text

diff 20 pub fn update_dom(old: &VNode, new: &VNode) -> AppResponse { let mut diff = Vec::new(); let removed_nodes = find_removed_nodes (old, new); for removed_node in removed_nodes { diff.push(Diff::RemoveNode(removed_node.clone())); } let added_nodes = find_added_nodes (old, new); for added_node in added_nodes { diff.push(Diff::AddNode(added_node.clone())); } let html = virtual_dom_to_html (&new.element_type); for change in &diff { match change { Diff::AddNode(node) => println!("Added Node: {:?}", node), Diff::RemoveNode(node) => println!("Removed Node: {:?}", node), } } AppResponse { diff, html } }

Slide 21

Slide 21 text

patch ● 仮想DOMノードの差分を元に、実際のDOMノードに新し いノードを追加したり、古いノードを削除 21 https://engineering.monstar-lab.com/jp/post/2022/05/27/Is-Vir tual-DOM-Outdated/

Slide 22

Slide 22 text

patch 22 fn find_added_nodes (old: &VNode, new: &VNode) -> Vec { let mut added_nodes = Vec::new(); find_added_nodes_recursive (&old.element_type, &new.element_type, &mut added_nodes); added_nodes } fn find_added_nodes_recursive (old: &ElementType, new: &ElementType, added_nodes: &mut Vec) { if old != new { if !new.is_empty_text_node () { added_nodes.push(VNode { element_type: new.clone(), }); } } else if let ElementType::Element(_, _, old_children) = old { if let ElementType::Element(_, _, new_children) = new { for (old_child, new_child) in old_children.iter().zip(new_children.iter()) { find_added_nodes_recursive (old_child, new_child, added_nodes); } } } }

Slide 23

Slide 23 text

patch 23 fn find_removed_nodes (old: &VNode, new: &VNode) -> Vec { let mut removed_nodes = Vec::new(); find_removed_nodes_recursive (&old.element_type, &new.element_type, &mut removed_nodes); removed_nodes } fn find_removed_nodes_recursive ( old: &ElementType, new: &ElementType, removed_nodes: &mut Vec, ) { if old != new { if !old.is_empty_text_node () { removed_nodes.push(VNode { element_type: old.clone(), }); } } else if let ElementType::Element(_, _, old_children) = old { if let ElementType::Element(_, _, new_children) = new { for (old_child, new_child) in old_children.iter().zip(new_children.iter()) { find_removed_nodes_recursive (old_child, new_child, removed_nodes); } } } }

Slide 24

Slide 24 text

Rust製フロントエンドフレームワーク /yew 24

Slide 25

Slide 25 text

yewの紹介 ● 仮想DOM+コンポーネントベースでWebを記述できる Rustのフロントエンドフレームワーク ● コードはWASMに変換され、ブラウザ上で実行できる 25

Slide 26

Slide 26 text

yewの実装との比較 26

Slide 27

Slide 27 text

render 27

Slide 28

Slide 28 text

28 https://github.com/yewstack/yew/blob/d0419a278dc126af4556c9afae2ef6b00b5fef36/packages/yew/src/app_handle.rs #L31-L46

Slide 29

Slide 29 text

diff 29

Slide 30

Slide 30 text

30 https://github.com/yewstack/yew/blob/d0419a278dc126af4556c9afae2ef6b00b5fef36/packages/yew/src/app_handle.rs#L5 9-L61

Slide 31

Slide 31 text

patch 31

Slide 32

Slide 32 text

32 https://github.com/yewstack/yew/blob/d0419a278dc126af4556c9afae2ef6b00b5fef36/packages/yew/src/app_handle.rs#L6 8-L71

Slide 33

Slide 33 text

終わりに ● yewの内部実装は学びが多そう ● Elmライクな構造体componentスタイルが面白そう 33