前は JavaScript だけ扱いを特別にしたけど HTML ファイル中にあったほうがいいかなと思ったので HTML ファイルに全部書く
使う側は一緒で foo-bar.js を読み込んで foo-bar タグを書いておく
[page.html]
<script type="module" src="foo-bar.js"></script>
<foo-bar></foo-bar>
[foo-bar.js]
import { importComponent } from "./base.js"
importComponent("foo-bar.html").then(component => {
customElements.define("foo-bar", component)
})
コンポーネントを import して customElements.define で定義
[base.js]
export const importComponent = async (htmlpath) => {
const res = await fetch(htmlpath)
const html = await res.text()
const doc = new DOMParser().parseFromString(html, "text/html")
const fragment = document.createDocumentFragment()
const scripts = [...doc.querySelectorAll("script")]
scripts.forEach(x => x.remove())
fragment.append(...doc.head.childNodes, ...doc.body.childNodes)
const module = {}
class Base extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" })
this.shadowRoot.append(this.constructor.fragment.cloneNode(true))
this.elements = Object.fromEntries(
Array.from(
this.shadowRoot.querySelectorAll("[id]"),
elem => [elem.id, elem]
)
)
}
}
Base.fragment = fragment
for(const script of scripts) {
const source = script.textContent
const fn = Function("module", "Base", source)
fn(module, Base)
}
return module.exports
}
importComponent は HTML を fetch してパースしていろいろ
script を含んだコンポーネントの HTML
[foo-bar.html]
<style>
.foo { font-size: 20px; }
.bar { color: red; }
.active { font-weight: bold; }
</style>
<div>
<div class="foo">FOO</div>
<div class="bar">BAR</div>
<button id="btn">Button</button>
</div>
<script>
module.exports = class extends Base {
connectedCallback() {
this.elements.btn.addEventListener("click", (eve) => {
eve.target.classList.toggle("active")
})
}
}
</script>
script タグ中のエクスポートは Node.js 形式で module.exports に代入
Base を継承したクラスをエクスポートする