Writing a plugin

plugin() is the one way to add anything to the runtime. An extension is a plugin too, and the built-in ones are made the same way.

An extension is a single plugin function. It uses the tools it receives to register names, attributes, components, and values to provide. Below is an extension that adds one price-formatting helper and one attribute.

The tools it receives

A plugin function receives the tools below. Called before start, it is queued and runs at start. Called after start, it runs right away.

define

Registers a name usable in expressions of every scope. It is a tool, not state. It cannot override runtime names ($scope and so on).

defineScoped

Registers a name created for each h-scope. When read, it receives that scope's handle (host.el) and builds the value. Use it for tools that depend on the scope element, and call it before start. Names created per scope →

directive

Creates a new attribute. directive('focus', …) becomes h-focus. The function it returns is called on cleanup.

component

Creates a new tag. It takes props and fires events. style (or a <style> inside the template) goes into the document head once per component. By default it is not contained. With scoped, it is contained with @scope (tag). Browsers without @scope apply it uncontained. A <style> inside <template h-component> works the same way. {{ }} in the template is also read only on an element with h-text-inline. Set on the component tag, it reads the text passed in as the slot.

provide

Provides a value to the whole document. Any scope injects it with h-inject or $inject. If the key already exists, the existing value is returned, so it survives across visits.

reactive · computed

The core's reactivity. An extension uses it to create its own state. If an extension file bundles its own reactivity library, the page cannot follow those values, so use the ones received here. $v is built this way.

Names created per scope

A name registered with define is one for the whole document. A tool that depends on the scope element is registered with defineScoped. Each time an expression reads that name, it receives the handle of the scope that read it (host.el is the scope element) and builds the value. $tf is built this way.

Tools that hook into a scope's lifetime

turbo-drive is built with the tools below. Use the same ones to work with other libraries that swap out the page.

ToolWhat it does
detach(selector)Matching elements do not share the lifetime of the enclosing scope. They stay when the enclosing scope is torn down. When they leave the document, the runtime waits one tick before deciding
snapshot()Attaches each mounted scope's pre-mount markup as an attribute. The page stays as it is. The returned function removes the marks
onRevive(fn)Called right before a marked dead clone is mounted again. If anything needs to move from the dead copy to the new one, do it here
onStop(fn)Called when the runtime stops. Removes the listeners the extension added

Rules to follow