自身を書き換えるウロボロスHTMLによるローカルWebアプリ

## はじめに
Vanilla Javascript (以下、Javascript)を利用したローカルHTMLファイルにWebアプリケーションとしての役割を持たせることを考える場合、ユーザーデータをどう保持するかが工夫処になるかと思います。
いきなり不可思議な前提が現れたと思う方は少なくないかもしれませんが、従業員によるプログラミング言語や各種ソフトウェアのインストールを一切許可しない企業もあることに御留意いただきたく。

そういった企業において自己・部下を問わずタスク管理を行うことを考えるとExcelがひとまず選択肢に挙がるわけですが、そんな用途のためのソフトウェアではないため使い易いわけもなく。
当記事はそんな縛りのもとでの代替案として発生した、社内の共有フォルダに置かれたHTML(+Javascript)ファイルにWebアプリケーションとしての役割を担わせようという試みに関するものです。

## LocalStorageの利用
ToDoリストのような自己管理など、データを他の人と共有するわけでもなければLocalStorageの利用で大体の問題は解決できます。
しかしLocalStorageは名前の通り、端末ごと（より正確には端末のブラウザごと）に情報を保持する仕組みであり同僚とデータを共有することはできず、プロジェクト管理のようなWebアプリもどきで利用するには片手落ちといったところです。

## 共有フォルダ上のファイルを通じたデータ共有
サーバを立ち上げることなくデータを共有するという前提の下では、必然的に社内の共有フォルダ上にデータを保持することになります。これ自体はExcelファイルを共同管理するのと同じことです。

ところでChromium系のブラウザにはFile System Access APIというAPIがあり、HTMLからローカルファイルを直接扱うことができます。
これを利用することで、データJSONファイルを社内共有フォルダ上に置くことによりWebアプリケーションのような仕組みを構築できます。

## 煩雑だそうです
上述したような仕組みでちょっとしたタスク管理アプリを作成して個人的に利用していたところ、通りがかった同僚に見咎められて「Excelマクロならデータとユーザーインターフェースの両方を1つのファイルにまとめられるけど、それはそれぞれ別にファイルを作らないといけないの？どうにかスッキリさせられないの？」といった趣旨のお言葉を頂きました。
そんなわけで1つのHTMLファイルにデータを保持させる枠組みを考えた、というのがこの記事の主旨です。ここまで前置きでした。

## ウロボロスHTML
端的に言ってしうと、File System Access APIを利用して自分自身（HTML）内で静的に定義された変数を直接書き換えるHTMLを作成しました。セキュリティ上の欠陥は存在しますが、そもそも同僚とローカル上で共有するだけのファイルなのでセキュリティを語るのも無意味かなと。

File System Access APIの制限として、ブラウザを操作しているユーザーが編集対象のファイルを何らかの方法で指定する必要があります。実装においてはHTML上に作成したDrop ZoneにHTMLファイル自身をドラッグアンドドロップすることで指定させました。自身を食らい書き換える、このコンセプトの命名はここに起因します。

## 実装例
ここでは単純なテキストデータをHTML内に保持するサンプルを示します。試していませんが、テキストデータがダブルクオーテーションを含んでいると破綻すると思います。

```HTML:sample.html
<!DOCTYPE html>
<html>
<body>
    <div id="drop_zone" style="border:2px dashed #bbb; margin:2rem;">ここにドロップ</div>
    <textarea id="text_area" style="width:60%; height: 120px; margin:2rem; display:none;"></textarea>
    <button id="save_button" style="display:none;">保存</button>
<script>
    var target = "";
    let fileHandle;
    let fileData = '';
    const dropZone = document.getElementById('drop_zone');
    const text = document.getElementById('text_area');
    const saveButton = document.getElementById('save_button');

    dropZone.addEventListener('dragover', (e) => {
        e.stopPropagation();
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';
    });

    dropZone.addEventListener('drop', async (e) => {
        e.stopPropagation();
        e.preventDefault();
        const item = e.dataTransfer.items[0];
        fileHandle = await item.getAsFileSystemHandle();
        const file = await fileHandle.getFile();
        const reader = new FileReader();
        reader.onload = function(evt) {
        fileData = evt.target.result;
        text.value = fileData.match(/var target = "([^"]*)";/)[1];
        }
        reader.readAsText(file);
        dropZone.style.display = 'none';
        text.style.display = 'block';
        saveButton.style.display = 'block';
        });

    saveButton.addEventListener('click', async () => {
        const replacedText = fileData.replace(/(var target = ")[^"]*(";)/, `$1${text.value}$2`);
        const writable = await fileHandle.createWritable();
        await writable.write(replacedText);
        await writable.close();
    });
</script>
</body>
</html>

```

使用イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2841874/6a4165fd-c458-6488-c7e6-7a148a026ce5.png)


## 実装例の説明
ファイルがドラッグアンドドロップされた際に、そのファイルをテキストファイルとして読み込み'var target = "(任意文字列)"'を探して任意文字列部分をtextareaに表示します。
保存ボタンを押した際には'var target = "(任意文字列)"'の任意文字列部分をtextarea内の文字列で置換して上書き保存します。
これにより、HTMLファイル内にテキストデータを静的かつ動的に保持させています。

実装例で出来ることはただの文通止まりですが、これをタスク管理アプリに応用することでサーバーレスWebアプリが構築できます。
実際に利用しているWebアプリもどきにおいて保持しているデータの実体はJSONオブジェクトですが、JSONオブジェクト変数をstringifyしてからbtoaしてBase64文字列として定義し、読み込む際にはatobしてからparseしています。