Skip to content

Instantly share code, notes, and snippets.

@treshugart
Last active May 6, 2024 05:01
Show Gist options
  • Save treshugart/847155b50464d01283698618c6ea570c to your computer and use it in GitHub Desktop.
Save treshugart/847155b50464d01283698618c6ea570c to your computer and use it in GitHub Desktop.
Declaratively import declarative custom elements built on SkateJS.

This allows you to define a custom element declaratively in HTML using lit-html and SkateJS. Ideally you wouldn't need either, but they exemplify what a platform-like solution could look like that gives you:

  • One-way attribute to property reflection.
  • Semantic props (i.e. boolean)
  • Functional rendering pipeline, like a vDOM (lit-html)
import { Component, props } from 'skatejs/esnext';
import { html, render } from 'lit-html';
window.__declarativeCustomElementGlobals = {
Component, html, props, render
};
class CustomElement extends HTMLElement {
connectedCallback () {
setTimeout(() => {
const name = this.getAttribute('name');
const props = this.getAttribute('props').split(' ').map(p => p.split(':'));
const script = document.createElement('script');
const template = this.innerHTML;
this.innerHTML = `
<style>
:host {
display: none;
}
</style>
`;
script.innerHTML = `
const { Component, html, props, render } = __declarativeCustomElementGlobals;
class CustomElement extends Component {
propsChangedCallback () {
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
}
render(
html\`${template}\`,
this.shadowRoot
);
}
}
CustomElement.props = {
${props.reduce((prev, curr) => {
return prev + `${curr[0]}: props.${curr[1] || 'string'}`;
}, '')}
};
customElements.define('${name}', CustomElement);
`;
document.head.appendChild(script);
});
}
}
customElements.define('custom-element', CustomElement);
<custom-element props="yell:boolean">
Hello, ${this.yell
? html`<strong><slot></slot></strong>`
: html`<slot></slot>`}!
</custom-element>
// This is mocking what it would do because
// you can't fetch local files in WebpackBin.
// See the hello.html file for the declarative
// custom element source.
const imports = {
'./hello.html':
'<custom-element props="yell:boolean">' +
'Hello, ${this.yell' +
'? html`<strong><slot></slot></strong>`' +
': html`<slot></slot>`}!' +
'</custom-element>'
};
function mockFetch(href) {
return Promise.resolve(imports[href]);
}
class HtmlImport extends HTMLElement {
connectedCallback () {
const as = this.getAttribute('as');
const href = this.getAttribute('href');
mockFetch(href).then(html => {
const div = document.createElement('div');
div.innerHTML = html;
div.firstElementChild.setAttribute('name', as);
document.body.appendChild(div);
});
}
}
customElements.define('html-import', HtmlImport);
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<html-import href="./hello.html" as="x-hello"></html-import>
</head>
<body>
<script src="index.js"></script>
<x-hello yell>World</x-hello>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment