(縛りプレイ)jQuery/React/Vue.js/Svelte/StimulusのTodoアプリをつくる

※この記事は[Misoca+弥生 AdventCalendar2019](https://qiita.com/advent-calendar/2019/misoca-yayoi)の18日目のエントリーです。

そうだ！フロントエンドを勉強しよう！
=====

昨今のフロントエンドは難しいですよね。私も何もわかりません。

特にフレームワーク・ライブラリが多すぎます。

jQueryが良いって聞いてたのに、ReactやらVue.js使って当然みたいな雰囲気ですし、最近だと[Svelte](https://svelte.dev/)やら、Basecampが作った[Stimulus](https://stimulusjs.org/)というのも登場してるらしいですよ。

とはいえ、嘆いていても始まりません。

まずは簡単なアプリをつくってそれぞれの実装を勉強してみましょう！！

Todoアプリをつくってみよう！
====

フロントエンドのFWを学びたければ、息をするより先にTodoアプリを作れって誰かが言ってました。

Todoアプリは動的な要素の追加や削除、さらにコレクションの描画が必要となるので入門にはぴったりですね！！

よーし、がんばるぞ〜！


できたもの
=====

そういうわけで、jQuery/React/Vue.js/Svelte/Stimulusそれぞれを利用してTodoアプリを作ってみました！！

まずは、jQueryで動くTodoアプリです！！

→ https://jumble-todo.netlify.com/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/e7963946-c76d-6517-fdf0-378d33a79b05.png)


しっかりタスクの追加もできますね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/4d51779e-e7ca-70d7-7724-a0bd8f27d5f3.png)

**いかがでしたか？**

このようにjQueryを駆使することで簡単にTodoアプリを作ることができました。
jQueryはお手軽に使えて便利ですね！

<br />

さて、私は

> jQuery/React/Vue.js/Svelte/Stimulusそれぞれを利用してTodoアプリを作ってみました

とお伝えしていますので、残りのTodoアプリもご紹介したいと思います。

<br />

...おや？

なんだか、このjQueryのTodoアプリのページ、下の方にまだ何かスクロールできますね...


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/1de5d4e4-8bb2-fc37-eb2f-be0ee0a84988.png)

（不穏な音）

おもむろにこのコードをコピーして、ChromeDevToolsのコンソールで実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/20ca30d1-b120-6e8c-5945-f013c4ba4835.png)


すると...


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/bdbb006d-bdcf-f539-25c9-f65a8c78a402.png)

**Vue.jsのTodoアプリ**になりました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/fff122bd-5d8f-bf4c-074c-16982c397cea.png)

Vue DevToolsでも、ちゃんとVue.jsで動作していることが確認できます。

そして、この画面も全体を表示すると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/1d794a74-d381-7953-b702-48b6e0413a72.png)


はい。もうここまで来たら**この後の流れはあなたの予想している通り**です。

このコードをコピーして同じようにDevToolsのコンソールで実行してみると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/11aa666e-ff6f-555f-c1cf-2bac9b8fa7e6.png)

**ReactのTodoアプリ**になりました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/8a6fe1ba-d2cb-c9d9-8390-f312c0452ed6.png)

React Developer ToolsのComponentsタブでも確認できますね。

そしてこのページのコードも実行してみると..

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/e708e382-f93e-78e6-d612-4959f03af7fb.png)

**SvelteのTodoアプリ**になりました。

（※DevToolsとかを動かせてないので、Svelteっぽさが表現できてないのはスイマセン）

さらにコードを実行してみます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/7e218442-0b87-a4e6-1611-49800a2fd5b6.png)


**StimulusjsのTodoアプリ**になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/57da4952-3ce0-e482-4f85-529237ca4ff2.png)

data-actionなどの属性で動作してるのがそれっぽいですね。


そして最後に、StimulusjsのTodoアプリのページのコードを実行すると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/846ecba0-4f66-2c2f-1d07-00738fdfd723.png)

**jQueryのTodoアプリ**に戻ってきます。

このページのコードを実行すると再度Vue.jsのTodoアプリとなり、以降はループします。

また、**最後に実行したコードは、ページ自体を初期表示した際に実行されるコードと完全に一致**するようになっています。

<br />

**いかがでしたか？**

> jQuery/React/Vue.js/Svelte/Stimulusそれぞれを利用してTodoアプリを（**縛りとして、単一の `index.html` のみで完結し、かつコードが循環することを条件に**）作ってみた

というエントリでした！

いやー、Todoアプリは入門にぴったりですね。勉強になりました。


コードについて
=====

はい、すいませんここからが本編です。

半分ネタみたいな内容にお付き合いいただきありがとうございました。

実際の動作ページやコードは以下で確認できます。

