Configuration
static config declares everything the framework needs to know about a component before an instance exists.
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 []:
import { Base } from '@studiometa/js-toolkit';
class Tabs extends Base {
static config = {
name: 'Tabs',
refs: ['list', 'tabs[]', 'panels[]'],
};
}config.options
Each entry is a type, a tuple of types, or an object with type and default:
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:
- it registers those names when this component registers, so one
registerComponent()call covers a whole tree; - it gives the name set that
on<Child><Event>resolution needs.
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
classthat does not extendBaseis reported ascomponent.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:
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:
| Key | Merge rule |
|---|---|
refs | union |
options | entry by entry |
components | entry by entry |
name | the most derived class wins |
mountStrategy | the most derived class wins |
A subclass that states a components key again wins for that key only.
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:
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:
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.