関数が受け取る引数の数で動作変えるのやめてほしい
一部のライブラリでは コールバック関数として渡した関数が受け取る引数の数で動作が変わります
こういうの

fn(() => {})
fn((a) => {})
fn((a, b) => {})

fn 側ではこういう感じで引数の数を見ています

const fn = (callback) => {
switch (callback.length) {
case 0: {
console.log("0")
return callback()
}
case 1: {
console.log("1")
return callback(1)
}
case 2: {
console.log("2")
return callback(1, 2)
}
}
}

これすごく分かりづらく感じるのでやめてほしいです
関数の引数の数ってあまりあてにならないです

console.log(((a, b = 1, c) => {}).length)
// 1

console.log(((...args) => {}).length)
// 0

デフォルト引数が設定された最初の引数より前の引数の数しかカウントされません
可変長引数は 0 になります
関数をラップして引数をそのまま渡して追加処理をするようなケースは可変長引数で受け取ることが多いのでそこで問題がおきます

fn((a, b) => {})
// 1

// ↑を↓にすると

const wrap = org => (...args) => {
// beforeSomething()
const result = org(...args)
// afterSomething()
return result
}

fn(wrap((a, b) => {}))
// 0

使われどころはコールバック関数を受け取るかどうかで コールバックと Promise のどちらを使うか分岐するというのが多い気がします
コールバック関数を受け取らないなら Promise を返す関数で そうでないならコールバック関数を呼び出すことで終了を呼び出し元に伝えるので 両方に対応するならこういう方法になります

const fn = async (callback) => {
switch (callback.length) {
case 0: {
console.log("0")
await callback()
break
}
case 1: {
console.log("1")
await new Promise(resolve => callback(resolve))
break
}
}
// after callback process
}

引数の数を見ないでとりあえず コールバックの関数を渡しておいて 返り値の Promise または渡した関数が呼び出されたかで判断でしてくれるといいのですが これの場合も使う側が async 関数にしてコールバック関数を使おうとするとうまく動かなかったりするのですよね

const fn = async (callback) => {
const { promise, resolve } = Promise.withResolvers()
const ret = callback(resolve)
const promises = (ret instanceof Promise) ? [ret, promise] : [promise]
await Promise.any(promises)

// after callback process
}

fn(async (next) => {
await something1()
something2(next)
})

fn に渡すのが async 関数なので something2 を待たず resolve されてしまって ここで fn は next の呼び出しを待たずに終わったとみなしてしまいます
無理にまとめず別の関数に分けたほうがいいと思うのですけどね

他には なにかの機能を引数経由で提供していて 引数として受け取らないならその機能は使われないので準備するのをスキップするとかでしょうか

const fn = (callback) => {
switch (callback.length) {
case 0: {
console.log("0")
return callback()
}
case 1: {
console.log("1")
const util = new Utility()
return callback(util)
}
}
}

引数を受け取らないということは util を使うことはないので new Utility() をスキップできます

JavaScript では引数の数があってる必要はないので 関数の引数の数は気にせず扱ってほしいですね
git なしで yarn berry の init をエラーなく実行する
特に役立たない話

yarn init で berry の初期化をするとき git でリポジトリが自動で作られます
git が入っていないとエラーが起きます

[root@60303402bf2f foo]# yarn init -2
➤ YN0000: You don't seem to have Corepack enabled; we'll have to rely on yarnPath instead
➤ YN0000: Downloading https://repo.yarnpkg.com/4.1.1/packages/yarnpkg-cli/bin/yarn.js
➤ YN0000: Saving the new release in .yarn/releases/yarn-4.1.1.cjs
➤ YN0000: Done with warnings in 0s 140ms
➤ YN0000: · Yarn 4.1.1
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 127ms
Internal Error: Process git failed to spawn
at ChildProcess.<anonymous> (/foo/.yarn/releases/yarn-4.1.1.cjs:149:5568)
at ChildProcess.emit (node:events:518:28)
at ChildProcess._handle.onexit (node:internal/child_process:292:12)
at onErrorNT (node:internal/child_process:484:16)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
error Command failed.
Exit code: 1
Command: /usr/bin/node
Arguments: /usr/local/bin/yarn init -2
Directory: /foo
Output:

info Visit https://yarnpkg.com/en/docs/cli/init for documentation about this command.

という感じ

git が入ってない環境で使うこともありますし git の初期化はスキップしたいです
ですが オプションに git の初期化をスキップする設定が見当たらないです

しかし .git フォルダがすでにあると初期化処理はスキップされるようになっています
https://github.com/yarnpkg/berry/blob/%40yarnpkg/cli/4.1.1/packages/plugin-init/sources/commands/init.ts#L252

つまり

mkdir .git
yarn init -2
rmdir .git

とすればエラーはなくなります

ただ上のリンクのソースコードを見ても分かる通り git の初期化処理は最後の処理です
エラーで途中終了しても init の処理は終わってるのでエラーを消しても特に何も変わりません

ログにエラーを出したくないということがあれば使えます
エラーが無い場合は Done の行までの出力になります
pino で 〇 を含むプロパティを伏せられない
pino の redact を見てると 「〇」 が使われてた
https://github.com/davidmarkclements/fast-redact/blob/v3.4.0/lib/validator.js

じゃあ〇を使うとエラーになる?と 思い付きで試してみました

pino と redact の使い方としてはこういうの

> require("pino")({ redact: ["key"] }).info({ key: "foo" })
{"level":30,"time":1710331939803,"pid":54,"hostname":"67cfbc893e4a","key":"[Redacted]"}
> require("pino")({ redact: ["あ"] }).info({ あ: "foo" })
{"level":30,"time":1710331978251,"pid":54,"hostname":"67cfbc893e4a","あ":"[Redacted]"}

ログするオブジェクトのパスを複数指定できて 指定したプロパティが伏せられます
〇 を入れてみると

> require("pino")({ redact: [`["〇"]`] })
Uncaught Error: pino – redact paths array contains an invalid path (["〇"])
at /mnt/x8uc1/node_modules/fast-redact/lib/validator.js:29:15
at Array.forEach (<anonymous>)
at validate (/mnt/x8uc1/node_modules/fast-redact/lib/validator.js:12:11)
at handle (/mnt/x8uc1/node_modules/pino/lib/redaction.js:107:5)
at redaction (/mnt/x8uc1/node_modules/pino/lib/redaction.js:16:29)
at pino (/mnt/x8uc1/node_modules/pino/pino.js:129:33)

確かにエラーになってます
ログをしなくてもインスタンスの作成時点でエラーです

少し特殊な

[`["〇"]`]

になってるのは記号の対処のためで 内部でプロパティをどう処理してるかというと

o${expr}

でコードを作ってそれを実行しています
eval みたいなものです

expr が .prop みたいになってます
foo.bar は有効ですが foo.1 や foo.! は無効です
JavaScript として有効なアクセス方法になるように [1] や ["!"] みたいにしないといけないです
そのため少し特殊な書き方になってます

記号などでも ["!"] の書き方にすると動作します

> require("pino")({ redact: ["!"] }).info({ "!": "a" })
Uncaught Error: pino – redact paths array contains an invalid path (!)
(略)

> require("pino")({ redact: [`["!"]`] }).info({ "!": "a" })
{"level":30,"time":1710352889569,"pid":350,"hostname":"8f9aa7f09ea2","!":"[Redacted]"}

〇 の記号はいくつかあって fast-redact に使われてるのは一番上のものです

〇 U+3007
◯ U+25EF
○ U+25CB

他の 〇 ならエラーになりません

> const fn = k => {
... console.log(k, k.charCodeAt().toString(16))
... require("pino")({ redact: [`["${k}"]`] }).info({ [k]: "a" })
... }

> fn("〇")
〇 3007
Uncaught Error: pino – redact paths array contains an invalid path (["〇"])
(略)

> fn("◯")
◯ 25ef
{"level":30,"time":1710416813795,"pid":3966,"hostname":"67cfbc893e4a","◯":"[Redacted]"}

> fn("○")
○ 25cb
{"level":30,"time":1710416820183,"pid":3966,"hostname":"67cfbc893e4a","○":"[Redacted]"}
fs で File URL をサポートしてほしい
Node.js を ESM にしてから不便に感じてる部分はやっぱりファイルパスの解決部分です

CJS のときはこんな感じで書けた部分ですが

[/tmp/a.js]
const path = require("path")

const file_path = path.join(__dirname, "./file.txt")
console.log(file_path)
// /tmp/file.txt

この長さになります

[/tmp/b.js]
import { fileURLToPath } from "node:url"
import path from "node:path"

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const file_path = path.join(__dirname, "./file.txt")
console.log(file_path)
// /tmp/file.txt

Node.js 20 なら import.meta に resolve があって __dirname を作らなくてもそのファイルからの相対パスでフルパスを取得できます
でも file:// から始まる URL 形式なので最後に fileURLToPath を通してローカルのパス形式にしないといけないです

[/tmp/c.js]
import { fileURLToPath } from "node:url"

const file_url = import.meta.resolve("./file.txt")
console.log(file_url)
// file:///tmp/file.txt

const file_path = fileURLToPath(file_url)
console.log(file_path)
// /tmp/file.txt

Node.js 21 なら import.meta.dirname があるので CJS と同じようなことができます
🔗 Node.js 21.2 で ESM に CJS の __dirname と __filename 相当の機能が追加された

[/tmp/d.js]
import path from "node:path"

const file_path = path.join(import.meta.dirname, "./file.txt")
console.log(file_path)
// /tmp/file.txt

でも現在の LTS は 20 です
21 に機能追加されて 3 ヶ月以上経っても 20 にバックポートされないのであまり期待できなそうです
使えるのは 22 の LTS からになるかもしれません

それに CJS と近い感じで書けるだけで __dirname が import.meta.dirname になって少し長いです

fs が URL 形式のパスもサポートしてくれるともっと楽なんですけどね
それなら同じフォルダのファイルを読み取るときはこれだけで済みます

fs.readFileSync(import.meta.resolve("./file.txt"))

そういう話がないのか探してみたのですが あまり積極的ではないようで放置されて issue がクローズされてました
https://github.com/nodejs/node/issues/48994

単に中で fileURLToPath を通すだけで file://foo/bar みたいなものを入れてもエラーにするでいいと思うのですけど
遅延初期化処理が非同期処理のときにうまく動かなくて困った話
うまく動かないところがあって 原因を見つけるのに苦戦しました
たまにしか使わない処理なので最初の使用時に初期化処理を行うようにしてるものです

イメージ

const execute = async (args) => {
if (!instance) {
await setupInstance()
}

return instance.run(args)
}

初期化処理に非同期処理が含まれるので初期化処理全体が非同期処理になっています
2 回目の呼び出しが 1 回目の直後だと インスタンスはあるものの初期化処理が完全に終わってないので instance.run の実行はできるのに何も行われないみたいな動きになってました

インスタンスの初期化が終わらないと 内部のリスナが設定されていなくてイベントを起こしても何も起きないという状況でした
ここが undefined のプロパティ参照や非関数の実行など実行時にエラーになってくれていれば簡単に原因がわかったのですけどね

インスタンス側に ready プロパティを用意して初期化後に解決する Promise を入れておくなどの対処が必要でした

const execute = async (args) => {
if (!instance) {
await setupInstance()
}
await instance.ready

return instance.run(args)
}

