Behaviour and macros for self-describing component plugins.
Plugins declare themselves declaratively, allowing Dala core to remain generic. Everything becomes a plugin: video, maps, charts, camera, ML views, custom renderers, AR, etc.
Architecture
Dala core knows almost nothing. Plugins self-describe through:
- Schema - component metadata (props, events, capabilities)
- Protocol - binary communication format (auto-generated)
- Native renderer - platform-specific implementation (iOS/Android)
This is the same pattern used by:
- React Native Fabric
- Flutter Engine
- SwiftUI internals
- Jetpack Compose runtime
- VSCode extension host
- Browser DOM
Example (top-level DSL)
defmodule MyApp.VideoPlugin do
use Dala.Plugin
component "video" do
prop "source", :string
prop "autoplay", :bool
prop "controls", :bool
prop "volume", :f32
event "progress"
event "ended"
event "ready"
native "ios", "DalaVideoView"
native "android", "com.dala.video.VideoView"
capability :gestures
capability :accessibility
capability :animation
end
endExample (plugin do block)
defmodule MyApp.VideoPlugin do
use Dala.Plugin
plugin do
plugin_description "Video playback plugin"
component "video", MyApp.VideoComponent
plugin_event "video_zoom", MyApp.Video.Events.Zoom
plugin_native :ios, MyApp.Video.IOS
plugin_native :android, MyApp.Video.Android
plugin_permission :camera
plugin_dependency {:maps, "~> 1.0"}
plugin_platform :ios
plugin_platform :android
end
endBehaviour
Modules using Dala.Plugin must implement (or get defaults for) these callbacks:
init(opts)→{:ok, state}|{:error, reason}— resource allocation (required)components()→[Component.t()]— declare components (required)capabilities()→[atom()]— what this plugin provides (required)permissions()→[atom()]— declare required permissions (optional, default[])native_modules(platform)→[module()]— platform-specific native modules (optional, default[])dependencies()→[{atom(), String.t()}]— dependency ordering (optional, default[])validate_config(config)→:ok|{:error, reason}— compile-time hooks (optional, default:ok)handle_event(event, payload, state)→{:ok, state}|{:error, reason}— runtime hooks (optional, default{:ok, state})cleanup(state)→:ok— resource deallocation / hot reload (optional, default:ok)
Versioning
Plugins MUST declare versions for compatibility:
schema_version "1.0.0"
protocol_version 3
native_api_version "2.0.0"This prevents ecosystem fragmentation.
Host/Runtime Separation
Plugins should NEVER directly access:
- BEAM internals
- Scheduler state
- Raw protocol sockets
Instead:
Plugin
↓
Host API
↓
Dala RuntimeExactly like browser extensions.
Generic Node Model
Everything becomes a generic node:
%Dala.Node{
type: "video",
props: %{source: "...", autoplay: true},
children: []
}Dala core NEVER special-cases video, maps, or charts. The same generic lifecycle applies to all plugins:
- create/2
- update/2
- layout/2
- event/3
- dispose/1
Optional capabilities:
- animate/2
- focus/2
- accessibility/2
- snapshot/1
- texture/1
- gesture/2
Universal Command Stream
Dala core emits only generic operations:
- CREATE_NODE
- UPDATE_PROP
- REMOVE_NODE
- EMIT_EVENT
- RUN_ANIMATION
Plugins interpret semantics. Core stays tiny.
Summary
Callbacks
Returns the list of capabilities this plugin provides.
Cleans up resources before hot reload or unload.
Returns the list of component schemas this plugin provides.
Returns dependency specifications: {plugin_name, version_requirement}.
Handles runtime events sent to the plugin.
Initializes the plugin. Allocates resources, starts processes, etc.
Returns {:ok, state} on success or {:error, reason} on failure.
Returns platform-specific native modules for the given platform.
Returns the list of permissions this plugin requires.
Validates plugin configuration at compile-time.
Functions
Macro callback - builds the full plugin struct and generates default behaviour implementations.
Defines a new plugin module.
Auto-registers all plugins found in loaded applications.
Defines a new component within the plugin.
Declares a dependency on another plugin with a version requirement.
Sets the plugin description.
Sets the native API version.
Declares a native module for a specific platform.
Declares a permission the plugin requires.
Declares platform support.
Main entry point for the plugin DSL block.
Sets the binary protocol version.
Sets the plugin schema version.
Types
@type capability() ::
:gestures
| :accessibility
| :animation
| :textures
| :overlay
| :clipping
| :touch
| :keyboard
| :focus
@type component_name() :: String.t()
@type event_name() :: String.t()
@type platform() :: :ios | :android | :web
@type plugin_name() :: atom()
@type prop_name() :: String.t()
@type prop_type() ::
:string
| :bool
| :integer
| :float
| :f32
| :f64
| :color
| :binary
| :list
| :map
@type status() :: :registered | :initialized | :active | :error | :unloaded
Callbacks
@callback capabilities() :: [atom()]
Returns the list of capabilities this plugin provides.
@callback cleanup(state :: term()) :: :ok
Cleans up resources before hot reload or unload.
@callback components() :: [Dala.Plugin.Component.t()]
Returns the list of component schemas this plugin provides.
Returns dependency specifications: {plugin_name, version_requirement}.
@callback handle_event(event :: atom(), payload :: map(), state :: term()) :: {:ok, state :: term()} | {:error, reason :: term()}
Handles runtime events sent to the plugin.
Initializes the plugin. Allocates resources, starts processes, etc.
Returns {:ok, state} on success or {:error, reason} on failure.
Returns platform-specific native modules for the given platform.
@callback permissions() :: [atom()]
Returns the list of permissions this plugin requires.
Validates plugin configuration at compile-time.
Functions
Macro callback - builds the full plugin struct and generates default behaviour implementations.
Defines a new plugin module.
Options
:schema_version- Plugin schema version (default: "1.0.0"):protocol_version- Binary protocol version (default: 3):native_api_version- Native API version (default: "2.0.0"):platforms- List of supported platforms (e.g. [:ios, :android]):capabilities- List of plugin-level capabilities:dala_requires- Dala version requirement string:metadata- Arbitrary metadata map
Auto-registers all plugins found in loaded applications.
Defines a new component within the plugin.
Example
component "video" do
prop "source", :string
prop "autoplay", :bool
prop "volume", :f32
event "progress"
event "ended"
native "ios", "DalaVideoView"
native "android", "com.dala.video.VideoView"
capability :gestures
capability :accessibility
end
Declares a dependency on another plugin with a version requirement.
Sets the plugin description.
Sets the native API version.
Declares a native module for a specific platform.
Declares a permission the plugin requires.
Declares platform support.
Main entry point for the plugin DSL block.
Inside a plugin do block, you can declare:
component :chart, MyPlugin.ChartComponentplugin_event :chart_zoom, MyPlugin.Events.Zoomplugin_native :ios, MyPlugin.IOSplugin_native :android, MyPlugin.Androidplugin_permission :cameraplugin_dependency {:maps, "~> 1.0"}plugin_platform :iosplugin_platform :androidplugin_description "My chart plugin"
If plugin do is used, it takes precedence for those declarations.
Sets the binary protocol version.
Sets the plugin schema version.