TypeScript inheritance of web components breaks while importing modules from mapped source files from dist

108 Views Asked by At

I am trying to figure out how to bundle my JS generated code from TS files and I am facing some issues. I have two components, where one is extending the another.

Let's keep it simple.

tsconfig.json

{
  "compilerOptions": {
    "target": "es2017",
    "moduleResolution": "node",
    "sourceMap": true,
    "baseUrl": "./public",
    "outDir": "dist",
        "paths": {
          "@js/*": ["../dist/public/javascripts/*"]
        }
  }
}

Just parent component:

const template = document.createElement('template');
template.innerHTML =
    `
    <style>
    </style>
    <h1>Parent</h1>
    `

export class Parent extends HTMLElement {

    constructor() {
        super();

        this.attachShadow({mode: 'open'});
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }


}

window.customElements.define('my-parent', Parent)

and the child component:

import {Parent} from '@js/Parent.js' <- does not work
import {Parent} from './Parent.js' <- works fine

const template = document.createElement('template');
template.innerHTML =
    `
    <style>
    </style>
    <h1>Child</h1>
    `

class Child extends Parent{

    formId = 'adventureNpcs';
    constructor() {
        super();

        this.attachShadow({mode: 'open'});
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

}

window.customElements.define('my-child', Child)

It seems that after importing module from mapped file (generated source from dist) can't see the inheritance between them anymore, which is resulting with:

Property 'attachShadow' does not exist on type 'Child'.

Why the child is recognized while importing the module in this way:

import {Parent} from './Parent.js'

And this does not work?

import {Parent} from '@js/Parent.js'

Am I missing something? Doing anything wrong?

1

There are 1 best solutions below

2
Danny '365CSI' Engelman On

Instead of relying on filelocations and synchronous loading, you can rely on the CustomElementsRegistry

You could refactor it to:

  • no export needed, because <my-parent> is defined (or not yet) in the CustomElementsRegisrty

  • <my-child> will be defined When <my-parent> has been defined

  • you can still keep the separate files for components, but can load them in any order you want

  • PS using a template to set innerHTML is bloated code and will only have a performance gain for thousand of elements. All old blogs showing this should be burned. Same goes for all not showing super() can be chained.

customElements.whenDefined('my-parent').then(() => {
  customElements.define('my-child', class extends customElements.get('my-parent') {
   connectedCallback(){
    this.innerHTML += ` inside <span part="title">${this.nodeName}</span>`;
   }
  })
});

document.body.onclick = () => {
  customElements.define('my-parent', class extends HTMLElement {
    constructor() {
      super().attachShadow({mode:'open'})
             .innerHTML = `<h5 part="title">${this.nodeName}</h5><slot></slot>`
    }
  })
}
body{ font: 16px Arial } 

*:not(:defined){
  background:red;
}

*::part(title){
  /* part does not style content in NESTED shadowRoots */
  background:green;
  color:gold;
}
<h2>Cick me to create &lt;my-parent></h2>

<my-parent><my-child>FOO</my-child></my-parent>