ただ この最初の呼び出しで初期化する方法だと instance.run が同期的なのに execute の処理が非同期処理になってしまうのですよね
setupInstance が同期処理だとそもそも発生しない問題ですし あちこちが非同期処理になると不便なところも多いです
CSS の適用範囲を制限したい
こんな HTML があって

<h1>H1</h1>
<p>p</p>

<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>

.article の中にだけスタイルを当てたいです
ただしそのスタイルは自分で書くものではなくユーザー入力です
なのでセレクタで .article の中だけが対象になっている保証はないです

最近は CSS をネストできるので ユーザー CSS の構文が正しいことを確認したあとに

const style = `
.article {
${original_style}
}
`

みたいにすればいいかなと思ったりもしました
ですが

.article {
body:has(&) {
background: black;
}
}

とすれば body にスタイルを当てられます

ShadowDOM を使ったほうがいいのかなと思ったものの slot の中にはスタイルが当たりません

<script type="module">
const css = new CSSStyleSheet()
css.replaceSync(`
p { color: red; border: 1px solid pink; }
`)

const article = document.querySelector(".article")
const root = article.attachShadow({ mode: "open" })
root.adoptedStyleSheets = [css]
root.innerHTML = `<slot/>`
</script>

<h1>H1</h1>
<p>p</p>

<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>

slot を使わず ShadowDOM の中に .article の子要素を持ってきてしまえばスタイルは当たりますが あまり良いやり方に思えないんですよね

<script type="module">
const css = new CSSStyleSheet()
css.replaceSync(`
p { color: red; border: 1px solid pink; }
`)

const article = document.querySelector(".article")
const root = article.attachShadow({ mode: "open" })
root.adoptedStyleSheets = [css]
root.replaceChildren(...article.childNodes)
</script>

<h1>H1</h1>
<p>p</p>

<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>

(追記) (コメントありがとうございます)
@scope でもネストと同じで突破できそうと思ってましたが いろいろ試してみたところ大丈夫そうでした
& はスコープ開始のセレクタを書くのと同じらしいですが そもそもスコープ内しか対象にしない機能なので body などの外側の要素を選択しても効果がないです

あとは } が多くて @scope を抜けてしまうみたいのを許可しなければ大丈夫そうです
ネストのときは すぐにダメそうとわかって あまり考えてなかったですが CSS って HTML の innerHTML みたいな感じで CSSStyleSheet から完全な CSS 文字列を簡単に作れないんですよね
標準機能だけで構文エラーを修正したものを作れるといいのですけど
この辺はライブラリに任せたほうがいいかもしれません

続き → CSSStyleSheet から CSS の文字列を取り出したい
Google Sheets って Safari 対応してないんだ
iPad で Google Sheets のリンクを開いたら 「お使いのブラウザのバージョンはサポートが終了しました」 ってメッセージが出てきた
アプリ版を使えってことかもだけど Google でも Safari のサポートしないんだなーって思った
競合とはいえ サービスのユーザーを増やす機会なのに
Google Sheets みたいな複雑な機能になるとやっぱり Safari の対応はかなりつらいものなのかな
最近は以前より Safari で動かないが減ってきたように思うけど
Workspace 機能使うか使わないか
フォルダ構造がこうなってるプロジェクトフォルダがあります

- project_root/
- package.json
- node_modules/...
- package1/
- package.json
- node_modules/...
- package2/
- package.json
- node_modules/...

ワークスぺース機能を使ってないので 個別に yarn install して node_modules が複数あります
ワークスペースを使ったほうがいいかなと思ったものの現状のメリットもあるので迷ってます

ワークスペースを使ったほうがいいところは node_modules がまとまってムダがないことです
ワークスペースを使ってないと共通のパッケージがあるとき各 node_modules に同じパッケージがインストールされます
また yarn install を個別にする必要もないです
パッケージ間の関係でも あるパッケージから別パッケージを参照する場合に楽です

逆にワークスペースにすると node_modules が 1 つになるので yarn install の処理が重たくなります
共通のパッケージがあまりない場合は パッケージ数が大きく増えますし デメリットが大きいです

あと ワークスペースにしたときにサーバー上での yarn install で特定パッケージの依存関係のみインストールってできるのでしょうか
ウェブサーバーや別の常駐アプリと共通部分のパッケージはインストールが必要ですけど フロント側で使うパッケージはいらないです
入ることで特別問題はないのですが フロント系はパッケージ数が増えて重くなるので不要なサーバーでまで入れたくないなと思います
値とそれを更新する関数の扱い
値とそれを更新するための関数を作る時よくやるやつ

const values = []
const append = (data) => {
if (data.type === 0) {
values.push(data.value)
}
}

変数と関数をフラットにおいてる

クラス好きな人ならクラス化しそう

class X {
values = []
append(data) {
if (data.type === 0) {
this.values.push(data.value)
}
}
}
const x = new X()

一回限りなので即時インスタンス化して

const x = new class {
values = []
append(data) {
if (data.type === 0) {
this.values.push(data.value)
}
}
}

こうすると append を渡すときに this のコンテキストが消えるので bind やアロー関数でラップが必要になる
使う側で気にしなくていいようにプロトタイプじゃなくてインスタンス自身に関数を入れる

const x = new class {
values = []
append = (data) => {
if (data.type === 0) {
this.values.push(data.value)
}
}
}

これならもうクラスの必要性がないのでただのオブジェクトにして

const x = {
values: [],
append: (data) => {
if (data.type === 0) {
x.values.push(data.value)
}
},
}
シンボルをキーにしても完全には隠せないよ
ネットで見かけた記事でシンボルの使い方について プライベートプロパティとして使うみたいなのがありました

const sym = Symbol()
class X {
[sym] = 1
print() {
console.log(this[sym])
}
}
const x = new X()
console.log(x[sym])
// 1
x.print()
// 1

これで sym をエクスポートせず X だけエクスポートすると x[sym] ができないから外部から直接アクセスできないってやつですね
使い方としては別にいいと思うのですが こうすることで外部からは完全にアクセス不可能で更新できないみたいなことが書かれてました

マイナーですが Object.getOwnPropertySymbols でシンボルを取得することができます
それ以外の Object.keys や for-in ではシンボルは取ってこれないです

console.log(Object.keys(x))
// []

for (const key in x) {
console.log(key)
}
// (no output)

const syms = Object.getOwnPropertySymbols(x)
console.log(x[syms[0]])
// 1

x[syms[0]] = 10
x.print()
// 10

困ることがあるとすれば 作る側が Symbol に説明を付けてない場合です
Symbol 関数の引数に文字列を入れていれば description プロパティで参照できます
それがないと複数のシンボルがあるとき何番目がどのプロパティなのか分かりづらいです

また Object.getOwnPropertyDescriptors ではシンボルの情報も取れます
しかし キーがシンボルのオブジェクトを受け取るので結局これだけじゃアクセスできなかったりします

最近では プライベートプロパティを使ってもデバッグ用途だと不便は減りましたが 実行時にどうやってもアクセスできない不便さがあるので これくらいのゆるさのものが良いですね
V8 の harmony フラグの一覧
久々に V8 の --harmony-xxx のフラグを使ったのですが 今って harmony フラグってどれくらいあるんでしょうか
ググって出てくるものって過去の時点のものばかりなので参考にできないです

Node.js だと --v8-options で V8 のオプション一覧が出せるので ここから harmony を含むものを取り出します

root@c3872dcf640f:/# node -v
v21.6.0

root@c3872dcf640f:/# node -p process.versions.v8
11.8.172.17-node.19

root@c3872dcf640f:/# node --v8-options | grep harmony
--harmony (enable all completed harmony features)
type: bool default: --no-harmony
--harmony-shipping (enable all shipped harmony features)
type: bool default: --harmony-shipping
--harmony-import-attributes (enable "harmony import attributes" (in progress / experimental))
type: bool default: --harmony-import-attributes
--harmony-weak-refs-with-cleanup-some (enable "harmony weak references with FinalizationRegistry.prototype.cleanupSome" (in progress / experimental))
type: bool default: --no-harmony-weak-refs-with-cleanup-some
--harmony-temporal (enable "Temporal" (in progress / experimental))
type: bool default: --no-harmony-temporal
--harmony-shadow-realm (enable "harmony ShadowRealm" (in progress / experimental))
type: bool default: --no-harmony-shadow-realm
--harmony-struct (enable "harmony structs, shared structs, and shared arrays" (in progress / experimental))
type: bool default: --no-harmony-struct
--harmony-array-from-async (enable "harmony Array.fromAsync" (in progress / experimental))
type: bool default: --no-harmony-array-from-async
--harmony-intl-best-fit-matcher (enable "Intl BestFitMatcher" (in progress / experimental))
type: bool default: --no-harmony-intl-best-fit-matcher
--harmony-remove-intl-locale-info-getters (enable "Remove Obsoleted Intl Locale Info getters" (in progress / experimental))
type: bool default: --no-harmony-remove-intl-locale-info-getters
--harmony-intl-locale-info-func (enable "Intl Locale Info API as functions" (in progress / experimental))
type: bool default: --no-harmony-intl-locale-info-func
--harmony-intl-duration-format (enable "Intl DurationFormat API" (in progress / experimental))
type: bool default: --no-harmony-intl-duration-format
--harmony-set-methods (enable "harmony Set Methods")
type: bool default: --no-harmony-set-methods
--harmony-iterator-helpers (enable "JavaScript iterator helpers")
type: bool default: --no-harmony-iterator-helpers
--harmony-import-assertions (enable "harmony import assertions")
type: bool default: --harmony-import-assertions
--harmony-change-array-by-copy (enable "harmony change-Array-by-copy")
type: bool default: --harmony-change-array-by-copy
--harmony-rab-gsab (enable "harmony ResizableArrayBuffer / GrowableSharedArrayBuffer")
type: bool default: --harmony-rab-gsab
--harmony-regexp-unicode-sets (enable "harmony RegExp Unicode Sets")
type: bool default: --harmony-regexp-unicode-sets
--harmony-json-parse-with-source (enable "harmony json parse with source")
type: bool default: --harmony-json-parse-with-source
--harmony-rab-gsab-transfer (enable "harmony ArrayBuffer.transfer")
type: bool default: --harmony-rab-gsab-transfer
--harmony-array-grouping (enable "harmony array grouping")
type: bool default: --harmony-array-grouping

結構多めですね
ただ array-grouping や change-array-by-copy など すでにリリース済み機能も入ってるようです
出たばかりはまだ安定してないかもなので無効にするためなんでしょうか

デフォルトで有効かどうかは default のところが --no- ではじまるかでわかります
--no- で始まってれば無効です
in progress / experimental とは一致してないみたいです

V8 のソースコード的にはこの辺で定義されてるようでした

https://github.com/v8/v8/blob/12.2.280/src/flags/flag-definitions.h#L247
https://github.com/v8/v8/blob/12.2.280/src/flags/flag-definitions.h#L292
ダミーデータ用に配列を簡単に増やす
テスト用にその場限りで適当にデータを増やしたいことがあります

something(
value,
[
{ a: 1 },
{ a: 2 },
{ a: 3 },
],
)

みたいなのがあって配列の要素数を 30 くらいにしたいです
実際はもっと長いので範囲選択のコピペもちょっとめんどうだったりします

something(
value,
[
{ a: 1 },
{ a: 2 },
{ a: 3 },
].repeat(10),
)

みたいなことがしたいですが配列には repeat メソッドはありません
サクッとかける方法でメソッドで要素数を増やしたいです

ということで使ってる flatMap

something(
value,
[
{ a: 1 },
{ a: 2 },
{ a: 3 },
].flatMap(x => Array(10).fill(x)),
)

1, 2, 3 の繰り返しじゃなくて 1 が続いた後で 2 が続いて 3 が続くことになるのと 全部参照は同じという欠点はあるのですが 数が増えれば中身は気にしない場所ではこれでもよかったりします
引数追加でもウェブ互換壊れそう
Iterator Helpers はじめウェブ互換に影響してる変更を見ていてふと思ったけど引数の追加でも壊れそうですよね
JavaScript だと map や filter 等の関数ではコールバック関数の 2 つめに index が 3 つめには配列自身が渡されます

parseInt を map に使うとおかしくなるのは有名です

[1, 1, 1].map(parseInt)
// [1, NaN, 1]

これがあるのと this が壊れることがあるのでアロー関数を使うことが多かったりしますが 直接関数を入れるケースもありえます
多いのだと Boolean でフィルタです

[{a: 1}, null, {a: 2}].filter(Boolean)
// [{a: 1}, {a: 2}]

他にも btoa など受け取る引数が 1 つのものは直接コールバック関数に渡したりします

["abc", "xyz"].map(btoa)
// ['YWJj', 'eHl6']

こういった関数に引数が追加されると 意図せず 2 つめ以降の引数を渡してしまって動作が変わります
引数の追加って互換性あるように見えますが JavaScript みたいに引数の数が一致してなくても動く言語だとそうとも言えなそうですね
ネットで見かける Windows Sandbox を Home Edition で使う方法
Windows Sandbox を使いたいことがよくあるのですが その時使ってる PC が Pro じゃないこともあります
公式ドキュメントでは Home エディションはサポートしてないと書かれています
ただ ググると Home エディションでも有効化するという bat ファイルが色々出てきます
サイトはいくつも出てきますが bat ファイルの中身は同じみたいです

怪しげなファイルをインストールするものだったり 海賊版的な方法だったりするのかと不安もあるので中身を見てみました
長い割にほとんどが管理者権限で動かすための部分だったりで実質は以下の部分だけでした

