Svelteが仮想DOMを使用していないってどういうこと

## はじめに

数年前からフロントエンド界隈で話題になっているSvelteですが、最近重い腰をあげて学び始めました。
その中で「仮想DOMを使用しない」というのが具体的にどういうことのか実動作ベースで調べてみました。

https://svelte.jp/

## Svelteとは

### 概要

* JavaScriptフレームワークの1種
* [Vue.js]()や[React](https://ja.reactjs.org/)のようにWebアプリケーションやUIを構築する

### 3つの特徴

1. Write less code（少ないコード量で記述）
1. No Virtual DOM（仮想DOMを使用しない） ← 今回の記事の目的
1. Truly reactive（真にリアクティブ）

## 調査用のSvelteプロジェクトを作成

### テンプレートをダウンロード

```terminal
$ npx degit sveltejs/template my-svelte-project
$ cd my-svelte-project
$ npm install
$ npm run dev
```

### DOMの変化がわかりやすいようにApp.svelteを改変

（余談） QiitaのコードブロックはSvelteに対応していないっぽい。。。以下は`vue`で代用

```vue
<script>
	let greetingText = "Hello";
	let areaName = "World";
	function changeData() {
		greetingText = greetingText == "Hello" ? "Good Night" : "Hello";
		areaName = areaName == "World" ? "Japan" : "World";
	}
</script>

<main>
	<h1>{greetingText} Svelte {areaName}!</h1>
	<a href="https://svelte.dev/tutorial">Svelte tutorial</a>
	<button on:click={changeData}>change</button>
</main>

<style>
	main {
		text-align: center;
		padding: 1em;
		max-width: 240px;
		margin: 0 auto;
	}

	h1 {
		color: #ff3e00;
		text-transform: uppercase;
		font-size: 4em;
		font-weight: 100;
	}

	@media (min-width: 640px) {
		main {
			max-width: none;
		}
	}
</style>
```

### アプリを表示

* http://localhost:5000/ にアクセス
* changeボタン押下でメッセージが切り替わる簡単なアプリ

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308848/c297a388-61d2-056a-e64d-3903d5335975.gif)

## 「仮想DOMを使用しない」の検証

### devツールを開いて生成物を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308848/84851f46-6f2e-2f88-5597-296c750d2327.png)

### bundle.jsを覗いてみる

```js

(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
var app = (function () {
    'use strict';

    …（中略）…

    function create_fragment(ctx) {
    	let main;
    	let h1;
    	let t0;
    	let t1;
    	let t2;
    	let t3;
    	let t4;
    	let a;
    	let t6;
    	let button;
    	let mounted;
    	let dispose;

    	const block = {
    		c: function create() {
    			main = element("main");
    			h1 = element("h1");
    			t0 = text(/*greetingText*/ ctx[0]);
    			t1 = text(" Svelte ");
    			t2 = text(/*areaName*/ ctx[1]);
    			t3 = text("!");
    			t4 = space();
    			a = element("a");
    			a.textContent = "Svelte tutorial";
    			t6 = space();
    			button = element("button");
    			button.textContent = "change";
    			attr_dev(h1, "class", "svelte-1tky8bj");
    			add_location(h1, file, 10, 1, 231);
    			attr_dev(a, "href", "https://svelte.dev/tutorial");
    			add_location(a, file, 11, 1, 275);
    			add_location(button, file, 12, 1, 334);
    			attr_dev(main, "class", "svelte-1tky8bj");
    			add_location(main, file, 9, 0, 223);
    		},
            ...（中略）...
    		m: function mount(target, anchor) {
    			insert_dev(target, main, anchor);
    			append_dev(main, h1);
    			append_dev(h1, t0);
    			append_dev(h1, t1);
    			append_dev(h1, t2);
    			append_dev(h1, t3);
    			append_dev(main, t4);
    			append_dev(main, a);
    			append_dev(main, t6);
    			append_dev(main, button);

    			if (!mounted) {
    				dispose = listen_dev(button, "click", /*changeData*/ ctx[2], false, false, false);
    				mounted = true;
    			}
    		},
    		p: function update(ctx, [dirty]) {
    			if (dirty & /*greetingText*/ 1) set_data_dev(t0, /*greetingText*/ ctx[0]);
    			if (dirty & /*areaName*/ 2) set_data_dev(t2, /*areaName*/ ctx[1]);
    		},
            ...（中略）...
    	};
        ...（中略）...
    }

    function instance($$self, $$props, $$invalidate) {
    	let { $$slots: slots = {}, $$scope } = $$props;
    	validate_slots('App', slots, []);
    	let greetingText = "Hello";
    	let areaName = "World";

    	function changeData() {
    		$$invalidate(0, greetingText = greetingText == "Hello" ? "Good Night" : "Hello");
    		$$invalidate(1, areaName = areaName == "World" ? "Japan" : "World");
    	}

    	const writable_props = [];

    	Object.keys($$props).forEach(key => {
    		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(`<App> was created with unknown prop '${key}'`);
    	});

    	$$self.$capture_state = () => ({ greetingText, areaName, changeData });

    	$$self.$inject_state = $$props => {
    		if ('greetingText' in $$props) $$invalidate(0, greetingText = $$props.greetingText);
    		if ('areaName' in $$props) $$invalidate(1, areaName = $$props.areaName);
    	};

    	if ($$props && "$$inject" in $$props) {
    		$$self.$inject_state($$props.$$inject);
    	}

    	return [greetingText, areaName, changeData];
    }
    …（中略）...
})();
//# sourceMappingURL=bundle.js.map
```

もともと`App.svelte`に記載していた内容がすべてバニラJSに変換されており、`changeData`関数で変更する変数`greetingText`や`areaName`が変更された際のDOMへの追従処理も全て記述されている。

### Svelteはコンパイラである

> Svelteはユーザーインタフェースを構築する先鋭的で新しいアプローチです。ReactやVueのような従来のフレームワークがその作業の大部分を ブラウザ で行うのに対し、Svelteはその作業をアプリをビルドする際の コンパイル時 に行います。

https://svelte.jp/

### コンパイル時に変更可能性を検知しておくってこと？

Vue.jsやReactを使用したアプリだと

1. ブラウザ側でフレームワークのコードを解釈
1. データの変更を元に仮想DOMを変更
1. 仮想DOMと実際のDOMの差分を検知
1. 差分部分のみを実際のDOMに反映

とするところを、Svelteは

1. コンパイル時に変更される可能性がある変数などをすべてバニラJSに落とし込む
1. データの変更があった場合は生成されているJSの該当コードが走るだけ（実際のDOMがそのまま変更される）

となるので、ブラウザ（クライアント）側でのオーバーヘッドが下がるといったところでしょうか？

## おわり

実際にコードを見てみることでSvelteの挙動が少しは分かったと思います。が、まだまだ触り始めたばかりなので間違った解釈をしている箇所がありそうです。もし詳しい方がいらっしゃいましたらご指摘いただけると幸いです。

実際にアプリを開発する際はフレームワークの挙動をすべて把握していなくとも問題なく開発を進めることができるケースが多いですが、この界隈はどんどん新しい技術が出てくるため世の中に置いていかれないようにキャッチアップを続けていきます。

ひとまずはSvelteのチュートリアルをやりきります

https://svelte.jp/tutorial/basics