CustomElements / WebComponents live reload
Tags: javascript, customelements, microfrontend, experimental
Published: 2020-02-13 10:45:09

## Premise

This is an experimental project, the described case will not happen in a "normal" custom element use.

## Background

I'm creating an orchestrator for MicroFrontend called AVE to learn MicroFrontends and how they work.

This application has the goal to create and manage different applications/custom elements for Microfrontend development:

[Watch on loom]( https://www.loom.com/share/2a1dca41a40a4265bb3b36dfa201a012 )

As you can see in the video, when I create a custom element and save it, the render on the right side of the application re-define the custom elements.

That's cool right? But how it is done? And why?

## The problem

According to [html spec](https://html.spec.whatwg.org/multipage/custom-elements.html#dom-window-customelements) you can only define and get a custom element, you cannot update it at runtime.

If you try to define twice the same custom element you'll get the following error:

![error duplicated definition](https://dev-to-uploads.s3.amazonaws.com/i/0oplak8a13n4io7hvvwt.jpg)

So you cannot define the same custom element twice. You need to reload the entire page to get the updated definition.

## But I don't want (in my project) to reload the entire page

Because I need to edit and update the custom element at runtime, I need to find a solution to edit and update the target custom element.

## Solution - using a nested document with iframe

The solution is to put the element definition into an incapsulated **iframe**:

```typescript
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const nestedWindow = iframe.contentWindow;
const nestedDocument = this.nestedWindow.document;

if (!nestedWindow.customElements.get('customElementName')) {
  const s = nestedDocument.createElement("script");
  s.type = "text/javascript";
  s.innerHTML = "myCustomElementDefinitionScript"
  nestedDocument.body.appendChild(s);
  nestedDocument.createElement('customElementName');
}
```

This script will create an iframe and encapsulate into it a script with the custom element class definition (i.e): 

**myCustomElementDefinitionScript**
```typescript
class CustomElementClass extends HTMLElement {
  [...]
}
window.customElements.define('customElementName', CustomElementClass);
```

Ok fine, our custom component will be rendered once:


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/46af73rlf8lwbhbt71g0.jpg)

### Upload the iframe at runtime

How can we re-define the custom element definition when updated?

We can destroy the iframe, create a new one and proceed with a new definition:

```typescript
// clear our wrapper
document.body.innerHTML = '';
// and re-create the iframe
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const nestedWindow = iframe.contentWindow;
const nestedDocument = this.nestedWindow.document;

if (!nestedWindow.customElements.get('customElementName')) {
  const s = nestedDocument.createElement("script");
  s.type = "text/javascript";
  s.innerHTML = "myCustomElementDefinitionScript"
  nestedDocument.body.appendChild(s);
  nestedDocument.createElement('customElementName');
}
```

And that's all! The custom element is updated and the iframe re-created!

## Notes

* document.body is an example wrapper, you can use any html element.
* putting a custom element into an iframe will remove any wrapper-defined stylesheet (like using a shadowDom), so you will not see the result as using the same custom element in the document directly
* this is just an experiment, in a normal use case you might reload the main page instead of using live update, so it will upload the modified script.

## Resources

* [html spec](https://html.spec.whatwg.org/multipage/custom-elements.html)
* [mdn webcomponents](https://developer.mozilla.org/en-US/docs/Web/Web_Components)

## Reviewers

Thanks to @pzzdvd for reviewing my post.