- https://jumble-todo.netlify.com/
- [mugi-uno/jumble-todo: jQuery/Vue/React/Stimulus Todo App on single source file](https://github.com/mugi-uno/jumble-todo)

以前RubyKaigiで[TRICK](https://rubykaigi.org/2018/presentations/tric.html)という超絶技巧プログラミングを見て感動して、同じレベルのものは無理でも、何か自分でも作ってみたいな〜と思ってやってみた、というのがこのエントリの本質です。

今回作成したのは、いわゆるQuineと呼ばれるものの一種です。

> クワイン（英: Quine）は、コンピュータプログラムの一種で、自身のソースコードと完全に同じ文字列を出力するプログラムである。娯楽として、プログラマが任意のプログラミング言語での最短クワインを書くことがある。プログラムを出力するプログラムだと見れば、クワインのプログラミングはメタプログラミングの一種である。  
[クワイン (プログラミング) - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AF%E3%82%A4%E3%83%B3_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0))


`自身のソースコードと完全に同じ文字列を出力する` という記述があったので、一応コードを実行するたびにHTMLのみだけではなく、console.logでもソースコードを出力しています。

動作の仕組み
--------

実際に動いているコードは若干のMinifyをしています。
圧縮前のコードはこちらです↓
https://github.com/mugi-uno/jumble-todo/blob/master/index.full.html

長々書いてありますが、結局のところ次のような構造です。

```js
const num = 0; // 実行するFW

const html = ["jQuery用のHTML", "Vue用のHTML", ...];
const scripts = ["jQuery用のJS", "Vue用のJS", ...];

const nextScriptTemplate = "次に実行するJSのテンプレート";

// 〜このあたりの処理で「自分自身と同じ構造のコード」をnextScriptTemplateをもとに構築〜

const script = scripts[num].replace("_NEXTHTML_", nextHtml);

// 完成したscriptを実行
window.eval(script);
```

「自分自身と同じ構造のスクリプトを文字列上で構築して、それをevalで実行する」を繰り返しているのみで、実はさほど難しいことはやっていません。
なお、`html`と`scripts`の配列の内容を順に実行する仕組みで、自由に増やしたり減らしたりできます。（AngularやElmのコードを実装してくれてもいいんですよ！）

実はどこでも動く
------

見た目をある程度整えるためにデモページを用意しましたが、スクリプトはデモページじゃなくても動きます。（すでに読み込まれているスクリプトやheadタグの内容によっては動作しない可能性もあります）

では、実際に試してみましょう。


**（※本エントリ内では紹介のために私自身が管理している別ページを利用しています。もし同様に試す場合は迷惑のかからないページで自己責任にて実行してください。）**


たとえば、私はToyama.rbというコミュニティを主催しており、公式ページは次のURLです。

→ https://toyamarb.github.io/

こちらのページでDevToolsを開いてコードを実行してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/12afb84d-954a-8168-2921-940e4e1f9383.png)

ソリャー！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55375/a606ba73-f4d7-dda0-3737-c06fa75d3437.png)

はい、動きましたね。bodyタグのみを書き換えるので、スタイルシートはもとのページのhead内に定義されているものを引き継いでいます。



ハマったところ
----

せっかくなので今回のコードを作るにあたってハマった点をいくつか紹介します。


### React動かない問題

「別にJSX使わなくてもReact使ったと言っても良いのでは..?」と一瞬思いましたが、負けた気がしたのでテンプレート部は絶対にJSXで書くことにしました。

幸い、standalone版のbabelが存在するので、そちらを利用すればいけるかな〜と思ってました。
https://reactjs.org/docs/add-react-to-a-website.html#quickly-try-jsx

が、今回のコードに適用した場合、サンプルのままでは動作しませんでした。

結果として、

「動的に挿入するstandalone版のbabelを使った場合に、同じく動的に挿入されるscript(type=text/jsx)タグの内容が動きません！！」

という、**恐らくこの世で誰も困ってないし解決しても二度と役に立たない**問題に激突しました。同じ質問されても「いますぐその方法をやめてwebpackでビルドしてください」と答えると思います。

結果的には、自力で `Babel.transformScriptTags()` を呼ぶだけで良かったのですが、やってることが日常と違いすぎて、辿り着くまでに結構ハマりました。

### エスケープ地獄

「スクリプトを文字列化してevalする」という前提があるので、何らかのエスケープはほぼ必須です。

最悪のケースでは、**「evalで実行可能な文字列から出力されるscriptタグ内のJSXの中に表示する文字列の中のevalで実行可能な文字列の中の `\`」** のエスケープが必要でした。自分でも何を言ってるのかわかりません。

さらに、実行対象のFWによって必要なエスケープ回数も異なるため、頭がクラッシュします。

最終的には、スクリプトを一旦Base64化した上で、eval実行されるスクリプト内でそれをDecodeする方法を取ることで回避しました。

### 妥協した点

完璧に作れたわけではなく、いくつか妥協しています。

- Vue.jsのSFCをブラウザ上でコンパイルして実行したかった
- Svelteは諦めてビルドしたものを直接埋め込んでる
- コードがでかい

Quine...奥が深いぜ...!


まとめ
====


というわけで、**JSを駆使してQuineを書いてみた**、というエントリでした。

仕事でコードを書くのも楽しいですが、たまにはこういうトリッキーなことをしてみるのも新鮮で面白いので、興味が湧いた方はぜひ遊んでみてください！

[Misoca+弥生 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/misoca-yayoi)の次回19日目は、@issiによる業務効率化の話とのことです。

私のエントリからガラリとかわり、現実で役に立ちそうな内容で楽しみですね！