Svelteが生成したコードってどうなってるの？

まずは```$ npx degit sveltejs/template 〜```で作成されたソースが、ビルド後にどういったコードとなっているかを確認してみた。

```html:App.svelte
<script lang="ts">
	export let name: string;
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

<style>
    /* 割愛 */
</style>
```

```javascript:main.ts
import App from './App.svelte';

const app = new App({
	target: document.body,
	props: {
		name: 'world'
	}
});

export default app;
```

なおjavascript版とtypescript版で生成されるコードは同じでした。


## 大まかな構成

* ユーティリティ関数(←勝手に名付けてます)
    * is_function(関数か否かを判別)やelement(DOMに要素を追加する)などの関数群。
* SvelteComponentクラス
    * コンポーネントの基底クラス。
* コンポーネントクラス
    * App.svelteファイルの実装が反映されたクラス。
    * SvelteComponentを継承。

* コンポーネントの生成
    * main.jsに書かれたコンポーネントの生成コード。
* デバッグ用コード
    * ```npm run dev```で実行した時(コンパイルオプションがdev=true) の時にだけ生成される。
    * Live Reload用の関数とSvelteComponentDevクラスが追加される。
    * SvelteComponentDevクラスは継承関係の間に挿し込まれ、SvelteComponent -> SvelteComponentDev -> Appという継承になる。
    * 今回の調査では以降割愛。

## 処理の流れ
* このコードは、ユーティリティ関数を展開して、直接DOM関数を呼び出す形で単純化しています。

1. Appのインスタンス生成。
main.tsにあるnew App()によりAppのインスタンス作成され、コンストラクタ関数が呼び出される。


1. DOMノードの生成。
App.svelteファイルのhtml部分が分解され、各ノードを生成するロジックに変換されている。
ここではまだdocumentにノードに追加していない。

    ```javascript
    c() {
        main = document.createElement("main");
        h1 = document.createElement("h1");
        t0 = document.createTextNode("Hello ");
        t1 = document.createTextNode(/*name*/ ctx[0]);
        t2 = document.createTextNode("!");
        t3 = document.createTextNode(' ');
        p = document.createElement("p");
        p.innerHTML = `Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.`;
        h1.setAttribute("class", "svelte-1tky8bj");
        main.setAttribute("class", "svelte-1tky8bj");
    }
    ```
    * ctx[0]にはコメントにもあるように、Appのコンストラクタに渡したnameの値が格納されている。
    * この時、CSSをこのDOMツリーにだけ適用させるためのclassとしてsvelte-1tky8bjを追加している。
1. DOMノードのマウント。
生成したノードをツリー状にdocumentオブジェクトに追加する。

    ```javascript
    m(target, anchor) {
        target.insertBefore(main, anchor || null);
        main.appendChild(h1);
        h1.appendChild(t0);
        h1.appendChild(t1);
        h1.appendChild(t2);
        main.appendChild(t3);
        main.appendChild(p);
    }
    ```
    * targetは、Appのコンストラクタのtargetで指定したに渡した要素で、今回はbodyになる。
    * anchorも、Appのコンストラクタで指定することができる。今回は未指定なのでinsertBeforeにはnullが渡される。


# バインディングさせるとどうなる

次に、テキストボックスにnameをバインディングさせて、変更可能にしてみる。

```html
<script lang="ts">
	export let name: string;
</script>

<main>
	<h1>Hello {name}!</h1>
	<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
	<input bind:value={name}>
</main>
```

## 主な変わった点

* ユーティリティ関数に、DOMノードのイベントリスナー登録関数などが増えている。
    * Tree Shakingで消えていたのが残るようになったのではなく、使っているSvelteの機能が増えたからユーティリティ関数が増えた感じ。

* DOMノードの生成に、input要素の生成処理が追加。

    ```javascript
    p.innerHTML = `Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.`;
    t7 = document.createTextNode(' ');;             // ← 追加
    input = document.createElement("input");        // ← 追加
    h1.setAttribute("class", "svelte-1tky8bj");
    main.setAttribute("class", "svelte-1tky8bj");
    ```

* DOMノードのマウントに、input要素にイベントハンドラを登録。

    ```javascript
    main.appendChild(p);
    // ↓ 追加
    main.appendChild(t7);
    main.appendChild(input);
    input.value = ctx[0] == null ? '' : ctx[0];

    if (!mounted) {
        input.addEventListener("input", /*input_input_handler*/ ctx[1]);
        dispose = return () => input.removeEventListener("input", ctx[1]);
        mounted = true;
    }
    ```
    * input要素をマウント後に、nameの初期値をセット。
    * mountedは初期値undefinedで、イベントハンドラが登録が1回しか行われないようにガードしている。
    * ctx[1]にinput_input_handler関数がイベントハンドラとして格納されている。
    * disposeに、コンポーネント破棄時にイベントハンドラの登録を解除を行う関数を登録している。

* DOMノードの更新、input要素の更新処理が追加。
    * 最初のHello world!だとnameプロパティが更新されることがく、呼び出されもしないので割愛したが、DOMノードの更新処理対象にinput要素が追加されている。

        ```javascript
        p(ctx, [dirty]) {
            if (dirty & /*name*/ 1) t1.data = /*name*/ ctx[0];

            // ↓ 追加
            if (dirty & /*name*/ 1 && input.value !== /*name*/ ctx[0]) {
                input.value = /*name*/ ctx[0] == null ? '' : /*name*/ ctx[0];
            }
            // ↑ 追加
        }
        ```
        * t1は、DOMノードの生成時にctx[0]の値を持ったTextノード。

        * dirtyには更新有無のビットフィールドで、各ビットはctxのインデックスと対応している。
        nameはインデックス0なので、dirtyの0ビット目に対応している。

## テキストボックスの値を変更した時の流れ

1. input_input_handler関数が呼び出される。
    * 入力された値が元の値と違うなら以下を行う。
        * コンポーネントの更新フラグをセットする。
            * dirty_components配列に、自身のコンポーネントを追加する。
        * 更新を予約する。
            * resolved_promiseというプロミスで、flush関数が呼び出されるようにする。
            * resolved_promiseは名前の通り```const resolved_promise = Promise.resolve();```なので、```resolved_promise.then(flush)```とすることで、イベントハンドラの処理が完了してからflush関数が呼び出される。
1. flush関数が呼び出される。                
    * dirty_components配列に格納されているコンポーネントのDOMノードの更新を呼び出す。
        * t1ノードおよびinput要素の値が更新され、画面に値が反映される。

# 気になったこと

上記のコードでは具体的な説明をしやすくするため、ユーティリティ関数を展開した形で行っているが、Textノードの値を更新する関数が実際には以下の通りとなっている。

    ```javascript
    function set_data(text, data) {
        data = '' + data;
        if (text.wholeText !== data)
            text.data = data;
    }
    ```

wholeTextは前後の連結するテキストを返すので、今回だと"Hello " + 旧dataとなるため、wholeTextとdataの比較が必ず成立してしまい、意味がないように見える。

ただ、その前段の関数でプロパティの値だけで前後の変更有無を確認しているので、set_dataが呼出されるのは```text.wholeText !== data```が成立しないといけない状況ではあるのだが、、、
今は、疑問点を記録しておくだけに留めて起きます。

## 雑記
* 他のフレームワークでも似たような実装なのかもしれませんが、Svelteだと行数が少ないのでDOM APIでノード操作しているところが単純で判りやすいです。
* ここではまだSvelteの実装を全て理解したとは言えないため表現を濁していますが、コンポーネントクラスには$$というオブジェクトでfragmentやctxなどなど重要な情報を管理しているみたい。