Web Components/Custom Elements as Micro Front-Ends (MFE)

Photo Boards
Photo Boards

A quick post showing how to integrate a StencilJS Web Component

A few years back (October 2017), I made a sample QR Code web component/Custom Element using StencilJS.

Last year I tried to show this off on this blog, and only succeeded today πŸ˜….

Better late than never!

The hard part was finding how to import an ES Module built by StencilJS and published on a Github Page.

This Stackoverflow post showed me how to use async imports to load an ES Module.

You can find the page on the github repository, try it in the playground web-component page; I also combined Markdown parsing + Svelte to integrate it in this post below.

Custom Elements or Web Components?

What is the difference?

See this really good answer on StackExchange https://softwareengineering.stackexchange.com/a/289039

(I’ll rewrite it in my own word later)

Import Maps

I discovered this when migrating my Vue 2 app to Vue 3 to be able to expose it as a custome element.

See https://www.honeybadger.io/blog/import-maps/ https://html.spec.whatwg.org/multipage/webappapis.html#import-maps

(EDIT 2023-02-06) Unfortunately, when writing this post, importmaps were not yet supported on Safari and Firefox. There is an existing polyfill for ES Modules including importmaps, So I added it after reading this great post explaining how it works.

I used importShim instead of import in the code above to allow dynamic imports to work as expected.

It should work on Safari, Safari Mobile, Chrome and Firefox, now.

Interoperability

On https://custom-elements-everywhere.com/ you can check which frameworks works well with Custom Elements and as Custom Elements.

Also a quick check on https://caniuse.com/?search=web%20component

In a nutshell, Custom Elements do not work on Internet Explorer 11 or Opera Mini, but almost everywhere else. There are polyfills if you want full support on these outdated browsers.

Try it out

Below, there are:

Voltron
Voltron

Svelte component compiled as Custom Element

  • with <svelte:options tag="sample-custom-element" />
  • only imported once to avoir errors
  • vite.config.js adapted to only compile custom elements the Svelte components with the above option (see this vite-plugin-svelte issue comment)

StencilJS component

And now an Angular Application (packaged as a Web Component using Angular Elements)

  • I switched to a Zone-Less on-push detection strategy.
  • Styles are not yet correctly imported.

Also Vue 3 + VueX 4

  • from my side-project https://github.com/doppelganger9/hystoire-de-fou (in french)
  • I had to handle importmaps to make it possible.
  • Google Font Caveat is imported with a dynamic <link rel='stylesheet'> tag appended to the <head> of the document.

Aaaaaaand React

The code

Seems simple, after having spent a few hours on trying to make it work πŸ˜‚!

<script lang="ts">
  import { onMount } from 'svelte';
  import SampleCustomElement from '/src/lib/components/SampleCustomElement.svelte';

  let qrCodeContents = "test";

  onMount(async () => {
    // this one is a StencilJS component
    await import("https://doppelganger9.github.io/stencil-qrcode-component/build/stqrcmp.esm.js");

    // this one is an Angular Element application
    await import("https://doppelganger9.github.io/tables-multiplications/main.js");

    // this one is a build-less Vue 3 Custom Element app.
    // I'm using an importmap for vue/vuex/vue devtools, I need to import it before all other scripts (or else it will throw an error) 
    // Note: svelte:head is not allowed inside the markdown blog post content, 
    // so we have to find another way!
    // So I had to add it the HEAD tag of the index.html file.
    // the required Google Font Caveat is included below
    // On Safari and Firefox, importmaps are not yet supported, so we use ES Module Shim to polyfill it.
    // Furthermore, dynamic imports in this case need another tweak, 
    // we'll use importShim instead of import to make them work.
    await importShim("https://doppelganger9.github.io/hystoire-de-fou/vue-app.mjs");

    // For the React App, I did not manage yet to package Fonts inside the Web Component.
    // Adding a link rel="stylesheet" for Google Roboto Font
    const styleLinkForRobotoFont = document.createElement("link");
    styleLinkForRobotoFont.rel = 'stylesheet';
    styleLinkForRobotoFont.href = 'https://fonts.googleapis.com/css?family=Roboto|Caveat';
    document.head.appendChild(styleLinkForRobotoFont);
    // Adding a style tag to load FontAwesome
    const styleLinkForFontAwesome = document.createElement("link");
    styleLinkForFontAwesome.rel = 'stylesheet';
    styleLinkForFontAwesome.href = 'https://doppelganger9.github.io/react-cv-template/assets/index.css';
    document.head.appendChild(styleLinkForFontAwesome);
    await importShim("https://doppelganger9.github.io/react-cv-template/assets/index.mjs");
  });

</script>

<!-- A StencilJS QR Code component with some Svelte reactivity added -->
<label for="qr-content">You can change the QR Code contents here:</label>
<input id="qr-content" type="text" bind:value={qrCodeContents} />

<qr-code
  class="with-rounded-corners"
  contents={qrCodeContents}
  output-mode="SVG"
  style="width: 300px;height: 300px;">
</qr-code>

<style>
  qr-code {
    padding: 5px;
    margin: 0 auto;
  }
</style>

<!-- A Svelte compiled as a Custom Element -->
<sample-custom-element></sample-custom-element>

<!-- And the Angular App Custom Element:  -->
<tables-multiplications-app></tables-multiplications-app>

<!-- And the Vue 3 + VueX 4 Custom Element app:  -->
<hdf-app-root></hdf-app-root>

<!-- And the React Custom Element app: -->
<cv-app-root></cv-app-root>

towards Micro-Frontends ?

This is an example to show how to mix 5 frameworks (Svelte, StencilJS, Angular, Vue 3, React) with SvelteKit as the Host, and Svelte / StencilJS / Angular / Vue 3 apps provided as web components. Some of these custom elements are complete apps!

My ultimate goal was to learn by experimenting on how to integrate/federate different apps built with different technologies (React, Angular, StencilJS, Svelte, Vue).

To do that, this blog is an interesting playground. Also I have other side projects in these technologies, that I could federate and later explain more in this blog.

  • βœ… Svelte
  • βœ… StencilJS
  • βœ… Angular
  • βœ… Vue 3
  • βœ… React
Power Ranger pyramid explosion
Power Ranger pyramid explosion

Maybe this blog will become a hub for all my web app creations, we’ll see…

See you in a few days!

(EDIT: I integrated an Angular App a few days later. I will complete this post with more details.) (EDIT2: I also integrated a Vue Custom Element app later.) (EDIT3: I finished by migrating my React App to be able to package it as a Custom Element and included it here!)

Reference

Image credit : β€œPhoto Boards” from Unsplash user @createandbloom

Discussion

As always, feel free to reply to this post below πŸ‘‡, also my DMs are open on Twitter or you can find me on Mastodon: @david_lacourt@mamot.fr!