9

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?

lxg
  • 12,375
  • 12
  • 51
  • 73
  • 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 Jan 16 '20 at 09:49
  • 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 Jan 16 '20 at 16:47
  • @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 Mar 22 '21 at 13:44
  • 1
    @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 Mar 22 '21 at 15:18
  • 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 Mar 23 '21 at 08:51
  • As mentioned above, we are using the approach where we export the class and then use customElements.define with a prefix on the name. This does work with the plain elements. – lxg Mar 23 '21 at 10:27

2 Answers2

3

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 />
}
  • Sounds good! Does this also work if both projects (UI library and microfrontend) are built with Stencil? – lxg Mar 27 '21 at 07:44
  • I haven’t tried it but I guess it should work as it is a pure JavaScript solution and not specific to react. – Arpitha Chandrashekara Mar 27 '21 at 07:51
  • My problem was that I got this working as well (in a similar way), but it did *not* work in a setup where both projects are Stencil; especially with different stencil versions. The reason was that Stencil builds/exports the UI library components in a way that they still have a dependency on Stencil. But for some reason, the generated component breaks something internally if the downstream also uses Stencil. I’ll have to investigate this once more. – lxg Mar 27 '21 at 09:38
  • ok, so how can you do this without the react wrapper? I'm trying to make a basic pure web component library that allows me to use my web components inchanably through libs and frameworks. I see the option to use the React nonsense but if I go through all that trouble why would I use web components in the first place. why would just write a React component – Justin Meskan Jun 01 '22 at 03:14
  • @ArpithaChandrashekara I tried above solution in a situation where I am including stencil primitive(ex:button) component in another stencil component(ex:widget). But i get error and the primitive component doesn't load at all. I am trying this with stencil v2.6. – Yugandhar Pathi Jun 29 '23 at 00:35
  • @lxg did this solution work when both projects are stencil? – Yugandhar Pathi Jun 29 '23 at 00:35
  • 1
    Not really. But we’ve abandoned Stencil at some point and went with native web components. – lxg Jun 29 '23 at 06:48
2

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.

G. Tranter
  • 16,766
  • 1
  • 48
  • 68