Stencil: Namespacing custom elements’ names to avoid collisions
Asked Answered
B

3

10

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?

Bolometer answered 13/1, 2020 at 8:22 Comment(5)
Yup, we’re doing exactly this right now. But it is somehow unsatisfying, as we strive to eliminate/avoid this kind of cross-dependencies.Bolometer
@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.Outfielder
@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.Bolometer
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.Outfielder
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.Bolometer
O
4

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 />
}
Outfielder answered 27/3, 2021 at 1:4 Comment(7)
Sounds good! Does this also work if both projects (UI library and microfrontend) are built with Stencil?Bolometer
I haven’t tried it but I guess it should work as it is a pure JavaScript solution and not specific to react.Outfielder
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.Bolometer
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 componentJugate
@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.Kremer
@Bolometer did this solution work when both projects are stencil?Kremer
Not really. But we’ve abandoned Stencil at some point and went with native web components.Bolometer
B
3

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.

Backwash answered 17/1, 2020 at 15:13 Comment(0)
K
1

This is how I Fixed in stencil 4.0 ..I added below configuration in my stencil.config.ts for which I want to dynamically change tag name.

  extras: {
    tagNameTransform: true,
    enableImportInjection: true,
  },

And this is how I consumed ...


import { defineCustomElements as stencilButtonComp } from "stencil-button/loader"

stencilButtonComp(window, {
  transformTagName: (tagName: string) => `uinqtag-${tagName}`,
} as any);

<uinqtag-stencil-button>Ok</uinqtag-stencil-button>

Note: You have to update tag name in all your unit tests and cypress tests.

Kremer answered 18/10, 2023 at 0:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.