dir /b %SystemRoot%\servicing\Packages\*Containers*.mum >sandbox.txt
for /f %%i in ('findstr /i . sandbox.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i"
Dism /online /enable-feature /featurename:Containers-DisposableClientVM /LimitAccess /ALL

パッケージの追加もパッケージ名はローカルにあるものから作ってますし外部からの取得はしてなさそうです
これだけで動くなら正規の方法のようです
ただ問題起きたら影響が大きそうなので事前に壊れてもいい PC で試したいので実行はまた今度にします



ちなみに 1, 2 行目でやってることは

C:\Windows\servicing\Packages\ 内の Containers を含む .mum ファイルすべてに対して

dism /online /norestart /add-package:***

を実行しています
*** のところに .mum ファイルのフルパスが入ります

一旦ファイルに出力してますが 特別な意味はなさそうです
findstr を使ってますが findstr は正規表現で検索するコマンドで 検索条件が「.」なので全件に一致します
空行は無視して 1 件ごと取得するために使ってそうです
作ったファイルもすぐに消しています

やってることは PowerShell のこれと同じはず

$items = dir C:\Windows\servicing\Packages\*Containers*.mum
foreach ($i in $items) {
dism /online /norestart /add-package:$i.FullName
}
dism /online /enable-feature /featurename:Containers-DisposableClientVM /LimitAccess /ALL

最終的に有効化するのが Containers-DisposableClientVM なら add-package するのは Containers を含む全てではなく Containers-DisposableClientVM を含むものだけでも良さそうに見えますが 他も必要になるのでしょうか

> dir C:\Windows\servicing\Packages\*Containers-DisposableClientVM*.mum | select name

Name
----
Containers-DisposableClientVM-merged-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.1.mum
Containers-DisposableClientVM-merged-Package~31bf3856ad364e35~amd64~ja-JP~10.0.19041.1.mum
Containers-DisposableClientVM-merged-Package~31bf3856ad364e35~amd64~~10.0.19041.3636.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~ja-JP~10.0.19041.3636.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~~10.0.19041.3693.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~~10.0.19041.3803.mum
来年は Deno 使おうかな
なんとなく Deno のドキュメントを眺めてると思ったよりも標準ライブラリが充実していました
https://deno.land/std@0.209.0?doc

CSV/JSONC/YAML/TOML などのファイル形式を扱えます
HTML のエスケープや正規表現のエスケープ機能があります
日付のフォーマット機能があります(足し算や月末取得などはなくシンプルな機能のみみたい)

この辺は普段から JavaScript のデフォルト機能にあってほしいと思うものです
Deno の場合は標準ライブラリも URL を指定してダウンロードするわけですが 一応標準ライブラリという扱いで入ってるのは良いところだと思います

Deno が npm サポートや Node.js 互換に方針転換したくらいから興味を失ってましたが Node.js 用コードがほぼそのままで動くわけですし 便利機能が多いなら Deno の方を使うでもいいのかなと思いました
Node.js の新機能で Deno にあったから追加したというのも割りと見ます
Deno のほうが先に機能追加されてると言えます

TypeScript 関連でも Deno の対応が早いと感じてます
JavaScript に新規追加された機能で TypeScript 公式に型定義を提供してないので使うと型エラーになるというものがありましたが Deno では独自に対応してたりしました
動かすために Deno のコードから型定義をコピペしてきたこともありました

懸念は std の機能にまだ Unstable が多いところです
以前も全然安定してないし まだいいかと思って詳細は見なかった覚えがあります
でも結構経ってもまだ Unstable ですし 長期的に Unstable のままになってそうです
日付のフォーマットとか HTML のエスケープなどは API や動作が変わりそうなものでもないですし バージョンを固定してれば勝手に動かなくなってるわけでもないので 別にいいかなというところです
filter は結構遅い
const create = (len) => {
return Array.from(Array(len).keys())
}

const m = create(10000)
const a = create(10000)
const b = create(10000)
const c = create(10000)
const d = create(10000)

console.time()
const result = m.map(id => {
return {
a: a.filter(x => x === id),
b: b.filter(x => x === id),
c: c.filter(x => x === id),
d: d.filter(x => x === id),
}
})
console.timeEnd()

m を基準に各要素に a, b, c, d を id 検索して一致するものを配列で保持する
これだと create ですべて [0, 1, ..., 9999] が入ってるのでフィルターの結果はすべて 1 件

毎回フィルターするのはムダそうに見えるけど
m の 1 要素あたりの処理で 1 万 x 4 = 4 万回の処理
それを m の要素 1 万回なので 4 億回

単に for ループで 4 億回ループしてカウントアップしても 800 ms くらいで 1 秒かからない

また実際には create で作られる a, b, c, d の要素には m の中には含まれない id が多数あって 0 件も多め
事前に a などを id ごとにグループ化できるけど 実際には filter のあとに変換処理の map もあって 使わない要素まで変換するのはムダになりそうという判断で m の map の中で都度フィルター
変換の方がフィルターよりも重たそうと思ってたけど実際は変換は大したことなくて フィルターでかなり遅くなってた

上のコードの実行時間は 21 秒くらい
単純な 4 億回ループの 800ms と比べるとかなり遅い

フィルターを使わずグループ化してみる
上のコードに合わせて フィルターした要素の変換はなし

const create = (len) => {
return Array.from(Array(len).keys())
}

const m = create(10000)
const a = create(10000)
const b = create(10000)
const c = create(10000)
const d = create(10000)

console.time()

const a_map = new Map()
for (const x of a) {
const arr = a_map.get(x)
if (arr) {
arr.push(x)
} else {
a_map.set(x, [x])
}
}

const b_map = new Map()
for (const x of b) {
const arr = b_map.get(x)
if (arr) {
arr.push(x)
} else {
b_map.set(x, [x])
}
}

const c_map = new Map()
for (const x of c) {
const arr = c_map.get(x)
if (arr) {
arr.push(x)
} else {
c_map.set(x, [x])
}
}

const d_map = new Map()
for (const x of d) {
const arr = d_map.get(x)
if (arr) {
arr.push(x)
} else {
d_map.set(x, [x])
}
}

const result = m.map(id => {
return {
a: a_map.get(id),
b: b_map.get(id),
c: c_map.get(id),
d: d_map.get(id),
}
})

console.timeEnd()

結果は 20ms くらいで 1000 倍くらいの高速化
単純な === が条件のフィルターってほぼ無視できるくらいに考えてたけど結構遅めだった

最近は自分でやらなくても groupBy があるので そっちにしてみるともっと短くかける

const create = (len) => {
return Array.from(Array(len).keys())
}

const m = create(10000)
const a = create(10000)
const b = create(10000)
const c = create(10000)
const d = create(10000)

console.time()

const a_group = Object.groupBy(a, x => x)
const b_group = Object.groupBy(b, x => x)
const c_group = Object.groupBy(c, x => x)
const d_group = Object.groupBy(d, x => x)

const result = m.map(id => {
return {
a: a_group[id],
b: b_group[id],
c: c_group[id],
d: d_group[id],
}
})

console.timeEnd()

プロパティの参照のみならオブジェクトが優れると聞いたのでオブジェクトにしてみたけど 速度はほぼ違いなかった
AI 補完結構良さそう
最近はコードの補完を AI がやってくれるサービスがあると聞きます
ただ有料だったりで気楽に使えなそうと使うことはなかったのですが VisualStudio 2022 で C# を書いていたら補完候補を出してくれました
for 文みたいな毎回同じパターンで書くところは手入力だと面倒なのでかなり楽です

関数名から中身を全部作るみたいのは あまり期待通りに動かなそうで良い印象はなかったですが こういう小さい範囲での補完なら扱いやすそうです
VSCode でも導入したくなりました
でも VSCode の拡張機能でこういうのに対応してるのはどれも有料なんですよね
VisualStudio みたいに標準機能として実装されると嬉しいです
プリミティブは thenable にできないみたい
オブジェクトじゃないしね

zx を使うとコマンドの実行をこう書けます

const path = "/tmp"

await $`cd ${path}`

$ は zx が提供する関数です
テンプレートリテラルのタグ関数として動作します

C# でも似たことをやってるコードを見かけました

var path = @"C:\tmp"

await $"cd {path}"

どうやってるのだろうと思いましたが C# の場合は $ は文字列中の埋め込みのために付けるもので 見た目は似てますが JavaScript とは違います
文字列の await で実行される処理を追加してるらしいです

それなら JavaScript もできるかもと思って

String.prototype.then = function() {
// なにか処理
}

await "echo 1"

のようにしてみましたが await では 文字列の then は呼び出されませんでした
thenable オブジェクトと呼ばれるだけあって オブジェクトでないとダメそうです
ユーザーの概念があるところでは localStorage を暗号化
前の記事で書いたログアウト後に localStorage が残る問題
対処するならどうするのがいいのかなと考えたところ ユーザーの key で暗号化して保存するのが一番かなと思います
暗号化しておけば別ユーザーでログインしてアクセスしたときに復号に失敗するので無視して復元なしになります

単純にログアウト時にクリアということも考えられますが Cookie のセッションや時間切れによる自然ログアウトという可能性もありえます
この場合は localStorage にデータは残ったままです
ログイン時に前回のログインユーザーと違えばクリアすることもできますが ログインする前に直接 localStorage を見ることだってできます
その点では暗号化しておけば安心です

ただ 暗号化は少し重ための処理になるので 入力イベントのたびに localStorage に書き込んだり localStorage からデータを探したりするのは向かなくなります



思いつきでちょっとしたものを作ってみました
https://nexpr.gitlab.io/public-pages/encrypt-storage/example.html

ユーザーを選んでログインボタンを押すとそのユーザーでログインできます
ログインといっても JavaScript で cookieStore に直接書き込む擬似的なものです
ログイン後は textarea が出るのでここに書き込むと内容が自動で localStorage に保存されます
ログアウトして別ユーザーでログインしても復元されません
同じユーザーでログインすると復元されます
localStorage のキーはひとつだけなので 別ユーザーでなにか入力すると上書きされて前のユーザーの入力情報は消えます

ここでは暗号化のキーはユーザー ID を元に適当に作ってますが ちゃんとやる場合は他ユーザーのキーを予想できないようにサーバーでランダムに作って ログイン中のユーザー情報と一緒に受け取るとかが必要です
コードのプレビュー機能
メインの方のブログと こっちのサブのブログの使い分けですが 基本は長さです
主にセクション分けしたいくらいになってきたらメインの方に書いて そうでもないときはサブの方です

他にはプレビュー機能の有無があります
プレビューボタンを押すとコードブロック内の HTML を実際に別タブで表示するので動きを確認できます
実現方法は新しいタブを開いて window オブジェクト経由で document.open して document.write するというだけ
あんまり好ましい方法じゃないと思いますが こういう用途に限ればありかなと思ってます

ただ 最近のこういう機能を実現してるページって ServiceWorker を使ってます
ServiceWorker を使うと実ページとして URL を持ってアクセスできます
今の方法だと動的に document.write で書き込んでるのでリロードすると再表示できない とか devtools でデバッグするとかで問題があるのですが ServiceWorker にするとそれにも対応できます
サブブログの方は ServiceWorker を使って機能を追加しようかなと考えてます

でも プレビューできるようにするとなると HTML 形式で 1 ページ全体のコードを記載する必要ができます
できるだけ短くシンプルにしたいはずの Short のブログなのに長くなってしまうのは どうなのかなと思ったりもしてます

ほぼ閲覧者はいないと思いますが 一応ライブドアブログはスマホ表示もあって こっちだと共通の処理やスタイルが適用されないので あまり特殊な機能を入れてるとそっちに影響したりします
CustomElements とか実質使えないですし

と考えると Gist などに完全なコードをおいて Githack でページを用意してそこへのリンクをつける程度がいいのかもです
Gist だと Githack を通さないとページとしてアクセスできないので 1 リポジトリにまとめて Pages 機能でもいいかも

(流し見するとタイトルが「コードレビュー」に見えてしまう)



その後

GitLab は VSCode の UI で編集してコミットできるようになってて便利だったので GitLab Pages にしようと思ってます
ただ 気になるところとして Gist を Githack で公開する場合はリビジョンも URL に含まれます
それに対して GitLab Pages は常に最新版です
各バージョンをフォルダとして用意すればできなくもないのですが バージョンで管理するようなちゃんとしたものでもないです

バグ修正レベルなら最新でもいいのですが 大きく変更したりフォルダの移動が起きると当時のバージョンがあったほうがいい気もします
はっきり決まらないですが ブログ記事の古いものなんてそんな見返すこともないので深く考えなくていいかな(諦めた)
Vite で動的インポートにしたらファイル数が思ってたよりかなり増えた
基本フロントエンドのビルドでファイルをまとめるとき 1 ファイルに全部をバンドルしているのですが 結構重たくなってきました
とりあえずページごとに動的に import するようにして分けたのですが 想像以上にファイル数が増えました

例えば こういう依存関係の場合

index.js
A.js
X.js
B.js
Y.js
Z.js
C.js
Z.js

A, B, C をそれぞれ動的なインポートにします
メインである index.js 以外に A, B, C の 3 つのファイルができて 4 つかなと思ったのですがそうじゃなかったです

Z は B からも C からも使われます
B と C の両方に Z を入れると両方を使うページでは二重になってムダですし 別モジュールとして扱われてしまう実害も出ます
なので Z は独立したモジュールとして自動的に分割されます
X は A だけで使われ Y は B だけなので これらはそれぞれ A と B に含まれることになります

実際のところ Z みたいに複数箇所で使われるモジュールってかなり多いです
各ページを開いたときにロードするファイルはメインのファイルとそのページ用のファイルの 2 ファイルになるくらいに考えていたのに 実際はそれよりはるかに多かったです

それでも最適化はされてるようで export せず console.log だけみたいな場合は一つのファイルにまとめられたり B と C の両方が Y と Z を読み込む場合は Y と Z は一つのファイルにまとめられるなどしてました

でも実際の大きめのプロジェクトになると まとまらないことも多そうですし ESM で直接ロードするのとそう変わらないくらいファイルをロードすることになりそうです
それならファイルをまとめる必要あるのかなと思いますね
React などで表示切替をアニメーションさせるとき
アニメーションで少し違和感を感じるところがありました
detail と summary タグ的なもので 高さを切り替えて表示の有無を切り替えているものです
UI ライブラリだとアコーディオンとか呼ばれてたりもします

devtools でゆっくりにしてみると 閉じてる途中で中身が消えていました
開くときには途中までアニメーションしてそこからは一気に本来の高さに切り替わっていました

React などのフレームワークならでは感がありますね
単純な開閉ならともかく 選択したものを表示する系だと 閉じてるときは選択中のものがありません
こういう感じで作ってると自然とそうなりそうです

<Accordion open={!!data}>
{data && <Internal data={data}/>}
</Accordion>

DOM を直接操作するなら選択が解除されても閉じるだけでよく わざわざ中身まで更新するのは面倒なので中身はそのままで放置だったりします
しかし React 的なものだと残すほうが大変です
開閉と状態と選択状態を別に保持して 閉じるのが完了したイベントで選択を解除するとか 選択と画面表示を別にして 選択されてなくても画面表示用のデータは最後の選択を保持するとかしないといけないです

開くときは その時点では開いたときの高さが不明なのでうまくアニメーションできないです
これは useLayoutEffect などでアニメーションさせればできそうな気はしますが ライブラリを使ってる箇所ではうまくいってないようでした
高さ不明なので適当な高さまでアニメーションで開いて 完了後に height を auto に切り替えることで実際の高さになるのですが そこで急にサイズが変わってました

DOM を直接触るとき以外は基本アニメーションはさせないようにして UI ライブラリのデフォルト挙動のみにしてましたが ちゃんとアニメーションさせようとすると大変そうです
highlight.js を更新
ブログのコードハイライトがところどころ崩れてました
JSX や ${} が入ってくると崩れやすいようです
highlight.js はメジャーアップデートが進んでたのでアップデートしてみました
軽く見た感じでは改善されてるようでした

これに合わせて highlight.js は CDN から動的に取得するよう変更しました
これまでは CDN が落ちてたり重かったりすることも考えて 一応ブログのサーバーにライブラリをアップロードしていました
CDN が落ちることはめったに無いとはいえ 画面表示に関わる部分ですし インポートに失敗すると初期化処理が中断されるのでサイドバーなど関係ない部分もちゃんと表示されなかったり動作しなかったりすることになりますし
ですが そこまで重要なブログでもないですし ライブドアブログのストレージにライブラリを配置すると更新するのが結構面倒だったので CDN に頼ることにしました



highlight.js の言語モジュールについてですが 画面に必要なもののみインポートするようしました
最初は手抜きで 全部のページで同じファイルのロードで考えてました
しかし ごく一部のページでしか使ってないマイナー言語も多くありました
nim とか elm とか vbs とか
こういうのがあるたびにロードする言語を追加するのは面倒です
それに CSS しかハイライトしないページでも 関係ない言語のモジュールをいくつもロードするのはムダです

ということで highlight.js は言語モジュールを含まない core 版をロードして 言語モジュールはすべて別途ロードさせるようすることにしました
ただこうすると 本来は highlight.js 側で管理してくれているエイリアスを自分で管理しないといけなくて面倒でした
JavaScript の言語モジュールは javascript.min.js というファイル名です
js という言語名から自動でこのファイルをインポートできないので 自分でエイリアス情報を管理して js を javascript に置き換えてからモジュールをインポートします

また code タグごとに並列して処理すると 同じ言語の code タグが複数あるときに少し面倒です
言語ごとに Promise を保持するようにして 最初にロードするときに Promise を作って 2 つ目以降では その Promise が解決されたら処理という感じにしないといけません
また 依存関係もあります
HTML だと内部で CSS や JavaScript も表示するという感じです
JavaScript が内部で HTML を表示することもあるので循環参照します
上記のような単純な Promise だと循環参照したときにデッドロック化することもあり 対応がとても面倒です

ここを頑張る気力もなかったので 2 段階の処理にしました
先に今のページに存在する言語とその言語に依存する言語の一覧を作ります
それらを全部インポートしてから ハイライト処理に移ります
全部の言語モジュールをインポートするまでハイライト処理が始まらないので少し遅くなりますが 2 回目以降はキャッシュされてますし 全部の言語と言ってもそのページで使われるものだけで ほとんどのページでは 2, 3言語くらいです
それくらい別にいいかなと楽な方にしました



ところで ライブドアブログのファイルマネージャーは相変わらず使いづらいです
まとめてアップロードやまとめて削除する機能がないです
10 くらいなら手動でやってもいいですが 100 近くもあるとさすがに嫌です
ひとつひとつボタン押してられないので 自動化しました

ブラウザでファイルマネージャーの画面を開いてコンソールで下のコードを実行します
これで今開いてるフォルダ内の全ファイルを上から順番に削除します

while (true) {
const img = document.querySelector(`.ftpFileList img[alt="削除する"]`)
if (!img) break
img.click()
await new Promise(r => setTimeout(r, 1000))
const ok = document.querySelector(`input[value="削除"]`)
if (!ok) {
alert("削除ボタンがみつからない")
break
}
ok.click()
await new Promise(r => setTimeout(r, 1000))
}

最初の img が×アイコンのボタンなので ここのセレクタのところで条件に一致するものだけにしたり 残したいものを除外したりします
いつの頃からかフォルダの削除はできるようになってたので 本当に全部削除だとフォルダを消したほうが早いです

一応 API も用意されてるのですが 使いやすくないし面倒なのでこれのほうが楽だったりします
getElementsByXXX の速度(続き)
前記事から続きます
HTMLCollection という特性上 遅そうに思ってましたが速かったです
プロパティにアクセスするたびに 検索していたらこの速度にはならないと思うので 工夫はされてるはずです
思いつくのだと DOM の更新時に変更が内部的に通知され その変更が HTMLCollection の監視対象であれば内部状態を更新している とかでしょうか?
それだと有効な HTMLCollection があれば DOM の更新速度が遅くなりそうです

試しに数百の HTMLCollection を作成してから body の子孫要素を大きく書き換えてみました
そのときの DOM 更新にかかった時間と HTMLCollection を作らない場合の DOM 更新にかかった時間を比べてみました

結果は差があるとは言えないくらいのものでした
どのタイミングで処理されてるのでしょうね
TypeScript の ! は競合しないのかな
TypeScript のコードを見ていたときのこと

fn(value!)

みたいなコードがありました
TypeScript の ! って JavaScript の ?. の ? みたいなポジションだと思ってたのですが違ったようです
!. でまとまってる必要はなくて ! だけの単体で使えて 直前の式の nullable を解除するようです

任意の式の末尾に ! が書けるようです
それって JavaScript の構文と競合するようなしないような

JavaScript での ! は式の前に書く単項演算子です
! の次は式である必要があります
一方 TypeScript の ! は式の末尾に書きます
競合するには 「expression expression」 が有効でないとだめです
「(() => {}) ``」 はどちらも式で満たしてますが特殊なもので後者はテンプレートリテラルである必要があります
! が入ることができないので競合できないです

意外と大丈夫なのでしょうか

1 つ特殊なケースで競合を見つけることができましたが 構文的に発生する場所でどっちかが明確なので問題にはならないものでした
そのケースというのはこれです

yield!+1

ひとつめの解釈方法はこうです

function* gen() {
yield !+1
}

このときの yield はキーワードで function*() {} の中でのみ書けます
yield false と同じです

もうひとつはこれです

const yield = 1
yield! + 1

function*() {} の外なら yield を変数に使えるので 変数に ! をつけたものと 1 との足し算です

function*() {} の中か外かで完全に分かれるので競合にはならないです
あと strict mode なら yield は変数に使えないです
input と textarea のフォント
input や textarea のフォントってどうするのがいいんでしょうか

デフォルトを見てみると input と textarea では異なるようです
またブラウザでも異なります

Windows 環境で Chrome/Edge (117) を見ると

input:
  ユーザーエージェントの font-family: 空
  表示されるフォント: Arial, Meiryo

textarea:
  ユーザーエージェントの font-family: monospace
  表示されるフォント: MS Gothic

Windows 環境で Firefox (118) を見ると

input:
  ユーザーエージェントの font-family: MS Shell Dlg 2
  表示されるフォント: tahoma, Meiryo

textarea:
  ユーザーエージェントの font-family: monospace
  表示されるフォント: consolas, MS Gothic



どちらも等幅かと思ったら input はそうではないようでした
また等幅フォントのデフォルトはギザギザして汚い MS Gothic でした

monospace で表示されるフォントはブラウザの設定で変えることができ 私は別フォントにしてたので全然気づいてませんでしたが 設定してないブラウザでみるとすごく残念な画面になってました
(Chrome のフォント設定画面は chrome://settings/fonts)
基本的にはデフォルトのものにしておきたいですが さすがにこれは汚すぎで見るに堪えないです
変えておこうかなと思っても標準フォントで等幅ってこれといったのが思いつかないです

UI ライブラリの入力コンポーネントや世の中のページを見てると そもそも等幅にしてるところがそんなに多くないようでした
等幅にしているところはだいたいウェブフォントで独自のフォントを使っているようです
可変幅のところでは inherit にして親と一緒 つまり div や p などの通常のテキストと同じフォントにしてるのを見かけました
また input と textarea でフォントを分けず まとめて同じ可変幅のフォントを設定していたりです
たしかに分ける必要って特に無いと思いますし 等幅じゃなくて困ったということもありません

とりあえず本文と同じ Meiryo でいいかなと思いました



この考えでいくと テキストエディタも可変幅でも良さそうに思えてきました
プログラムで 「=」 や 「:」 や 「//」 の位置など 行頭以外の場所を縦に揃えたがる人は抵抗がありそうですが 行頭以外は基本揃える必要ないって私からすると別に問題無い気もします
考えてみると 可変幅になってる textarea でコードを書くことって普通にあります
「見直してみたら可変幅だね ここ」 くらいに気にしてなかったです
また 日本語がない半角フォントが設定されていて 半角のみ等幅になっていて 日本語が入ると可変幅になるところもありました
フォントの違いが気になりはしますが 一時的なところなら実害はないので放置でした

VSCode のフォントを Meiryo にしてもいいかななんて思いましたが 考えてみたらプログラミング用フォントは等幅なだけではなく 紛らわしい文字の差別化もしてくれてるのですよね
やっぱりプログラミング用フォントを使うのが無難そうです

他には 等幅のほうがパフォーマンス的にも優れてそうな気はしました
文字関係なく幅が固定のほうが事前にサイズ計算ができて重たいファイルを開いて画面に表示するときは差が出そうです
と思いましたが等幅でも半角全角が存在しますし 絵文字みたいなところでは完全に等幅になっていないです
それらも考慮しないといけないので あまり変わらないかもです

あとは 矩形選択したいようなものを扱うときでしょうか
ただこれはマルチカーソル機能があればそっちのほうが高度なので 重要でないかもしれません

どちらかというと SQL の結果など 表形式のテキストを貼り付けてきれいに見えないほうが困りそうです
条件つきメソッドチェインしたい
最近はメソッドチェインを長く書くようなライブラリをあまり使うことがなかったのですが 久々につかうとやっぱりチェインに条件をつけたくなります

こういうの

const val = new A()
.method1()
.method2() // ← flag が true のときだけ追加したい
.method3()

ライブラリによっては考慮されていて チェイン内で分岐できるようなメソッドがあったりします
しかし分岐したいなら変数にいれて if を使ってというスタンスのライブラリもあります
そうするとメソッドチェインできれいに書けるメリットが失われます

JavaScript の機能が増えてきているといってもやっぱりこういうのはいい方法がないです
ありがちなのがラップして独自の分岐メソッドを追加する方法

const val = $(new A())
.method1()
.$(flag, $ => $.method2())
.method3()
.unwrap()

しかし method1 などはラップしたオブジェクトが持っていないので Proxy が必要になります
また最後にアンラップして中身を取り出さないといけないです
このタイプは過去何度か作ってみても 利便性のいまいちさや Proxy に抵抗があって実際には使ってないのですよね

全てのオブジェクトにメソッドを追加する prototype 拡張がもっとシンプルになるのですが prototype 拡張は積極的に使うのはどうかなというところです
特に全てに影響する Object.prototype ですし

ただ キーがシンボルなら 影響はほぼなさそうですし ありなのかなと思ったり

const val = new A()
.method1()
[chain_if](flag,
$ => $.method2(),
)
.method3()

チェインの記法がちょっと変わるのが専用構文ぽくて ひとつ上の例よりは好きかもしれないです

動かす用のサンプル

Object.prototype[Symbol.for("if")] = function(cond, fn) {
if (cond) {
return fn(this)
} else {
return this
}
}

class A {
arr = []
method1() {
this.arr.push(1)
return this
}
method2() {
this.arr.push(2)
return this
}
method3() {
this.arr.push(3)
return this
}
}

const flag = true // or false
const val = new A()
.method1()
[Symbol.for("if")](flag,
$ => $.method2(),
)
.method3()

console.log(val)
セミコロン猛者
ちょっと前に見かけた JavaScript のセミコロンの話題
初心者だとどこに書けばいいのかわからない という話でした

たしかになくても動いてしまう以上 初心者はどこで必要なのか分かりづらい気がしますね
C や PHP など類似の構文でセミコロン必須の言語に触れたことがあれば そこではセミコロンが無いとエラーなのでちゃんとルールを把握してなくても使っていれば感覚で必須な場所はわかってきます
その流れで JavaScript を書いていれば セミコロンを文末に書く場合に必要な場所には困らないと思います
しかし JavaScript が初めてなら 要らないところにつけても 必要な場所につけなくても動きます
ちゃんと構文を理解してなければ難しそうです

基本は文末に書く と言っても if や for 文や関数宣言では不要です
セミコロンを書かない部分は 改行もセミコロンもなく 1 行にまとめて動く部分です

function a() {} a()

const a = function() {} a()

上は動きますが 下は構文エラーで動かないです
なぜかというと 上は関数宣言なので 「}」 が来るとそこで文の終わりと判断できます
なので自然と次のトークンの a は新しい文の始まりとわかります
下だと代入式なので 「}」 で文が終わるかは不明です
なので次のトークンの a も読み取ってみて ここで構文エラーになります

それならここで自動で文を分割してくれれば と思わなくもないですが 先を読んでみないとわからないのはパフォーマンスやパーサーの複雑度に影響しそうですし 構文エラーのバグが意図せず動いてしまうとかあるのでしょう

話を戻して 初心者にこういうところまで理解してというのは無理があると思います

構文チェックのツールやフォーマッターを使えば良いという意見もありそうです
ですが これも初心者がいきなり使うにはハードルがあるように思います
初心者というのは環境を準備というのが一番苦労するところです
JavaScript はメモ帳レベルのテキストエディタでも HTML を書いて script タグに JavaScript コードを書けばとりあえず動くという手軽さが魅力なのに こういうのの準備ってそういうメリットが失われますからね

なので個人的にはセミコロンは書かないほうがいいと思ってます
コード上入れるとしてもフォーマッターが入れて 人が書く必要はないと思います

ただその議論では予想外の方法を取ってる人がいました
すべての文にはセミコロンを入れるルールにしているようです
if や for のあとに入れても意味ないだけで別に問題ないですからね
たしかに統一感があり 迷うこともないので それならそれでいいんじゃないかなと思えました

こんな感じでしょうか

function a(value) {
if (value > 1) {
for (let i = 0; i< value; i++) {
console.log(foo(i));
};
} else {
console.log(value);
};
return {
foo: {
bar: {
baz: () => {
}
}
}
};
};

ただ最後に } が並ぶとき セミコロンなしのほうが全部が揃って見た目がきれいには思います

				}
}
}
}
}

意味が違う } でも全部一緒だとむしろ分かりづらいという考え方もできますが
React だとリアルタイム反映のほうがやりやすい
React などのライブラリを使わず DOM 操作で作ってるページだと なにかが変わったときにリアルタイムにあっちもこっちも反映って面倒です
だから確定ボタンを押したみたいなときに全体に反映させて それまでは外部に影響させないとか最小限の部分だけに反映させたりということが多いです

そうなってるものを動きを変えずに React で置き換えそうとすると 逆に面倒なんですよね
React だと state に持ってる情報から今の画面状態を作るので普通にやると全部が変わってしまいます
確定ボタンを押すまでは他に影響しないようにしたいなら 新たに state を用意する必要があります
グローバルの state と ローカルの state を用意して 普段はローカルの方だけを更新して ボタンが押されたらローカルをグローバルに反映するみたいなことになります
(グローバルと言ってもその state を使う範囲でのグローバルなのでページ内だったりタブ内だったりで プログラム的なグローバル変数というわけではないです)

React だと state 特にコンポーネントローカルの state はあまり増やしたくないのですよね
ローカルに state を持つことでそれを更新するための処理も必要になります
特に props や別フックの更新に応じて state を更新する必要があれば 変更を監視して更新するための useEffect もセットで必要になります

フォームや state 系のライブラリを見てみても 他の値の更新で state を更新する必要がある以上 どれもこの問題あるように思うのですが いい感じに解決できるものがあるのでしょうか
React 18 で unmount 後に state を更新したときの警告がなくなってた
未だに React 18 より 16 や 17 を使ってることが多いです
そんななので今更気づいたのですが 18 では コンポーネントのアンマウント後に state を更新したときに出ていた警告がなくなっていました

こういうの

Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

表示させる例

export default () => {
const [shown, setShown] = useState(false)
return (
<div>
<button onClick={() => setShown(!shown)}>toggle</button>
{shown && <Component/>}
</div>
)
}

const Component = () => {
const [count, setCount] = useState(0)
const onClick = () => {
setTimeout(() => {
setCount(count => count + 1)
}, 3000)
}
return (
<div>
<button onClick={onClick}>{count}</button>
</div>
)
}

toggle ボタンのクリックで Component の有無を切り替えます
Component はボタンをクリックするとカウントアップするコンポーネントですが 3 秒ディレイしてからカウントアップします
この 3 秒の間に Component を表示しないようにすると アンマウントされて その後に state の更新が起きるので警告が出ます

実際のケースでは fetch など非同期処理を行うコンポーネントで その処理が完了する前に別のページに遷移したりタブを切り替えたりすることで発生することが多いです

これの対処をちゃんとやると面倒です
アンマウント済みかを示すフラグを用意してアンマウント時に更新します
state 更新時にはフラグを見てアンマウント済みなら更新をスキップします
面倒な上にコードも複雑化する嫌なやつです

実際こういう一回限りの setState がアンマウント後に行われてもメモリリークにはならないです
それでも警告として出る以上対処しようとして その結果 面倒が多く複雑になるという問題があったのでなくしたのかもですね

メモリリークになるのは setInterval や外部のサブスクリプションなどで 1 回限りではなくイベントが起きるたびに更新が発生するものです
こういうのです

useEffect(() => {
setInterval(() => {
setState(Date.now())
}, 1000)
}, [])

この警告は出ていても無視していいケースが多かったですが 無くしてしまうとこういうケースに気づけないという問題はありそうです
成功と失敗の英語
ステータスや結果として受け取る情報で成功や失敗が入ってるときなんて英単語にするか

名詞の 成功・失敗 だと success / failure
動詞の 成功する・失敗する だと succeed / fail
過去分詞形の 成功した・失敗した だと succeeded / failed

success / failure か succeeded / failed の組み合わせのどっちかかなと思うけど succeeded ってほとんど目にしない
ググっても Windows 系のドキュメントとかが出てくるくらい
じゃあ success / failure かというと failure もけっこうレアな気がする

success と failed もしくは success / error をよく見るような気がする
よく考えると対称じゃない気がするけど 英語が怪しい日本人が作ったものじゃなく 海外のサービスでも見かけるものだし これでいいのかな
Wireit 思ってたのと違ってそう
OSS の package.json を見てると wireit というキーがあるのを見つけました
なにかと思って調べたら Google 製のツールで npm run を拡張してくれるものみたいです
https://github.com/google/wireit

便利そうなものかなと期待して詳しい機能を見ていたのですが 思ってたのと違ってそうでした
コマンド間に依存関係を定義できて 依存するコマンドも自動で実行してくれたり 並列に実行したりとかそういうのがメインみたいです

またインクリメンタルビルドやキャッシュやウォッチ機能があり そのために各コマンドの入力と出力のファイルを書かないといけないようです
たしかにこういった機能の実現だと必要そうですが これが結構面倒そうです
それに各コマンド側でもすでに持ってる情報なので二重に管理することになり 漏れが出そうですし

また環境変数を設定できるようですが静的な値のみのようです
求めてるのはコマンド実行時に OS 依存せず指定できることなので求めてることはできなさそうです
こういうのがやりたいのです

期待してたのとは違ったので私は使わなそうです
Github アクションとの統合もあるようですし OSS 向けなんでしょうかね
ライブドアブログのリッチリンク機能
特に使ってこなかったけど ライブドアブログの機能で通常のリンクとは別にリッチリンクって機能があります
一部のブログでは見かけるもので 大きめのブロックで記事タイトルや画像が表示されたりします

こんなの↓


テキストや画像をリンクにせず 単純に https://~~ みたいな URL をリンクにしてるのならこれのほうがよかったりするのかもとふと思いました

ですが中身を見てみると いまいちなものでした

iframe になっていて src には専用ドメインで UUID くらいしか情報がありません

<iframe  frameborder="0" scrolling="no" style="height: 120px; width: 580px; max-width: 100%; vertical-align:top;" src="https://richlink.blogsys.jp/embed/7a07f8d3-1c61-333f-b67f-31d0933ba2cd"></iframe>

https://richlink.blogsys.jp/embed/7a07f8d3-1c61-333f-b67f-31d0933ba2cd

です
UUID だけで元 URL の情報がないので扱いづらいです
元 URL を知るために上のページにアクセスしないといけません
それにもし将来的にこのリッチリンク機能が終了したら 復元手段がないです
変なことせず素直にパス部分に元 URL をそのまま入れる形式にしておいてくれればよかったのに
フォーマッターの行幅はインデントを除外した長さを対象にしてほしい
たいていのフォーマッターは 1 行の文字数を設定できます
指定値を超えると適当なところで自動で改行されます

80 文字だと少なすぎて インデントが深くなるところだとすぐ折り返されて逆に見づらくなります
なので 120 文字くらいにしてるのですが インデントが深くならないところだと長すぎてこれも見づらいです
トップレベルや 1 段階のインデントで 120 文字だと結構ありますからね
それくらいの長さのメソッドチェーンや配列だと折り返したくなります

元コードの折り返しを優先してくれるフォーマッターなら問題ないのですが それを無視して指定文字数に収まるなら改行を消すようなフォーマッターもあります
そう考えると 行幅の数え方を左端からじゃなくてインデントを除外してから数えるのがちょうどいい気がしますね
インデント除外で 80 文字くらいだとインデントの深さを気にせずいつでもちょうどいい長さになりそうです
オブジェクトと配列の記法のカッコの内側のスペース
const {prop1, prop2} = obj
const [item1, item2] = arr

というコードを dprint や Prettier などでフォーマットするとデフォルトだと

const { prop1, prop2 } = obj
const [item1, item2] = arr

{} だけ内側にスペースが入ってる非対称感が気になる
オブジェクトは

const { foo: bar } = { foo: 1 }

のように 1 要素に key と value みたいな複数を書くから前後の空白がある方が見やすいとか?
でも配列だって

const [a = 1, b = 2] = [10]

って書いたら同じようなものだと思うけど

ちなみに console.log での出力の場合 ブラウザ (devtools) 上ではオブジェクトも配列も前後にスペースなし
Node.js だとどっちもスペースあり

> console.log({a:1})
{ a: 1 }

> console.log([1,2])
[ 1, 2 ]
配列の最後を取得する
今では at メソッドがあるので

[1, 2].at(-1) // 2

という風に簡単に取得できます

これが無い頃は不便でした

items[items.length - 1]

と items を 2 回参照するので一旦変数に入れる必要があります
ひとつの式の中で書きたいときに扱いづらいです
なので関数を作るのですが 関数だと配列を取得する式全体を囲む必要があるので書きづらいです
なので Array.prototype を拡張して last みたいなメソッドを追加していました
しかしこれでも 最初の取得の [0] とは非対称感があっていまいちです
わかりやすさ的には first メソッドも作ればいいのかもですが コードが長くなります
特に Python も書いていた頃だったので

[1, 2][0] # 1
[1, 2][-1] # 2

のスッキリとした書き方に憧れます
実際に使うのは「最後」のみで 後ろから N 番目なんてまずないので -1 だけ対応すればいいかとこんなことをしてみたりしました

Object.defineProperty(Array.prototype, "-1", { get: function () { return this[this.length - 1] } })
;[1, 2][-1] // 2

-2 もいけそうなのに動かないし 色々問題はあって実用はためらわれるものの結構好きな方法です

ためらった結果使わず prototype を拡張する last もなんかなぁ ということで結局はこんな形でした

[1, 2].slice(-1)[0]

でもこれをみては [-1] って書けるようにしたいなぁと

at が使えるようになっても 最初は [0] なのに最後は .at(-1) になる非対称感のイマイチさを感じてますし かといって 最初や 2 番目の取得でわざわざ at を使おうとは思いません
最後を取得したいってことがあると [-1] で取得できるようにしたいなーと思っては諦めてる状況です
react-hook-form の感想
便利そうかもと思ったらやっぱり違うなと思ったりで ちゃんと使うことはなかったライブラリですが 使われてるコードをいじる機会があったので色々使ってみた感想です
簡単に言うと React ぽくなくなったり 単純なフォームじゃなくなると useEffect だらけで辛くなるので そんなに積極的に使いたいものではないです

react-hook-form を使うと 内部で state を管理してくれて 再レンダリングを防げます
各 input が props ではなくコンテキスト内の form から値を取得するので 親の再レンダリングが不要になり 変更があった input のコンポーネント内だけが再レンダリングされます
表示内容が多いページで memo なしだと入力ごとに再レンダリングされる範囲が広くなり 重たくなったりしますが それを防げます

フォームの制御が特になく 項目が多いだけのフォームだと楽に使えて便利です
しかし 入力値の変化に応じてあちこちを変更する場合は面倒になってきます

useEffect(() => {
const subscription = watch((values, { name, type }) => {
if (name === "foo") {
setValue("bar", values.foo)
} else if (name === "bar") {
setState(values.bar)
}
})
return () => subscription .unsubscribe()
}, [watch])

みたいに callback を指定する watch を使い form の値や state を更新していくことになります
useEffect が必要です

更新された場所の名前はわかりますが 更新前の値はわかりません
変化した内容に応じて処理を変えたいなら自分で前の値を保持する仕組みを作っておかないといけません

また イベントハンドラ的な部分での処理ではなく render 関数中の処理で現在の form の値を使いたいなら 名前指定で watch を使います

const [foo, bar] = watch(["foo", "bar"])

この場合はどれが変わったのかわかりません
また 変わるたびに この watch を使ったコンポーネントが再レンダリングされるので useState に入れているのと変わらなくなります
ものによっては form の値のほとんどを watch に入れることになり useState と対して変わらないってこともあります

watch で取得した値を使うのが hook だと仕方ないですが JSX 部分なら input 以外でも form を参照するようにしてしまうのもありです
input のように controller を使って値を取得すれば watch にする必要がなく コンポーネント全体の再レンダリングを減らせます

input に使う以外の form とは関係ない値でも form に入れてしまい state にせず form で管理するのもありかもです
そうすれば form の処理だけに統一できますし 名前指定の watch を減らせます
それに コンポーネント間の共有でコンテキストのようにも使えます
親コンポーネントで form に関数を入れて 深い部分のコンポーネントでそれを参照します
ただしあくまでライブラリとしては form なのでこの使い方はいいのだろうか?という気はします
想定されたことを外れると将来的なアップデートで面倒な目に合うのは十分考えられます
そういう使い方のものを別に用意したほうがいいのかもという気はしてます

深い部分で watch を使う注意点ですが 使ったコンポーネントだけが再レンダリングされそうに見えてしまいますがそうではないです
React の仕組み的にそういう手段はなく useForm を使ったコンポーネントが再レンダリングされます
その結果 その子孫コンポーネントも再レンダリングされてそうみえるだけです

こういうことをしてみるとわかりやすいです

const Input = ({ control, name }) => {
const { field } = useController({ control, name })
return <input {...field} />
}

const Child = ({ control, watch, name }) => {
const value = watch(name)
console.log("render Child", name)
return (
<div>
<div>input value: {value}</div>
<Input control={control} name={name} />
</div>
)
}

const MemoChild = React.memo(Child)

const Parent = () => {
const { watch, control } = useForm({ defaultValues: { a: "1", b: "2", c: "3" } })
console.log("render Parent")
return (
<div>
<Input control={control} name="a" />
<hr />
<Child control={control} name="b" watch={watch} />
<hr />
<MemoChild control={control} name="c" watch={watch} />
</div>
)
}

Parent で useForm して watch を Child に渡します
Child で watch していて その input を書き換えると 「render Parent」 もログに表示されます
Parent コンポーネントも再レンダリングされています
また Child をメモした版の MemoChild を使うと props の control, name, watch には変更がないため MemoChild コンポーネントは再レンダリングされません
結果 Parent は再レンダリングされるのに MemoChild は再レンダリングされず 「render Child c」 はログに表示されません
画面上でも input の現在の状態を表示しているのですが ここが更新されず初期状態のままです

