This does not work:
<!-- wtf.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title></title>
<script type="module" src="./wtf.js"></script>
</head>
<body>
<script>
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
</script>
</body>
</html>
// wtf.js
customElements.define('my-element', class extends HTMLElement {
constructor() {
super()
}
callMe() {
window.alert('I am called!')
}
})
Firefox throws me a nasty exception on line myElement.callMe()
. Apparently, "myElement.callMe is not a function
".
I am confused why is that so? To my understanding, as soon as I type const myElement = document.createElement('my-element')
, I am receiving an object whose type is not a generic HTMLElement
but an object of my class I wrote that extends HTMLElement
! And this class exposes callMe
.
I have confirmed that my use of modules seems to be the culprit here. This code works as expected:
<!-- wtf.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super()
}
callMe() {
window.alert('I am called!')
}
})
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
</script>
</body>
</html>
Yes I know that things defined in a module are scoped to this module. But here it does not even seem (to me) to be a scoping issue. For example, if I do inside a module something like that:
function callMe() {/*blah blah */}
window.callMe = callMe
then I will be able to use callMe
outside of the module anyway, because the module exposed this function through other means than export
(this time through assigning it to the global window
object).
The same, to my understanding, should be happening in my use case. Even though I define callMe
in a class scoped to the module, this class method should be accessible outside of the module by the virtue of it being a property of an object of this class that is exposed by calling document.createElement('my-element')
. Yet manifestly, this does not happen.
This is really weird to me. It almost seems as if the module was enforcing its scoping by tangling with types unrelated functions return(!!) - so in this case, it is just as if the module magically forces document.createElement
to cast the object it returns up in the inheritance hierarchy (to HTMLElement
)?!?! This is mind-blowing to me.
Could someone please clear my confusion?
(And if I define a custom element inside a module, how may I expose its API outside of this module?)
The problem is that a <script type="module">
implicitly has a defer
attribute, so it doesn't run immediately.
Even though I define callMe in a class scoped to the module, this class method should be accessible outside of the module
Yes, it is. The problem is just that it is defined asynchronously :-) To use stuff from a module, you should explicitly import
that module to declare the dependency, which makes sure it is evaluated in the right order. It would also work if your global script was defer
red somehow.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title></title>
<script type="module" src="./wtf.js"></script>
</head>
<body>
<script type="module">
import './wtf.js';
// ^^^^^^^^^^^^^^^^^^
const myElement = document.createElement('my-element')
document.body.appendChild(myElement)
myElement.callMe()
</script>
</body>
</html>