Appearance
Using Nessie Webcomponents in Widgets work in progress
Problem: Nessie webcomponents are not scoped by default
Nessie's web components register themselves under fixed tag names, such as nes-button or nes-list-item. The browser's custom element registry is global to the page — it's shared by the host application and every widget embedded in it. If your widget registers nes-button as-is and the host (or another widget) also registers it, only the first registration wins. Every other widget silently gets whichever version registered first, even if it's a different Nessie version than the one your widget was built and tested against.
Solution: Scoping Nessie webcomponents in your widget
To avoid this, widgets register Nessie's web components under a scoped tag name, prefixed with their team name — see naming conventions. For example, the nsweb team registers nes-button as nsweb-nes-button instead.
How to scope Nessie webcomponents
WORK IN PROGRESS
In the future, Frontpack will contain tooling to set up auto-prefixing for you. For now, follow this guide to set it up manually.
Stencil (the framework Nessie's web components are built with) supports rewriting tag names via setTagTransformer. Use it in a small helper that your widget calls once, before it renders anything that uses Nessie components:
Step 1: Create a file to register the Nessie components your widget uses
For example: nessie/define-scoped-elements.ts
ts
// Import setTagTransformer from Stencil.
import { setTagTransformer } from '@stencil/core/internal/client';
// Import the relevant Nessie components.
import { defineCustomElement as defineNesButtonElement } from '@nessie/webcomponents/dist/components/nes-button';
import { defineCustomElement as defineNesListItemElement } from '@nessie/webcomponents/dist/components/nes-list-item';
// Define a prefix.
const WIDGET_TAG_PREFIX = 'nsweb-';
const DEFINE_CUSTOM_ELEMENT_FNS: readonly (() => void)[] = [defineNesButtonElement, defineNesListItemElement];
let tagTransformerConfigured = false;
export function defineScopedNessieElements(): void {
if (typeof customElements === 'undefined') {
return;
}
if (!tagTransformerConfigured) {
setTagTransformer((tag) => (tag.startsWith('nes-') ? `${WIDGET_TAG_PREFIX}${tag}` : tag));
tagTransformerConfigured = true;
}
// defineCustomElement() registers under the tag-transformer's rewritten
// name, and so does any nested component a component renders internally.
for (const defineCustomElement of DEFINE_CUSTOM_ELEMENT_FNS) {
defineCustomElement();
}
}A few things to note about this helper:
- Only import the components you use. Importing
defineCustomElementfrom each component's own path (instead of the@nessie/webcomponentsbarrel) keeps unused components out of your widget's bundle. setTagTransformeris configured once, and must run before anydefineCustomElement*()call — it only affects elements defined after it's set.- Guard against a missing
customElementsregistry so the helper is safe to call in non-browser environments (e.g. during server-side rendering or tests).
Step 3: Call the defineScopedNessieElements function
Call defineScopedNessieElements() once, as early as possible in your widget's main.ts.
Step 3: Use the scoped elements
Reference the prefixed tag names in your templates, and add CUSTOM_ELEMENTS_SCHEMA so Angular doesn't complain about an unknown element:
ts
@Component({
selector: 'widget-nsweb-station-list',
templateUrl: './list.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class StationListComponent {}html
<nsweb-nes-button [variant]="'primary'" (nesOnClick)="onAddItem()"> Add item </nsweb-nes-button>TIP
nsweb- is just this team's prefix. Use your own team's prefix, following the naming conventions.
Why @stencil/core/internal/client?
setTagTransformer isn't part of @nessie/webcomponents's public API yet, so it's imported directly from Stencil's internal client runtime. This is a temporary workaround — track WEBFUN-44 for when Nessie exposes it directly. Until then, you'll also need an ambient type declaration, since the internal module has no public types:
ts
// nessie/stencil-internal-client.d.ts
declare module '@stencil/core/internal/client' {
const setTagTransformer: (transformer: (tag: string) => string) => void;
export { setTagTransformer };
}