Extending sigmx
Everything you write in markup is served by a plugin: data-text is a directive, @post() is a function, event: sigmx-patch-elements is handled by a server-event handler. sigmx ships fifty of them and treats yours exactly the same way. This section shows how to write each kind, how to test and package one, and walks through a complete analytics plugin.
Why it is built this way
Section titled “Why it is built this way”- A plugin is a plain value.
attribute(),action()andhandler()add atypefield and give you typed contexts; they do not touch any global. Nothing registers on import, so a plugin can live in any module, be tree-shaken, and be tested without a page. - Three shapes, no special cases. Every built-in is one of the same three objects you can create. There is no privileged core plugin API, so anything the built-ins can do, yours can do.
- Auto mode reads your plugin like a built-in. Point auto mode at the file and the scanner works out from your templates whether it is used, ships it only then, and precompiles its expressions along with everything else.
- Teardown is automatic. Effects, listeners and cleanups registered through the context are undone when the attribute changes, the element leaves the document, or the instance is destroyed. A plugin that only uses the context cannot leak.
- Errors are routed, not lost. Anything thrown while mounting, evaluating or running later is reported through
onErrorwith the plugin name, element and attribute attached.
The three kinds
Section titled “The three kinds”| kind | written as | defined with | example |
|---|---|---|---|
| directive | data-x |
attribute({ name: 'x', mount }) |
data-clock:now__every.1s writes the time into $now |
| function | @x() |
action({ name: 'x', call }) |
@share({ url }) returns a promise |
| server-event handler | event: sigmx-x |
handler({ name: 'x', handle }) |
event: sigmx-toast shows a message |
import { attribute, action, handler } from 'sigmx'
export const upper = attribute({ name: 'upper', value: 'required', mount({ el, evaluate, effect }) { effect(() => { el.textContent = String(evaluate()).toUpperCase() })} })
export const shout = action({ name: 'shout', call: (_, text: string) => text.toUpperCase() })
export const toast = handler({ name: 'toast', handle: (_, { message }) => show(message) })Register them once, with the built-ins:
import { createSigmx } from 'sigmx'import { essentials } from 'sigmx/presets/essentials'
createSigmx({ plugins: [...essentials, upper, shout, toast] })or later, on a running instance, with app.use(upper). Directives registered late are applied to the page straight away.
When to write one
Section titled “When to write one”Most behaviour belongs in an expression. data-on:click="$open = !$open" needs no plugin, and neither does a one-off data-init="el.focus()". Reach for a plugin when:
- the same few lines of
data-initordata-effectkeep appearing across templates; - the behaviour needs teardown (an interval, an observer, a listener on
window) that an expression cannot register; - it wraps a browser or third-party API that should not leak into markup, such as an analytics script;
- the server needs to say something to the page that is not a signal patch or an element patch.
If the code fits in one attribute and never needs undoing, keep it as an expression.
In this section
Section titled “In this section”| page | what you will build |
|---|---|
| Writing a directive | data-upper and a data-clock with a key, a modifier and an interval that is cleared for you |
| Writing a function | @shout and an async @share with a clipboard fallback |
| Writing a server-event handler | a toast handler fed by a streaming endpoint |
| Recipe: Plausible analytics | a complete plugin: data-track, @track() and event: sigmx-track, registered through auto mode |
| Packaging a plugin | shipping it as a project file or an npm package, with a test and a size check |