また props や state を見て ちょっと加工するというだけでも useEffect で setValue が必要になります
普通の React なら render 関数内で計算したり JSX 内に式を入れるくらいで済むことのために useEffect が増えていくのは見通しが悪くなるので不満です

さらに問題になるのはあちこちで同じ値を更新したいときです
useEffect やイベントハンドラの中で setValue することになりますが 特に useEffect だと呼び出し順がわかりづらいです
あちこちのコンポーネントから setValue されるとどういう順で呼び出されて値が変わるのかデバッグしづらく 思い通り動かないことがけっこうありました
通常の React なら useEffect は極力なくせて render 関数内での処理です
親から子の順ですし レンダリングごとにイミュータブルのはずなので 困ることは少ないです

完全に独立した入力項目で 初期値以外は外部から変更されることがないような場合はシンプルに書けて良さそうですが 制御する部分が多いと不満点が目立ちます
やっぱり 素の React で state 管理してる方が見やすくていい気がしますね
React で hook はコンポーネントを分けたい
React の hook は便利なんだけど 作るものが大きく複雑になってくるとコンポーネントも複雑になってくる
やっぱり props として受け取った状態から HTML を作るだけ のコンポーネントのほうがスッキリしていて好き

そうするためにコンポーネントを 2 段にして外側では hook を使ってデータを取得したり加工したりするだけで そのデータを内側コンポーネントの props として渡す
内側コンポーネントは hook は使わず 受け取った props から画面を作る JSX を作って返す

スッキリはするけど長くなるのが面倒
それにこの感じ Redux が流行ってた頃に見かけた作り方に近そう
自分でそういうのは作ってないのであまり詳しくはないけど プレゼンテーションコンポーネント?みたいな名前がついてて ストアと通信してデータを受け取る部分と 副作用を持たない props から JSX を作るだけのコンポーネントに分けていたと思う
hook になってもそれでいいのかな と思ったけど こう分けるのは今では推奨しない みたいなのも見かけた
直接 hook を使えばいいから らしいけど使うとコンポーネントがごちゃごちゃしてくるし テストもしづらくなる

useMemo や useCallback はパフォーマンスの都合なもので なくても動作は変わらないからどっちでもいいけど useEffect は入るとかなり複雑化するから コンポーネント外に持っていきたい
useState は関数の引数として状態と更新関数を受け取るのと同じと聞くこともあるけど リセットしたいときとか テスト時のダミーデータを渡すことを考えたらやっぱり違うかなって思う

WebComponents だと標準の HTML 要素みたいなものとして考えればいいので内部で状態を持って外部通信もしてっていうのはありだと思う
img タグとか画像を取得して表示してるし
でも React の考え方だと 親が子のメソッド呼び出したり 自由に子の状態を取り出したりしない
親で最初から持っていて子に渡すだけ
その考え方だと コンポーネント内で外部通信とか状態を保持とか色々しないほうがいいようにも思う

とりあえず 外部通信したりアプリ固有のロジックでデータを変換したり計算したりみたいのをコンポーネントの中じゃなくて外でやってコンポーネントはその結果を受け取るだけにしたい
Node.js でモジュールをフォルダにまとめるとき
(1) foo.js を読み込んで foo.js が foo/ 以下のモジュールを読み込む
foo.js
foo/file1.js
foo/file2.js

(2) foo/index.js を読み込んで foo/index.js が foo/ 以下のモジュールを読み込む
foo/index.js
foo/file1.js
foo/file2.js

CJS なら (2) が相性がいい
フォルダ名の foo を require すれば自動で index.js を補完してくれる
もとは foo.js 単体であとからモジュール分けするときに foo で require していれば読み込む側のコードは変更しなくていい
それに foo に関係するモジュールはすべて foo フォルダに入ってる

だけど ESM は CJS みたいな補完はなくて完全なファイル名が必要
どのモジュールをロードしているのかわかりやすくはあるけど import に foo/index.js のように書かないといけなくなる
foo.js からモジュール分けすると使うところの変更も必要
だから ESM なら (1) 形式にしたほうがいいのかなと最近思ってたりする
foo.js が foo/ を単純にインポートしてエクスポートするだけならまだいいけどここでも処理を入れると foo フォルダに入っていて欲しくなる
Microsoft のサイトのドキュメントのドメインがまた変わってる
Microsoft のドキュメントのページの URL が変わってることに気づきました
昔の見づらい頃から少し見やすくなった頃に docs というドメインになってました
https://docs.microsoft.com

これがいつの間にか learn に変わっていて docs でアクセスしたときに こっちにリダイレクトされるようになってました
https://learn.microsoft.com

C# やオフィスや fluent ui や WSL など Microsoft 製のもの全般で変わってます
ドキュメントなら docs でいい気もしますし 頻繁に変えるのはやめてほしいですね

過去に保存していた microsoft.com ドメインのリンクを確認していると support.microsoft.com でも一部 learn.microsoft.com にリダイレクトされてるのがありました
Edge で履歴一覧を取得する
標準の履歴画面だとエクスポート機能は無い
検索はあるけど 「http://」 で http のサイト一覧が出ることはなく全件ヒットみたいな動き
単純に URL に入力内容が含まれるかではなく色々内部でやってそう
一応クオートで囲めばそれっぽくはなったけど 生のデータをもとに自分でフィルタ処理をしたい

履歴画面で devtools を開いてみたけど localStorage とかには保存されてない
ソースコードを見ると chrome.send で Edge 側で用意した非公開 API を呼び出してそう

これを無理に使うよりは拡張機能の機能で取得したほうが楽そうなので拡張機能を使う

適当なフォルダに manifest.json を準備

{
"manifest_version": 3,
"name": "popup",
"version": "1.0",
"action": {
"default_popup": "popup.html"
},
"permissions": ["history"]
}

同じフォルダに popup.html もつくる

<h1>popup</h1>

「edge://extensions/」 を開いて 開発者モードを有効にして このフォルダを選択
ボタンが追加されてるのでそれをクリックしてポップアップウィンドウを開く
ポップアップウィンドウで devtools を開く
あとは この devtools のコンソールで

const items = await chrome.history.search({ startTime: 0, maxResults: 10000, text: "" })

items.filter(item => item.url.includes("http://"))
.map(item => `${new Date(item.lastVisitTime).toLocaleString()} ${item.url} ${item.title}`)

みたいな感じで好きにフィルタできる
text は必須なので空文字を入れて絞り込みせずに持ってきて 配列の filter メソッドで処理してる

JavaScript で自由に処理できるので CSV 形式で保存なども簡単にできる
setState の実行タイミング
ライブラリがちゃんと動いてくれなくて 内部挙動調べるために console.log すると実行順がよくわからないことになってて React だけでの動きを調べたメモ
コールバックタイプの setState を使って 同期的に複数回呼び出されていたので それだけの動きを確認するためにこんな感じのページを用意

const App = () => {
const [state, setState] = useState(0)

const onClick = () => {
console.log("ONCLICK")

console.log("pre setState(1)")
setState(prev => {
console.log("setState(1)")
return 1
})

console.log("pre setState(2)")
setState(prev => {
console.log("setState(2)")
return 2
})

console.log("pre setState(3)")
setState(prev => {
console.log("setState(3)")
return 3
})
}

return <button onClick={onClick}>{state}</button>
}

この画面を開いてボタンをクリックしたときのログは

ONCLICK
pre setState(1)
setState(1)
pre setState(2)
pre setState(3)
setState(2)
setState(3)
setState(2) ///
setState(3) ///

/// を書いてる行は React の devtools を入れてると薄く表示されるログ

もう一度ボタンを押すと少し変わって

ONCLICK
pre setState(1)
pre setState(2)
pre setState(3)
setState(1)
setState(2)
setState(3)
setState(1) ///
setState(2) ///
setState(3) ///

最初の 1 を見ると同期的に実行されてるようにみえるけど 2, 3 を見るとコールバックは即時呼び出されずあとになって呼び出されてるのがわかる
薄く表示されるのは Strict モードで 2 回レンダー関数が呼び出されるときに 2 回目の実行中に呼び出されたものだったと思うけど そんなタイミングで実行されてるの?
コンポーネントの関数の最初に console.log を入れて試してみると本当にそんなタイミングで呼び出されてた

二回目のボタンでは setState の 1 より先に 2 や 3 の pre が呼び出されているのも気になる
どっち先かは運次第のランダムかなと思ったけどリロードして試すと再現性がある

薄いところだけ無視すると一回目は 2 と 3 だけ消えて 1 は残るという変な状態になるから console.log デバッグのときは Strict モードを一時的に無効にしたほうがわかりやすいかも

ちなみに原因になったライブラリは props で受け取った値が変わったときに state を更新するために useMemo + setState するとか変なことしてるのが多くて色々辛かった

useMemo(() => {
setState(value_prop)
}, [value_prop])

みたいなの
メモの中で副作用起こさないでほしいし useEffect か ref に保持して if 文でやってほしい
spread operator とプロパティ順
このコードを実行したときの出力(プロパティ順)はどうなるでしょうか?

const obj = { foo: 1, bar: 2, baz: 3 }
const obj2 = { ...obj, bar: 4 }
console.log(JSON.stringify(obj2, null, " "))

答えはこうなります

{
"foo": 1,
"bar": 4,
"baz": 3
}

bar は最後に来ず obj を展開したときの最初の場所です

const obj2 = {}
obj2.foo = 1
obj2.bar = 2
obj2.baz = 3
obj2.bar = 4

と書いた場合と同じです

この仕組み 順番に意味を持たせたいときに良くも悪くもあるんですよね
この場合は bar は常に上書きするもので obj.bar はいらないものです
なのにそれのせいで順番が決まってしまいます

オブジェクトで順番は無いものと考えるべきという主張もありますが 必要になるときもあるものです

上書きするものを除外したオブジェクトを作ればできなくはないものの面倒がありますし 数が多いと書くのが面倒です

const obj = { foo: 1, bar: 2, baz: 3 }
const { bar, ...rest } = obj
const obj2 = { ...rest, bar: 4 }
console.log(JSON.stringify(obj2, null, " "))
{
"foo": 1,
"baz": 3,
"bar": 4
}

関数を用意しておけば書く場所は楽になりますが こんなことしないといけないのかって気持ちになります

const merge = (...objs) => {
const result = {}
for (const obj of objs) {
for (const [k, v] of Object.entries(obj)) {
if (result.hasOwnProperty(k)) {
delete result[k]
}
result[k] = v
}
}
return result
}

const obj = { foo: 1, bar: 2, baz: 3 }
const obj2 = merge(obj, { bar: 4 })
console.log(JSON.stringify(obj2, null, " "))
{
"foo": 1,
"baz": 3,
"bar": 4
}

ただ 今の動きで助かってる部分もあるのでデフォルト挙動が変わってほしいかというと難しいところです
localStorage などに JSON 文字列を保存して それと現状に差分があるかを確認したいときなんかは今のほうが嬉しいです
JSON 文字列比較だとプロパティ順の違いで不一致になるので順番が変わってほしくないです
イミュータブルオブジェクトとして扱いたいときは上に書いたような方法で obj2 を作るのでこれで毎回順番が変わるとチェックが面倒になります
JSX で設定類が書けないのが微妙な気もした
WPF の XAML とか WebComponents の CustomElement を見てると設定のようなものを XML(HTML) のタグで書けます
子要素としてのタグを親要素にとっての設定として扱えます

