Skip to content

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 defineCustomElement from each component's own path (instead of the @nessie/webcomponents barrel) keeps unused components out of your widget's bundle.
  • setTagTransformer is configured once, and must run before any defineCustomElement*() call — it only affects elements defined after it's set.
  • Guard against a missing customElements registry 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 };
}