HTML Imports load order quirks?

349 views Asked by At

Hey there!

I’m having a hard time understanding a specific behaviour with HTML Imports. I’m just swapping one line within the imports and get a totally different output.

So here is what I got…

index.html

<!DOCTYPE html>
<html>
<head>
    <link rel="import" href="./element-a.html">
    <link rel="import" href="./element-b.html">
    <link rel="import" href="./element-c.html">
</head>
<body>
    <element-a></element-a>
</body>
</html>

element-a.html

<template>
    <element-b>
        <element-c>Hi!</element-c>
    </element-b>
</template>

<script>
console.log('registering a');

class ElementA extends HTMLElement {
    constructor() {
        super();

        console.log('upgrading a');

        const $template = this.constructor.ownerDocument.querySelector('template');
        const $clone = $template.content.cloneNode(true);

        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild($clone);

        const $c = this.shadowRoot.querySelector('element-c');
        const isDefined = () => console[$c.say ? 'debug' : 'error'](`say() is ${$c.say ? '': 'un'}defined`)

        isDefined();

        const undefined = this.shadowRoot.querySelectorAll(':not(:defined)');
        const promises = [...undefined].map(el => customElements.whenDefined(el.localName));

        console.log('undefined: ', undefined);

        Promise.all(promises).then(() => {
            console.log('ready');
            isDefined();
        });
    }
}

ElementA.ownerDocument = document.currentScript.ownerDocument;

customElements.define('element-a', ElementA);
</script>

element-b.html

<template>
    <slot></slot>
</template>

<script>
console.log('registering b');

class ElementB extends HTMLElement {
    constructor() {
        super();

        console.log('upgrading b');

        const $template = this.constructor.ownerDocument.querySelector('template');
        const $clone = $template.content.cloneNode(true);

        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild($clone);
    }
}

ElementB.ownerDocument = document.currentScript.ownerDocument;

customElements.define('element-b', ElementB);
</script>

element-c.html

<template>
    <slot></slot>
</template>

<script>
console.log('registering c');

class ElementC extends HTMLElement {
    constructor() {
        super();

        console.log('upgrading c');

        const $template = this.constructor.ownerDocument.querySelector('template');
        const $clone = $template.content.cloneNode(true);

        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild($clone);
    }

    say(words) {
        console.log(words);
    }
}

ElementC.ownerDocument = document.currentScript.ownerDocument;

customElements.define('element-c', ElementC);
</script>

I also created a pen. Now what’s confusing me: If I import element-a first I get this output:

registering a
upgrading a
say() is undefined
undefined: (2) [element-b, element-c]
registering b
upgrading b
registering c
upgrading c
ready
say() is defined

But if I import it at last, I get a totally different output and order of registering and upgrading.

registering b
registering c
registering a
upgrading a
upgrading b
upgrading c
say() is defined
undefined: []
ready
say() is defined

Why is that? I kind of expect the last output to be the one that always happens. Has it something to do with the slotting/Shadow DOM?

1

There are 1 answers

1
jorgecasar On

You should import element-a's dependencies inside element-a.html. HTMLImport imports and registers the component, that's why you get some undefined because register the component before you import other files (imports are async). Importing the dependencies inside the element the browser wait until all dependencies are ready before register the component.

You can read more about Managing dependencies and sub-imports