DOM だと JavaScript で読み取って解析できるので設定と渡す方法として使うこともできます
しかし JSX は React など UI を作るライブラリ用のもので それを使うユーザーのためのものじゃないです
JSX で作られた React の Element などは基本的に中の構造を直接触るべきものじゃないですし それを解析して使うのは一般的でないです
無理に使っても仕様として公開されてるものではないのでアップデートで頻繁に変わる可能性もあります

JSX では props としてオブジェクトなど JavaScript の式を書けるので関数やオブジェクトを渡すのが一般的です
ただこの辺に統一感のなさを感じます
それなら タグ(コンポーネント)や children を含む props も全部オブジェクトとして書くのもありなのかもと思ったり
そう考えると JSX のような記法を導入せず 直接プログラムで JSX 相当のものを書く Flutter の考え方もありなのかもと思ったり
enum 的なものは文字列派だったけど
個別に扱うことがないただの選択肢なら数字だけでもいいかなって思った

昔ながら感ある方法

const FOO = 1
const BAR = 2

export const foo_bar_options = {
[FOO]: "foo text",
[BAR]: "bar text"
}

選択肢それぞれに名前をつけて数値を対応させる
key-value 形式で数値と表示用ラベルを保持する

foo text のところは日本語だったりで画面に表示する用のテキスト
簡単なツールなどだと選択肢も簡単で英単語で表すのも簡単

だけど特殊な固有名詞とか選択肢を表す言葉が複雑なものになってくると 名前を考えるのも面倒
そもそも一般的な英語にしづらい固有名詞とかは日本語で書けばいいとも思ってるけど 他とのバランスとかで急に日本語を混ぜたくないとかもあって 英語にしたいときもある

こういうので数が多いと嫌になる
思ったのが 選択肢が特別な意味のないただの選択肢で これを選んだ場合に何かがあるとかが無いなら 最初から名前を用意しなければ名前を考える必要はないってこと
全体としてこれが何なのかを表してる foo_bar_options に当たる名前は必要だけど ここはそんなに難しいことがなくて面倒なのは選択肢なのでこれで十分

export const foo_bar_options = {
1: "foo text",
2: "bar text",
}

=== "1" は 1 が何かわからないのでしたくないけど それをすることがないなら名前もいらない

React で select を作るときもこういう感じでいい

import { foo_bar_options } from "./constants.js"

const Select = ({ options }) => {
const opts = Object.entries(options)
return (
<select defaultValue={opts[0][0]}>
{opts.map(([k, v]) => (
<option key={k} value={k}>{v}</option>
))}
</select>
)
}

const Component = () => {
return (
<div>
<Select options={foo_bar_options} />
</div>
)
}
引数は全部オブジェクトにまとめてもいいかも
fn(a, b, c)

みたいに書くよりも

fn({ a, b, c })

プロパティ名を記述するので 数字や真偽値だけのときでも渡すものがなにかわかる
then の引数とか return とか 1 つしか値を渡せなくてオブジェクトにまとめることは多いし React だと props は一つの引数にオブジェクトで渡す扱い
引数として渡すのが数値や真偽値型などの値が 1 つだけとかでないなら基本は全部オブジェクトでもいい気がする

特に最近思ったのは引数として受け取ったのを他の関数にほぼそのまま渡すとき
一部だけの置き換えが地味に面倒
例えば 2 番目 (0 始まりで 1 番目) を書き換えて別の関数を呼ぶ場合

const fn1 = (...recv_args) => {
const send_args = recv_args.slice()
send_args[1] = { foo: "bar" }
fn(...send_args)
}

const fn2 = (...recv_args) => {
const send_args = recv_args.map((x, i) => i === 1 ? { foo: "bar" } : x)
fn(...send_args)
}

オブジェクトなら簡単にもとの値を変更せず上書きできる

const fn3 = (args) => {
fn({ ...args, arg1: { foo: "bar" } })
}

似たやり方を配列でやろうとしても オブジェクトを配列として展開はできないので Object.assign で指定のインデックスの値を書き換えになる

const fn4 = (...args) => {
fn(...Object.assign([...args], { 1: { foo: "bar" } }))
}

見やすさわかりやすさだと fn1 や fn2 のほうがマシな気がする
変数のリネーム機能が欲しい
変数に入る値が複数のパターンの可能性があることってけっこうある
if 文でどっちかチェックしてそれぞれのパターンの処理をする
関数なら実行して返ってきた値 それ以外ならその値そのものを使うとか
Promise なら await した値 それ以外ならその値そのものを使うとか
省略可能な引数で省略された場合にずれて入ってるとか

そういうとき名前を曖昧なものにしたり 本来の場合の想定したものを使ってたりする
チェック後もそのままの名前で使ってるケースを結構見るけど分かりづらい
確定したならその名前に変えてほしい

けどリネームのような機能がある言語ってほぼ見ない
新しく変数を作ってそっちに同じものを代入すれば似たことはできるけどリネームではなくコピー
リネームの意図なのかわかりづらい
後で比較するためにバックアップを残してるようにも見えてしまう
それに前の変数名も生きてるのでそのまま使えてしまうし
そんなだとあえて変数を追加するまでもない気もする
Rust では新しい変数に代入したら古い変数は使えなくなる仕組みがある
普通の代入とは別でこういう機能が多くの言語でも使えてほしい

構文はおいておくとしてこういう感じ

const fn = (x_or_y) => {
if (isX(x)) {
rename x_or_y to x
console.log(x)
} else {
rename x_or_y to y
console.log(y)
}
}

関数を呼び出して引数で名前を変えればいいけど ここまでするほどでもない気がする

const processX = (x) => {
console.log(x)
}

const processY = (y) => {
console.log(y)
}

const fn = (x_or_y) => {
if (isX(x)) {
processX(x_or_y)
} else {
processY(x_or_y)
}
}
Fastify 4 の変更が思ってたのと違った
少し前に Fastify の 4 が出たってニュースを見かけて簡単な変更点紹介で ルートのハンドラー内でのレスポンスの返し方が統一されたみたいのを聞いてました
Fastify のルートハンドラーではコールバック関数と async 関数どっちでも使えるようになってることもあって 以前使ったときは罠のような仕様があって不便に感じたので あれが改善されたんだなーと思ってました

一般的なレスポンスの返し方はこの 2 パターンです

fastify.get("/foo", (request, reply) => {
reply.send({ hello: "world" })
})

fastify.get("/bar", async (request, reply) => {
return { hello: "world" }
})

通常の関数ならコールバック関数で async 関数なら return でレスポンスを返します
async 関数でできるならと通常の関数でも return でレスポンスを返したくなります
しかしこれは無視されるようで reply を使ったコールバック関数を呼び出さないとレスポンスは返されずブラウザはずっとロード中になります
他フレームワークだとだいたいデフォルトのレスポンスを返してくれて Node.js を生で使ったときみたいにレスポンスを返さず放置はなかったのでこれが Fastify の不満点の一つでした

return 値をレスポンスとして捉えてしまうとあとから reply 関数を呼び出す場合に対応できなくなるので return は無視する仕組みなんだと理解しましたが それでも async 関数の場合は return できるので使いづらく感じます
それがあってから全部 async 関数にしようとしてますが それでも async 関数になってなかったということはけっこうあります

そういう問題があって きっと 4 での変更点はこれが改善されたんだろうなと思って試してみました

fastify.get("/", (request, reply) => {
return { hello: "world" }
})

でレスポンスが返ってきました
改善されてるーと喜んで記事にでも書こうかと思って ついでに 4 の変更点をまとめた公式ブログの発表を軽く見ていると……これのことじゃなさそう
async 関数のときに あとからコールバック関数を使うなら reply を return しないといけないとか書いてる

じゃあもっと前から直ってたの?
と思って 3 系をいくつか試したところ どれも return でレスポンスを返せていました
そんな前から対応されてたんだ
まぁ あとから reply を呼び出すつもりなら return が undefined になりそうだし 十分判断はできるのかな
やっぱり気になったので Github で履歴見てみると 3.0 の更新で変更されてました

その他の 4 での変更点はあまり興味ない部分でしたし 今回はメジャーアップデートの割にそんなに大きく変わってないようです
React で props の変更時にリセットしたいとき
コンポーネントが内部で state を持っているときに親から受け取る props に応じてリセットしたいことがある
親が key を変えることでコンポーネントが作り直されるけど 使う側で追加でやることを増やしたくない
忘れて思い通り動かないケースとか出てきそう
なので内部で制御するように useEffect を使って props の変化を検出して setState で更新ってやってることが多い

新しい作りかけドキュメントの方では useEffect をこの目的で使うことを推奨していなくて key を使うほうがいいと言ってる
ドキュメントにまで書かれるならその方向にしようかなと思うけど やっぱり面倒
間にコンポーネントを挟んで 指定の props の変化時に key の制御は自動でやるようにする

const withReset = (Component, names) => {
const diff = (a, b) => {
return names.some(name => a[name] !== b[name])
}
return (props) => {
const ref = useRef({ key: 0, props: {} })
if (diff(ref.current.props, props)) {
ref.current = { key: ref.current.key + 1, props }
}
return <Component key={ref.current.key} {...props} />
}
}

・使用例
Component のボタンを押すとカウントアップ
foo の入力が変わったときのみ key が新しくなってカウントがリセットされる
bar, baz を変更してもカウントは維持される

const Component = withReset(({ foo, bar, baz }) => {
const [num, up] = useReducer(s => s + 1, 1)
return (
<div>
<div><button onClick={up}>{num}</button></div>
<div>foo: {foo}</div>
<div>bar: {bar}</div>
<div>baz: {baz}</div>
</div>
)
}, ["foo"])

const App = () => {
const [foo, setFoo] = useReducer((_, eve) => eve.target.value, "foo")
const [bar, setBar] = useReducer((_, eve) => eve.target.value, "bar")
const [baz, setBaz] = useReducer((_, eve) => eve.target.value, "baz")
return (
<div>
<div>foo: <input value={foo} onChange={setFoo} /></div>
<div>bar: <input value={bar} onChange={setBar} /></div>
<div>baz: <input value={baz} onChange={setBaz} /></div>
<Component foo={foo} bar={bar} baz={baz} />
</div>
)
}

作ってみて 以前似たようなもの作った気がすると思って探すとあった

以前は useMemo を使ってたらしい
新規の key 作成と 変化ありの検出をまとめてできてるし良さそう
今のよりスッキリしてる
以前の方が良かったって成長してないどころか劣ってる……

でも useMemo って React がメモリの都合で勝手にメモを忘れて再計算するかもしれないって言ってるから安全ではないかも
リセット目的じゃない再レンダリングで state がクリアされてしまうと困るし

そう考えるとこれ以外でも変更検出のために useMemo 使ってるところ全部ダメそう
変更無いのに変更ありってみなされるケースも無いとはいえない

変更の有無は標準のフックで用意してほしい
React 18 でクリア処理は要素を消すだけの場合の useEffect
前記事で書いた React 18 からは useEffect が 2 回呼び出される問題
サードパーティライブラリだとアンマウント時に行うクリア処理が提供されず DOM の要素を消せばいいだけって場合に困りそうだったけど useEffect 内で手動で要素を追加してそれを消せば問題なさそう

useEffect(() => {
const child = document.createElement("div")
div_ref.current.append(child)
library.init(child)
return () => {
child.remove()
}
}, [])