drop の層と、片づけの約束

drop が窓に置いたものは、drop を外したとき、その窓のまま元に戻る。 それを drop ごとに手で書かないための、置きかたの約束。

台帳

content hook の ctx.onDestroy(fn) は、actor が外れるとき(drop を外す・置き換える・窓が閉じる)に呼ばれる列。 置いたものは、置くのと同じ息で、戻しかたをこの列に積む。 列は置いた順の逆に走る。

その動詞が ctx.io(_shared/io.ts):

動詞 置くもの 戻しかた
io.place(node, { parent | before | after }) DOM の node remove()
io.style(doc, css) <style> を head に remove()
io.listen(target, type, fn) event listener removeEventListener
io.pref(name, fn) pref observer removeObserver
io.defer(fn) それ以外 fn そのもの

view は mount(ctx.io, view, { parent | before | after, tag?, id? })(_shared/ui.ts): host 要素を置いて、その中に preact で描く。戻すときは render(null) → host の remove() の順。

置きかた

規則は一つ。触るものは io/、決めるものは .tsubaki(か ops/)、あとは平らに。

src/<actor>/
  main.tsubaki   決めるもの。窓も pref も知らない(純粋)
  actor.ts       入口: meta / parent / content。殻で足りるなら要らない(GUIDE の 3.5)
  io/            窓に触るところ。ここだけが増える
  view.tsx       描くところ。育ったら ui/ に割る

前は六つ(types/ data/ ops/ io/ state/ ui/)を並べていた。数えてみたら、五つはどの drop でも 1 file しか入らなかった(webpanel は 11 files で io/ だけが 3、rename-tab は 9 files で io/ が 4)。 複数あるのは副作用の種類だけで、types/data/ は分類ではなく、どこかの file に付いた注釈だった。 state/ は「signal をどこに置くか」で、Tsubaki の殻が持つようになって消えた。

だから、名前で覚えるのはやめて、規則で覚える:

drops/newtab が一枚(actor.ts だけ)、drops/hello-tsubaki が JS 無し(ops/main.tsubaki だけ)、 drops/webpaneldrops/rename-tab は前の六層のまま(動いているので、次に触るときに寄せる)。

増やしかたは folder ではない。 drop の力を増やすのに io/ へ JS を書くと、drop ごとに一回ずつ 書かれて、審査する人が毎回ぜんぶ読むことになる。増やすなら std の語彙のほう(SetPref / OpenURL / Logdocs/GUIDE.md の 3.5)。一回書いて一回審査されて、以後どの drop も使えて、 入れる人には「この drop に何ができるか」の一覧として見える。io/ は、まだ語彙になっていないものの置き場。

設定(pref)

設定は about:config の pref を一本ずつ。まとめて一本の JSON にしない (user.js で一つだけ上書きできない、他の mod から触れない、項目を足すと既に答えられている設定ごと壊れる。 cf. f3liz-casa/noraneko#127)。まとめて書ける嬉しさ — 全部が一枚に並ぶ・型が付く・補完が出る — は schema が持つ:

// data/prefs.ts   schema は定数。ここでは作らない
export const SCHEMA = { globalWidth: pref.int(400), positionStart: pref.bool(false) };
// actor.ts        窓が来てから作る(module 直下で browser に触ると build が転ぶ)
const prefs = definePrefs("noraneko.webpanel", SCHEMA);
prefs.globalWidth.value          // 読む。既定は default branch に置かれるので about:config に見える
prefs.globalWidth.set(420);      // 書く。今の値を読む必要はない
watchPrefs(ctx.io, prefs);       // 外から変わったら signal も動く(片づけは台帳に載る)

ops/ は Tsubaki で書いてもよい(ops/*.tsubaki[deps]std)。drops/webpanel がそう (actor.ts ごと Tsubaki にもできる: docs/GUIDE.md の 3.5、drops/hello-tsubaki):

中に何が入るか

xpi の content.js には preact が npm の src から 同梱される(dist は minify 済で「読める形」の検査に引っかかる)。 一つの drop で 40KB ほど。source/ には src/<actor>/ の木がそのまま入る。 親の actor.mjs には parent だけが残る(module 直下は純粋、という contract で、副作用だけの import は落とす)。

依存関係と std

drop は他の drop(library drop)に依存できる。drop.toml に:

[deps]
std = "<std の uuid>"

compat と台帳(Julia の絵)

[deps]
std = "<uuid>"

[compat]
std = "1"          # 1.x(caret。"1.2.3" は [1.2.3, 2.0.0)、"0.2.3" は [0.2.3, 0.3.0))
# std = "~1.2"     # [1.2.0, 1.3.0)
# std = "1.2 - 1.5"  # 上を含む範囲
# std = "=1.2.3"   # その版だけ。"," で並べれば union