Remote Single file component without any framework

artydev - Mar 13 '23 - - Dev Community

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>
Enter fullscreen mode Exit fullscreen mode

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>' );
} );
Enter fullscreen mode Exit fullscreen mode

You can see a demo here SFC

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .