socket.io のページが新しくなってた
たまたま数週間前に socket.io のページを開いていて さっきいらないタブを閉じていたら新しくなってるのに気づきました

これまでのページのドキュメントってすごく読みづらくて 毎回 github の方で見ていたので読みやすくなって助かります
と言っても socket.io を使うことがそうそうないのですけどね
ページだけじゃなくて socket.io 自体もメジャーアップデートしたのかなと思いましたがバージョンは前見たときと一緒でした
HTML 文字列から簡単に append する
document.body.append(`<div class="x"><span>1</span>23</div><div>45</div>`)

って書きたい
innerHTML ならできるけど append にはできない
これだとそのまま文字列として追加される

一旦 innerHTML に入れて全部の子要素を追加するの面倒
なので HTML テキストを DocumentFragment 化するもの

const fr = html => Object.assign(document.createElement("template"), {innerHTML: html}).content

これを使って

document.body.append(fr(`<div class="x"><span>1</span>23</div><div>45</div>`))
document.body.append(fr`<div class="x"><span>1</span>23</div><div>45</div>`)

() 書くの面倒ならなくしてテンプレートストリングのタグとしても使える
ただし間に ${} による埋め込みがないときだけ

せっかくだからタグならではの機能でエスケープ機能を入れる

const html = (strs, ...values) => {
const esc = value => Object.assign(document.createElement("div"), { textContent: value }).innerHTML
const fmt = value => value != null
? (value.html ? value.html : value.text ? esc(value.text) : esc(value))
: ""
return fr(strs.reduce((a, e, i) => a + fmt(values[i - 1]) + e))
}

これを使うと

document.body.append(html`<div class="x"><span>1</span>23</div><div>45</div>`)
document.body.append(html`<div class="x"><span>${1}</span>${"<br>"}${{html:"<b>bold</b>"}}</div><div>45</div>`)

「<br>」はエスケープされて文字列として表示されて 「<b>」 は太文字になる
10 万リクエスト
最近は静的ファイルの配信だけじゃなくてサーバサイドプログラムも動くアプリケーションを無料公開できるサービスというのも見かけますね
今回見つけたのは月 10 万リクエストまで無料っていうものです

10 万もあれば小さいものなら余裕かなって思ってましたが

1 日 2000 PV として 1 PV で JavaScript や CSS や画像ファイルなど色々含めて 20 リクエストとしたら
1 日 4 万リクエスト (2000 * 20)
30 日だと 120 万リクエストもあります

思ったより少ないですね
こういうリクエスト数の制限があるなら画像なども含めてバンドルして html ファイル 1 つだけで全部入りっていうのが良いのかも
あと SPA にして毎回サーバの通信が不要なものはクライアント側だけで画面切り替えたり そういう普通はしなくていい工夫が必要になったりしそう
リスナ設定時の bind が面倒なので
this.querySelector(".elem").addEventListener("click", this.onClick.bind(this))

// とか

const tpl = html`<button on-click="${this.onClick.bind(this)}"></button>`

毎回 bind 書くのってすごく面倒
なので自動でするようにしました

class ExampleElement extends HTMLElement {
constructor() {
super()
for (const [key, fn] of Object.entries(this.listeners)) {
this[key] = fn.bind(this)
}
}

get listeners() {
return {
onClick() { console.log(this) }
}
}

example() {
this.querySelector("button").addEventListener("click", this.onClick)
}
}

listeners の getter でリスナ関数を返すようにする
コンストラクタで listeners で取得できるの全てに this を bind してプロパティに設定する
example メソッドのように 使うとき bind が不要になる
コンストラクタの処理は共通のベースクラスにでも書いておくといい感じ

コンストラクタで何もしない版

class ExampleElement extends HTMLElement {
get listeners() {
return {
onClick: () => { console.log(this) }
}
}

example() {
this.querySelector("button").addEventListener("click", this.listeners.onClick)
}
}

listeners を挟むけどわかりやすいといえばわかりやすい
アロー関数にしたらなにもしなくても this がクラスのインスンタンスに固定される

this.listeners = this.listeners

しておくほうがリスナ関数が同じ値になっていいかも
SQL の IN / ANY / ALL
毎回のようによくわからなくなるのでまとめ

IN
IN で指定したどれかに行の値が一致すると true
NOT はどれにも一致しないと true
単純に IN にマッチしない行が true になる
「x NOT IN y」 と 「NOT (x IN y)」 は一緒

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c IN (1, 2)

-- 1, 2

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c NOT IN (1, 2)

-- 3, 4, 5

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE NOT (c IN (1, 2))

-- 3, 4, 5

ANY
通常は IN と一緒
postgresql の場合は配列を指定するので $1 などのパラメータ化できるのが特徴
<> を使うと配列の要素のどれにもマッチしない行が true になる
「x <> ANY(y)」 と 「NOT (x = ANY(y))」 は別
「NOT (x = ANY(y))」 を使うと 「x NOT IN y」 と同じになる

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c = ANY(ARRAY[1, 2])

-- 1, 2

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c <> ANY(ARRAY[1, 2])

-- 1, 2, 3, 4, 5

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c <> ANY(ARRAY[1, 1])

-- 2, 3, 4, 5

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE NOT (c = ANY(ARRAY[1, 2]))

-- 3, 4, 5

SOME
ANY と全く同じ

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c = SOME(ARRAY[1, 2])

-- 1, 2

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c <> SOME(ARRAY[1, 2])

-- 1, 2, 3, 4, 5

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c <> SOME(ARRAY[1, 1])

-- 2, 3, 4, 5

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE NOT (c = SOME(ARRAY[1, 2]))

-- 3, 4, 5

ALL
配列の全てに一致する行が true になる
<> するとひとつでも一致しないのがあると true になる
「x <> ALL(y)」 と 「NOT (x = ALL(y))」 は別
「x <> ALL(y)」 と 「NOT (x = ANY(y))」 が同じになる

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c = ALL(ARRAY[1, 2])

-- none

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c = ALL(ARRAY[1, 1])

-- 1

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE c <> ALL(ARRAY[1, 2])

-- 3, 4, 5

SELECT c
FROM unnest(ARRAY[1,2,3,4,5]) as c
WHERE NOT (c = ALL(ARRAY[1, 2]))

-- 1, 2, 3, 4, 5
JavaScript で rendering の時間を測る
performance タブ見るの面倒
JavaScript で測りたい

console.time("dom")
div.prepend(document.createElement("an-element"))
console.timeEnd("dom")

これだと DOM 更新のみ
この後に画面更新があるので 重いページでは ↑ は数 ms なのに画面に反映されるまで数秒固まったりする

非同期処理をすると画面更新後に行われる (ことが多い)

console.time("rendering")
setTimeout(() => console.timeEnd("rendering"), 0)

ときどき画面更新より前に処理されて ↑ は数 ms となって画面更新は 1 秒後だったりする
結果見たらわかるけど あんまり重くないと判断しづらい上に確実じゃないのはいまいち

Promise.resolve による非同期はたぶん確実に画面更新前に実行される

console.time("rendering")
Promise.resolve().then(() => console.timeEnd("rendering"))

JavaScript で rendering の後にわかる情報を取得しようとするとそこでブロックしてくれる

console.time("dom")
div.prepend(document.createElement("an-element"))
console.timeEnd("dom")
console.time("rendering")
div.getClientRects()
console.timeEnd("rendering")

// dom: 0.0810546875ms
// rendering: 1819.3779296875ms
HTML 文字列から DocumentFragment を作る
DocumentFragment に innerHTML はない

const df = document.createDocumentFragment()
df.innerHTML = `<p>text</p><div>div</div>`
console.log(df.innerHTML)
// "<p>text</p><div>div</div>"

console.log(df)
// #document-fragment

ただのプロパティだから入れても子要素が作られない

既存の DocumentFragment に入れたいなら div などに一旦入れて append

const div = document.createElement("div")
const df = document.createDocumentFragment()
div.innerHTML = `<p>text</p><div>div</div>`
df.append(...div.childNodes)
console.log(df)
// #document-fragment
// <p>text</p>
// <div>div</div>

新規に作るなら template タグを使う

const template = document.createElement("template")
template.innerHTML = `<p>text</p><div>div</div>`
console.log(template.content)
// #document-fragment
// <p>text</p>
// <div>div</div>
localName
elem.tagName だと DIV みたいな大文字になる
小文字化面倒だと思ってたら localName だと小文字でとれる

document.createElement("div").localName
// "div"

document.createElement("div").tagName
// "DIV"

document.createElement("div").nodeName
// "DIV"

「For example, in the qualified name "ecomm:partners", "partners" is the local name and "ecomm" is the prefix」
https://developer.mozilla.org/en-US/docs/Web/API/Element/localName

って書いてるのに

document.createElement("ecomm:partners").localName
// "ecomm:partners"

コロン含めて全部取れる