These pages document @evanion/widget 0.1.3, the version on npm.
Write your own widget renderer
If you are using a framework without a published renderer, you can implement your own. To ensure your renderer provides a consistent developer experience, you should synchronize its warnings with the @evanion/widget core.
The Problem: Inconsistent Warning Logs
When a renderer encounters a widget type it doesn’t have a component for, it must skip that item. If every renderer in your ecosystem has its own way of warning about this, your logs become a mess of inconsistent messages.
Both @evanion/react-widget and @evanion/astro-widget solve this by using ERROR_MESSAGES from the core. This ensures that regardless of the framework, the same unknown type produces the same log line.
import { } from '@evanion/widget';
const = .('listting', 'hive');
; // -> 'Unknown widget type "listting" for widget ID "hive". Skipping render.'Using warnOnce
Renderers run during the render loop. Without protection, a single unknown widget would trigger a warning on every single re-render, flooding your console.
@evanion/widget provides warnOnce, which prints a message the first time it is encountered in a process and then silences it. It also automatically suppresses all warnings when NODE_ENV is set to production.
Implementing the Render Loop
A basic renderer iterates over the widget items, looks up the type in a registry, and renders the corresponding component. If the type is missing, it uses warnOnce to report the issue.
import { , , } from '@evanion/widget';
import type { , } from '@evanion/widget';
type = <string, { ?: string }>;
const : <(: { ?: string }) => string> = {
: () => `Listing: ${.}`,
};
function (: []): string[] {
return .(() => {
// An own key only: a CMS type of `constructor` is unknown.
const = ...(, .)
? [.]
: ;
if ( === ) {
(.(., .));
return [];
}
return [(.)];
});
}
// The set of printed messages lives as long as the process, so a renderer's
// tests clear it before each case.
();
const : [] = [
{ : 'wingspan', : 'listing', : { : 'Wingspan' } },
{ : 'hive', : 'listting', : { : 'Hive' } },
];
(); // -> ['Listing: Wingspan']
(); // -> ['Listing: Wingspan']Key implementation details:
- Registry Lookup: Use
Object.prototype.hasOwnPropertyto check for the type. Avoidingitem.type in registryprevents the renderer from accidentally trying to render inherited object properties (liketoString). - Warning Reset: In your tests, call
resetWarningsbefore each case to ensure that one test’s warning doesn’t silence the next. - Production Silence: In production,
warnOnceprints nothing. This is why you must runvalidateItemsas a separate quality gate (as shown in Getting started) to catch content errors before they hit production.
Extending the Renderer for Complex Layouts
To build a production-ready renderer, you need to handle four additional responsibilities:
- Children: Decide how to handle nested items. You can either draw them inside the parent (like
@evanion/react-widget) or pass them as data to the parent’s component (like@evanion/astro-widget). - Meta: Pass the
metaproperty to the item chrome (chrome.item), never to the widget’sprops. - Structural Warnings: Use
warnOnceto report structural failures:MALFORMED_ITEMS: The items provided are not an array.MALFORMED_ITEM(id, type): An entry is not an object or lacks a stringtype.MALFORMED_CHILDREN(id): Thechildrenfield is present but is not an array.
- Separation of Concerns: Do not call
validateItemsinside the renderer. The renderer’s job is to draw what it can and warn about what it can’t. The validation check should run where the payload first enters your system.
Every widget core export
The API reference lists every export of @evanion/widget with its signature and the shape of each ERROR_MESSAGES key.