6
votes

Our platform is built on a micro-frontend architecture with web components. We are using Stencil for some of them, this means we have multiple Stencil apps within one web page. Additionally, we have a UI library, also built with Stencil, which we want to use in these microfrontend components.

We would like to use the Stencil UI library as build-time dependency to the Stencil micro-frontend components. But this currently not possible due to tag name collisions:

Theoretically, two micro-frontends could ship two different versions of the UI library. However, at runtime, they would collide, as they’re both trying to register their UI elements with customElements.define. Of course, this doesn’t happen as Stencil checks for existing names before registering a new one – but the result is just as bad: The first loaded component always wins, and if it is an older version or has a different API, other components will break.

A solution would be namespacing/prefixing tag names at build or run time, but there is nothing in the web standards for this (yet). And while there is a namespace config option in Stencil, that doesn’t seem to solve this kind of problem.

With pure ES6, we could at least do the following (i.e. register a custom element with a dynamic tag name):

export class InnerComponent extends HTMLElement
{
    static register(prefix) {
        customElements.define(`my-${prefix}-inner-component`, InnerComponent)
    }

    constructor() {
        super()
        this.shadow = this.attachShadow({ mode: "open" })
    }

    connectedCallback() {
        this.shadow.innerHTML = `<span>this is some UI component</span>`
    }
}

And I’m sure we could employ some sort of build-time solution with Webpack etc. to achieve the same.

But is something similar possible with Stencil? Or how else can this be solved?

2
Well, prefixing custom elements in build time seems like the solution but then you'd need to use these prefixed component names in your target applications as well. What if your prod apps would always use the latest stable version of your UI lib and you'd make developers accountable for this? - Michal Cumpl
Yup, we’re doing exactly this right now. But it is somehow unsatisfying, as we strive to eliminate/avoid this kind of cross-dependencies. - lxg
@Ixg were you able to find any other solution to this problem (apart from prefixing custom elements)? I too am facing same issue in my project which is again micro-frontend based architecture with a library of reusable stencil components. - Arpitha Chandrashekara
@ArpithaChandrashekara Unfortunately not. While Stencil actually did introduce a build target for that, I found it to be broken and unsupported. But I will also say that we moved away from Stencil as it (in our opinion) develops in a weird direction. We are now using LitElement or just plain components instead. - lxg
But @Ixg, by using LitElement were you able to solve the above problem? Because, I guess, this is a problem with Web component architecture itself, we cannot define 2 components with the same name and in microfrontend application we will surely have multiple versions of same component. - Arpitha Chandrashekara

2 Answers

2
votes

We face a similar problem. Our solution was to avoid bundling dependencies and deploy them as separate libraries. So if A and B both depend on C, neither A nor B has any C components, and C is included in the front end as its own script resource.

2
votes

Stencil provides tagNameTransform config to support renaming of tag names at runtime. By default its value is false, ensure to make it to true.

This feature helps us in using stencil reusable components in microfrontend architecture, as each consuming Microfrontend can give its own unique name to the tagname which resolves the issue of using multiple versions of same stencil reusable component in Microfrontend platform.

Add below config to your stencil config file -

config.extras.tagNameTransform: true

And in the consuming microfrontends ensure to override the tagnames by using below piece of code -

import { defineCustomElements } from '@yourcomponent/libraryname/dist/loader';
...
defineCustomElements(window, { transformTagName: tagName => `unique-prefix-${tagName}` });

Note: In case you are using stencil-ds-output-targets for creating wrappers for stencil components, the support for renaming is not provided yet. A PR for the same is still pending - https://github.com/ionic-team/stencil-ds-output-targets/pull/59 . Possible workaround until the feature is supported - try and mimic the creation of wrapper from the consuming microfrontend. E.g -

import { JSX } from '@yourcomponent/libraryname';
import { createReactComponent } from '@yourcomponent/libraryname/<react-wrapper>/dist/react-component-lib'; 

const TextBox = createReactComponent('unique-prefix-<tagName>')

// Use this new TextBox component while rendering rather than from stencil wrapper directly
class YourComponent = () => {
    return <TextBox />
}