@ryoppippi

雑にシングルファイルのWebアプリを作る時に使ってるもの

1 Jul 2024 ・ 7 min read


TL;DR

Deno の​力を​借りつつ、esm.shで​依存関係を​解決。

https://deno.com/ https://esm.sh

本編

まずHTMLファイルと​JSファイルを​作る。

touch index.html index.js

初っ端から​シングルファイルじゃなくて​タイトル詐欺で​草と​思うかもしれないが、​後ほど​言及する。

index.htmlには​雛形と​して​以下を​書いておく。 ベースは​ emmet の​ html:5 で​出てくる​ものを​使っている。 スニペットに​登録しておいても​いい。

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<script type="module" src="./index.js"></script>
		<title>foo</title>
	</head>

	<body>
		<!-- なんでも良い -->
		<input id="input" type="text" />
		<div id="info"></div>
		<img id="image" src="" alt="" />
	</body>
</html>

で、​あとは​好きなように​ index.js を​書いていく。 ミソなのは

  • JavaScriptで​そのまま​書く
  • 型が​必要なら​JSDocを​書く
  • 依存関係は​ esm.sh からimportする

近頃polyfill.ioの​騒動で​外部​CDNに​依存するのは​あまり​よくないと​言われているが、​使い捨ての​小物なので​問題ない。

esm.shは​本当に​便利で、npmjsrに​ある​ライブラリを​サーバー上でesm形式に​変換してくれるので​そのまま​ブラウザでimportできる。 もちろんちゃんとした​アプリを​作る​ときは​バンドルして​tree-shakingとか​するべきだが、​ちょっとした​ものを​作る​ときには​便利。 ブラウザで​動く​ライブラリは​大体動く。​動かなくて​困った​ことはない。

編集する​時は、denolspを​使うと、esm.shからimportした​moduleに​ついては​型が​補完される。 それでも​足りない​場合はJSDocを​書く​(そして​JSDocは​ほとんどの​場合Copilot君が​補完してくれる)。 これで​体験を​落と​さず​JSで​コードを​書く​ことができる。

import * as IM from "https://esm.sh/image-meta@0.2.0";
import { toUint8Array } from "https://esm.sh/uint8array-extras@1.2.0";
import { effect, ref } from "https://esm.sh/@vue/reactivity@3.4.31";

const $ = document.querySelector.bind(document);

/** @type {HTMLInputElement} */
const input = $("#input");

/** @type {HTMLImageElement} */
const infoDiv = $("#info");

/** @type {HTMLImageElement} */
const img = $("#image");

/** @type {{meta: IM.ImageMeta, url: string}|null} */
const info = ref(null);

/** infoが変更されたらDOMを更新 */
effect(() => {
  if (info.value == null) return;
  const { meta, url } = info.value;
  infoDiv.innerHTML = JSON.stringify(meta, null, 2);
  img.src = url;
});

/** inputが変更されたら画像を読み込んでメタデータを取得 */
input.addEventListener(
  "input",
  (e) => {
    const url = e.target.value;
    if(!URL.canParse(url)) return;

    fetch(url)
      .then((res) => res.blob())
      .then((blob) => {
        const meta = IM.imageMeta(toUint8Array(await res.arrayBuffer()));
        info.value = { meta, url };
      });
  },
);

formatも​lintも​型チェックも​それぞれ deno lintdeno fmtdeno check で​できるので、npm で​ eslint や​ prettier 、​ biome とかを​入れる​必要が​ない。 node_modulesの​管理とか​やりたくもない。 コンパイルも​必要ない。​本当に​楽。

この​記事では​ JavaScript は​ index.js に​切り出しているが、​ HTML に​直接書いても​いい。 その​場合はPartEdit とかを​使うと​一部​分を​切り出して​JavaScript ファイルと​して​編集できるので​便利。

https://github.com/thinca/vim-partedit

ところで上のコードに vue がいるけどなんで??

vue の​ reactivity は​ Svelte の​ものと​違い、​ランタイムベースの​実装に​なっている。 つまり​ビルドステップが​必要ない。 また​コアの​部分は​標準的な​JavaScriptで​実装されているので​ vue に​依存していない。 つまり、​ただの​JavaScriptライブラリと​して​使う​ことができる。 https://x.com/youyuxi/status/1804005076853219445

FrontEndに​限らず、​実は​Server Sideや​CLIでも​使える。

https://ja.vuejs.org/guide/extras/reactivity-in-depth

どこで​使うのか

以下のような​状況を​想定している

  • 本当に​最小限の​機能だけを​持つツールが​求められてる​時
  • ランタイムを​インストールするとか、​複数ファイルを​渡すとかが​面倒である​場合
  • シェルスクリプトに​慣れてない​人向けに、​ブラウザで​動く​ツールを​作りたい​場合​(エンジニア相手だったら​シェルスクリプトでも​なんでも​いいと​思う​)
  • メールや​Slackで​さっと​渡せるような、​ぺらっとした​1枚の​HTMLで​済ませる​ことが​望ましいと​されている​場合
  • ビルドとか​テストとか​必要ない​(し、​やりたくない)​場合

そんな​状況あるのだろうかと​いう​話が​あるが、​まあまあある。 そんな​時に​いちいちツールとか​入れてビルドして、​ビルド結果が​複数ファイルに​なってて、​それを​先方に​渡して 「node index.js で​やってください​! あれNode入って​ない、​ブラウザだけしか​使えない?」 「シェルスクリプト動かしてください、​あれ、​terminal​使えない?」 とか​なる​ことが​ちょく​ちょく​発生する。 そんな​時に​便利。

開発するのも​簡単で​便利。 ブラウザで​完結するのも楽。

これより​も​う​少し​複雑な​設計が​必要なら、Vite + React or Svelte で​素直に​バンドルしてしまうのが​筋が​いいと​思う。 node_modules の​管理は​面倒だが。

関連記事

https://zenn.dev/razokulover/articles/7653ef0336db77

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi