以下の内容はhttps://let.blog.jp/tag/CustomElementより取得しました。


未定義要素が接続されたときに起きるイベントがほしい
ページのロード時に全部の Custom Element の定義を読み込むのはムダが多い
めったに開かないページでしか使わないものまでロードする必要ないし

基本は Custom Element の定義ごとにファイルを分けてモジュール化してるので モジュールごとに内部で使う Custom Element のモジュールを import するのが一般的だと思う
この方法でも よく使うモジュールを毎回 import したり Shadow DOM 内の HTML を変えるたびに import も見直しが必要で面倒

未定義要素が Document や ShadowRoot に接続されたときにイベントが起きれば そのイベントのリスナでモジュールを読み込んで customElements.define を呼び出しできるのに
PHP の autoload 風で良さそう

ただ 今のところはそんなイベントは無いみたい
自分でやるなら MutationObserver 使って全部の Node の追加を監視して 「-」 を含むタグ名の要素があれば定義済みかチェックすることになりそう
DOM の更新が多いページは少し重そうなのと Shadow DOM 内を検知できないのが問題点

この機能用に HTMLElement を継承したクラスを作って 全部の Custom Element がこのクラスを継承するように作れば 共通部分で MutationObserver の監視設定を入れて Shadow DOM 内も要素の追加を監視できるけど ライブラリを使うとか既存のコンポーネントを使うとかもあってその制限は結構不便
attachShadow を上書きしてしまうのが簡単
とりあえずこんなので動きそう

const mo = new MutationObserver((mutations) => {
const added_nodes = mutations.flatMap((m) => [...m.addedNodes])
const collect = (node) => {
for (const child of node.childNodes) {
added_nodes.push(child)
collect(child)
}
}
added_nodes.slice().forEach(collect)

const undefined_tags = added_nodes
.map((n) => n.localName)
.filter((t) => t && t.includes("-") && !customElements.get(t))

for (const tag of new Set(undefined_tags)) {
// load module and define custom element
// (ex) import(`/path/to/components/${tag}.js`)
console.log(tag)
}
})
const mo_conf = { childList: true, subtree: true }
mo.observe(document.body, mo_conf)

const attachShadow = Element.prototype.attachShadow
Element.prototype.attachShadow = function (...args) {
const shadow = attachShadow.apply(this, args)
mo.observe(shadow, mo_conf)
return shadow
}

import するところは仮の console.log にしてる
実際のロード処理は

import(`/components/${tag}.js`)

みたいに 1 ファイルを動的 import して import するモジュール内で customElements.define まで行う
Custom Element を定義するモジュールがすべて同じフォルダにない場合は importmaps でそれぞれの場所を設定で良いかも
CustomElement と constructor
CustomElement にできない 「-」 なしの要素を作ると HTMLUnknownElement

const elem1 = document.createElement("foo")
elem1.constructor.name
// HTMLUnknownElement

「-」 ありだと 未定義でも HTMLUnknownElement にはならず HTMLElement

const elem2 = document.createElement("foo-bar")
elem2.constructor.name
// HTMLElement

作った後に CustomElement を定義しても変わらない

class FooBarElement extends HTMLElement {}
customElements.define("foo-bar", FooBarElement)
elem2.constructor.name
// "HTMLElement"

定義後に作った要素は 定義時に使った class のインスタンス

const elem3 = document.createElement("foo-bar")
elem3.constructor.name
// "FooBarElement"

instanceof しても

elem2 instanceof FooBarElement
// false

elem3 instanceof FooBarElement
// true

body などに append して document のツリーと接続するタイミングでアップグレードされる

document.body.append(elem2)

elem2.constructor.name
// "FooBarElement"

elem2 instanceof FooBarElement
// true
CustomElements の初期化はいつすべき?
CustomElement を初期化するタイミング
普通のクラスだと初期化 constructor
だけど Custom Element の場合は connectedCallback が推奨されてる
このメソッドは document のツリーにアタッチされたときに実行される

constructor で初期化すると作っただけで使われない場合や メソッド呼び出すだけでに無駄が多い
div を作って textContent に代入して innerHTML を取り出したり
template を作って innerHTML に代入して content をクローンしたり
form を作って JavaScript から直接 POST したり
HTMLElement は document のツリーにアタッチするとは限らない
表示が必要になって初めて shadow dom の中を作ると無駄が少ない

でもそうすると メソッド実行やプロパティ代入時の setter で困ることがある
初期化されてなくて shadow dom の中がないのでアクセスしてエラーになる
ツリーにアタッチしないと使えないのは不便だし メソッド呼び出し時に初期化することになる
でも全部のメソッドや setter に未初期化を判断して初期化するコードをいれるのは面倒すぎ
結局初期化するし 最初から constructor 呼び出し時に全部作ってしまったほうがいいと思う

connectedCallback のほうがいいケースもある
未初期化でも使えるメソッドがあって それを使うためだけに要素を作ることが多そうな Custom Element
form みたいな標準機能ならともかく ユーザレベルで作れるものでそういうのはほとんどなさそう
単にメソッドに便利機能があるからくらいなら それは別モジュールに切り出しておくべきな気がする



以上の内容はhttps://let.blog.jp/tag/CustomElementより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14