Skip to content

Configuration

static config declares everything the framework needs to know about a component before an instance exists.

ts
interface BaseConfig {
  name: string;
  components?: Record<string, BaseConstructor | ComponentImporter>;
  refs?: string[];
  options?: Record<string, OptionDefinition>;
  mountStrategy?: MountStrategy;
}

config.name

Required. The name the registry registers under, the token data-component writes, and the prefix of every instance's $id.

The name comes from the merged config, so a subclass that extends a component and forgets to rename registers under the name it inherited — and collides — rather than under undefined.

config.refs

The refs the component declares. A list ref keeps its []:

js
import { 
Base
} from '@studiometa/js-toolkit';
class
Tabs
extends
Base
{
static
config
= {
name
: 'Tabs',
refs
: ['list', 'tabs[]', 'panels[]'],
}; }

See Refs and data-ref.

config.options

Each entry is a type, a tuple of types, or an object with type and default:

js
import { 
Base
} from '@studiometa/js-toolkit';
class
Slider
extends
Base
{
static
config
= {
name
: 'Slider',
options
: {
label
:
String
, // short form
speed
: {
type
:
Number
,
default
: 1 }, // primitive default
loop
: {
type
:
Boolean
,
default
: true },
tween
: {
type
:
Object
,
default
: () => ({}) }, // factory required
offset
: [
Number
,
Array
], // a union, in order
}, }; }

The five option types are String, Number, Boolean, Array and Object. Function is not one, which is what makes a function default unambiguously a factory.

See Options and data-option-<name>.

config.components

The declared family. It does two jobs, and neither is ownership:

  1. it registers those names when this component registers, so one registerComponent() call covers a whole tree;
  2. it gives the name set that on<Child><Event> resolution needs.
js
static config = {
  name: 'Accordion',
  components: {
    AccordionItem, // a class
    Icon: () => import('./Icon.js'), // a thunk — its own chunk
  },
};
  • The key supplies the name, so a lazy child is a name the registry knows with nothing downloaded.
  • A thunk is deferred rather than resolved, and becomes a lazy entry of the same registry.
  • First wins, quietly. Several parents declaring the same lazy child is the normal case.
  • A value written with class that does not extend Base is reported as component.invalid-family-declaration, where it is declared.

See Autoloading.

config.mountStrategy

The component's default answer to when. Any element overrides it with data-mount:

js
import { 
Base
} from '@studiometa/js-toolkit';
class
Map
extends
Base
{
static
config
= {
name
: 'Map',
mountStrategy
: 'visible',
}; }

See Mount strategies and data-mount.

Merging along the prototype chain

$config walks the prototype chain and merges every config it finds:

KeyMerge rule
refsunion
optionsentry by entry
componentsentry by entry
namethe most derived class wins
mountStrategythe most derived class wins

A subclass that states a components key again wins for that key only.

js
import { 
Base
} from '@studiometa/js-toolkit';
class
AbstractControl
extends
Base
{
static
config
= {
name
: 'AbstractControl',
refs
: ['button'],
options
: {
label
:
String
},
}; } class
NavigationControl
extends
AbstractControl
{
static
config
= {
name
: 'NavigationControl',
refs
: ['compass'], // merged: ['button', 'compass']
options
: {
showCompass
:
Boolean
}, // merged with `label`
}; }

An intermediate class should declare static config: BaseConfig, or let TypeScript infer a literal type every subclass matches.

The registry reads the merged config too, before any instance exists, through resolveConfig(). That is how it knows the mount strategy of a pair, the family to register, and the name a class registers under.

There is no withExtraConfig()

To extend a component with a different config, declare a class. To extend one you cannot edit, do it in expression position:

js
registerComponent(
  class extends Vendor {
    static config = { name: 'CompactVendor', options: { compact: Boolean } };
  },
);

@component()

The decorator writes static config and calls registerComponent() in one step, and the two forms merge:

ts
import { 
Base
,
component
} from '@studiometa/js-toolkit';
@
component
({
name
: 'Slider',
refs
: ['next'] })
class
Slider
extends
Base
{
static
config
= {
name
: 'Slider',
options
: {
speed
:
Number
} };
}

They merge in a class initializer, which runs after the fields and inside the class definition, so registerComponent() reads the finished config. A key both sides declare differently is reported as component.config-conflict.

See @component.

MIT Licensed