Rust plugin API
Implement plugins with the Rust Plugin trait and SharedPluginable.
The telarel crate re-exports the plugin interface from telarel_plugin. A Rust plugin implements the Plugin trait and is wrapped into a shared, object-safe handle with Plugin::new_shared.
For the hook contract shared by both languages, see the Plugin API.
The Plugin trait
pub trait Plugin: Any + Debug + Send + Sync + 'static {
fn new_shared(plugin: Self) -> SharedPluginable
where
Self: Sized;
fn name(&self) -> Cow<'static, str>;
fn register_hook_usage(&self) -> HookUsage;
// ... one method per hook, each defaulting to a no-op ...
}namereturns the plugin name.register_hook_usagedeclares which hooks the plugin implements. The partitioned stages —parse,prepare,transform,finalize, andgenerate— are only called when declared.options,start, andendrun on every settled plugin regardless of usage.new_sharedwraps a sized plugin into aSharedPluginable, which isArc<dyn Pluginable>.Pluginableis the object-safe mirror of the trait and is implemented for everyPlugin.
Every hook has a default implementation that does nothing, so a plugin only writes the hooks it needs.
HookUsage
HookUsage is a bit flag set with one flag per hook:
use telarel::HookUsage;
let usage: HookUsage = HookUsage::Prepare | HookUsage::End;The flags are Options, Start, Parse, Prepare, Transform, Finalize, Generate, and End.
Hooks
Each hook returns an impl Future, so its body can be written as an async block. The context borrows the pipeline state, and the hook resolves to its return type.
| Hook | Arguments | Return |
|---|---|---|
options | &CommonPluginContext, &OptionsArgs | OptionsReturn |
start | &PluginContext, &StartArgs | NotifyReturn |
parse | &PluginContext, ParseArgs | ParseReturn |
prepare | &PluginContext, PrepareArgs | NotifyReturn |
transform | &PluginContext, TransformArgs | TransformReturn |
finalize | &PluginContext, FinalizeArgs | NotifyReturn |
generate | &PluginContext, GenerateArgs | GenerateReturn |
end | &PluginContext, &EndArgs | NotifyReturn |
Arguments
| Type | Fields |
|---|---|
OptionsArgs | options: ResolvableOptions, plugins: Vec<SharedPluginable> |
StartArgs | options: ResolvedOptions |
ParseArgs | code: &str, options: &ParserOptions |
PrepareArgs | ast: &Ast |
TransformArgs | ast: Ast (owned) |
FinalizeArgs | ast: &Ast |
GenerateArgs | ast: &Ast, options: &GeneratorOptions |
EndArgs | error: Option<CompileError> |
The options bag is shared by every entry point and always uses source_map. parse and rewrite expose a flat input_source_map at the entry point; it is normalized to source_map.input before the options hook runs. Those AST-producing entry points ignore source_map.output and output, so a stage-agnostic plugin may set them for every stage without failing.
A parse hook replacement is used as returned: its own Ast / Program.source_type carries the grammar that later stages and code generation see. ParserOptions are passed to the hook, but the driver applies them only to the builtin parser that runs when every hook defers. The JavaScript binding differs: it rebuilds a parse replacement with the parser options' grammar.
TransformArgs owns a cheap Ast handle: Ast::clone is O(1), and Ast::with_mut copies the arena on write when the driver still holds the carried AST. TransformArgs is Clone, not Copy. Returning None leaves the driver's AST untouched.
with_mut may rewrite the tree, but changing the source text requires building a new Ast and returning it. Debug builds assert that Program::source_text still matches Ast::source.
TransformOutput is the value a transform hook returns to replace the carried AST; it wraps the new owned ast.
Returns
Every fallible hook returns Result<_, PluginError>. NotifyReturn is Result<(), PluginError>; the stages that hand back a value return Option inside the result, where None means "no change".
| Type | Definition |
|---|---|
OptionsReturn | Result<Option<OptionsArgs>, PluginError> |
NotifyReturn | Result<(), PluginError> |
ParseReturn | Result<Option<AstResult>, PluginError> |
TransformReturn | Result<Option<TransformOutput>, PluginError> |
GenerateReturn | Result<Option<CodeResult>, PluginError> |
Returning Some from options replaces the whole options bag; fields the returned OptionsArgs omits are dropped and fall back to their defaults.
Errors
A hook reports failure with PluginError. The error reaches the entry point as a CompileError with its message and attached source unchanged.
Ordering
Each Rust hook exposes a <hook>_meta method returning Option<PluginHookMeta>, all defaulting to None. PluginOrder is Pre or Post.
use telarel::{PluginHookMeta, PluginOrder};
fn transform_meta(&self) -> Option<PluginHookMeta> {
Some(PluginHookMeta { order: Some(PluginOrder::Pre) })
}Context
optionsreceivesCommonPluginContext, which carries no fields.- Every later hook receives
PluginContext:
| Field | Type | Description |
|---|---|---|
cwd | &Path | Filesystem root used to resolve relative paths. |
module | ModuleInfo | Module being compiled. |
ModuleInfo holds file: &str (the file being compiled), language: Language, and source_type: SourceType.
Passing plugins
Wrap each plugin in Plugin::new_shared and pass the list to any entry point.
use telarel::{CodeResult, CompileOptions, Plugin, SharedPluginable, compile};
let plugins: Vec<SharedPluginable> = vec![
Plugin::new_shared(TracePlugin),
Plugin::new_shared(ReplaceRootPlugin),
];
let result: CodeResult = compile(
String::from("const x: number = 1;"),
CompileOptions::default(),
plugins,
)
.await?;Example
A plugin that rewrites the module specifier react to preact/compat in transform. The hook owns a cheap AST handle; mutating it through Ast::with_mut copies the arena on write, and the mutated AST is returned as TransformOutput:
use std::borrow::Cow;
use std::future::Future;
use telarel::allocator::Allocator;
use telarel::ast::ast::{ImportDeclaration, Program};
use telarel::ast_visit::VisitMut;
use telarel::ast_visit::walk_mut;
use telarel::str::Str;
use telarel::{
Ast, HookUsage, Plugin, PluginContext, TransformArgs, TransformOutput,
TransformReturn,
};
const FROM: &str = "react";
const TO: &str = "preact/compat";
struct ImportRewriter<'x> {
allocator: &'x Allocator,
}
impl<'x> VisitMut<'x> for ImportRewriter<'x> {
fn visit_import_declaration(
&mut self,
declaration: &mut ImportDeclaration<'x>,
) {
if declaration.source.value.as_str() == FROM {
declaration.source.value = Str::from_str_in(TO, &self.allocator);
}
}
}
#[derive(Debug)]
struct ImportRewritePlugin;
impl Plugin for ImportRewritePlugin {
fn name(&self) -> Cow<'static, str> {
"import-rewrite".into()
}
fn register_hook_usage(&self) -> HookUsage {
HookUsage::Transform
}
fn transform<'a>(
&'a self,
_ctx: &'a PluginContext<'_>,
args: TransformArgs,
) -> impl Future<Output = TransformReturn> + Send + 'a {
async move {
let mut ast: Ast = args.ast;
ast.with_mut(|allocator: &Allocator, program: &mut Program<'_>| {
let mut rewriter: ImportRewriter<'_> =
ImportRewriter { allocator };
walk_mut::walk_program(&mut rewriter, program);
});
Ok(Some(TransformOutput { ast }))
}
}
}Last updated on
Copyright © 2026, Alpheus