I read an awesome article I and wanted to share it with the community.
Implementing single-file Web Components
The idea is to be able to load and run a remote single file component like the one below.
This one is accessible from the following url :
https://blog.comandeer.pl/assets/jednoplikowe-komponenty/HelloWorld.wc
Notice the extension.
<template>
<div class="hello">
<p>Hello, world! My name is <slot></slot>.</p>
</div>
</template>
<style>
div {
background: red;
border-radius: 30px;
padding: 20px;
font-size: 20px;
text-align: center;
width: 300px;
margin: 0 auto;
}
</style>
<script>
export default {
name: 'hello-world',
onClick() {
alert( `Don't touch me!` );
}
};
</script>
Here is the loader :
let loadComponent = (function() {
function fetchAndParse( URL ) {
return fetch( URL ).then( ( response ) => {
return response.text();
} ).then( ( html ) => {
const parser = new DOMParser();
const document = parser.parseFromString( html, 'text/html' );
const head = document.head;
const template = head.querySelector( 'template' );
const style = head.querySelector( 'style' );
const script = head.querySelector( 'script' );
return {
template,
style,
script
};
} );
}
function getSettings( { template, style, script } ) {
const jsFile = new Blob( [ script.textContent ], { type: 'application/javascript' } );
const jsURL = URL.createObjectURL( jsFile );
function getListeners( settings ) {
return Object.entries( settings ).reduce( ( listeners, [ setting, value ] ) => {
if ( setting.startsWith( 'on' ) ) {
listeners[ setting[ 2 ].toLowerCase() + setting.substr( 3 ) ] = value;
}
return listeners;
}, {} );
}
return import( jsURL ).then( ( module ) => {
const listeners = getListeners( module.default );
return {
name: module.default.name,
listeners,
template,
style
}
} );
}
function registerComponent( { template, style, name, listeners } ) {
class UnityComponent extends HTMLElement {
connectedCallback() {
this._upcast();
this._attachListeners();
}
_upcast() {
const shadow = this.attachShadow( { mode: 'open' } );
shadow.appendChild( style.cloneNode( true ) );
shadow.appendChild( document.importNode( template.content, true ) );
}
_attachListeners() {
Object.entries( listeners ).forEach( ( [ event, listener ] ) => {
this.addEventListener( event, listener, false );
} );
}
}
return customElements.define( name, UnityComponent );
}
function loadComponent( URL ) {
return fetchAndParse( URL ).then( getSettings ).then( registerComponent );
}
return loadComponent;
})()
let url = "https://blog.comandeer.pl/assets/jednoplikowe-komponenty/HelloWorld.wc" + `?x=${Math.random()}`
loadComponent(url) .then( ( component ) => {
console.log( 'Component loaded' );
document.body.insertAdjacentHTML( 'beforeend', '<hello-world>Comandeer</hello-world>' );
} );
document.querySelector( 'button' ).addEventListener( 'click', () => {
document.body.insertAdjacentHTML( 'beforeend', '<hello-world>Comandeer</hello-world>' );
} );
You can see a demo here SFC