Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stencil: Namespacing custom elements’ names to avoid collisions

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?

like image 915
lxg Avatar asked Jan 13 '20 08:01

lxg


2 Answers

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.

like image 65
G. Tranter Avatar answered Oct 05 '22 04:10

G. Tranter


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 />
}
like image 45
Arpitha Chandrashekara Avatar answered Oct 05 '22 04:10

Arpitha Chandrashekara