Skip to main content

Node

Enum Node 

Source
pub enum Node {
Show 25 variants Text(String), Code(String), Interpolate { expr: String, escape: bool, }, Js(String), If { cond: String, then_branch: Vec<Node>, else_branch: Vec<Node>, }, Foreach { binding: String, iter: String, body: Vec<Node>, }, Extends(String), Section { name: String, body: Vec<Node>, }, Yield(String), Push { name: String, body: Vec<Node>, }, Stack(String), Csrf, Global { name: String, fallback: Option<String>, }, PersistGlobal { cookie_name: String, fallback_expr: String, }, Globals(Vec<GlobalEntry>), Wire { name: String, props: Vec<(String, String)>, }, LarustScripts, LoadOnce(Vec<Node>), Resource { name: String, props: Vec<(String, String)>, slot: Vec<Node>, }, Live { channel: String, body: Vec<Node>, }, Can { permission: String, then_branch: Vec<Node>, else_branch: Vec<Node>, }, Role { role: String, then_branch: Vec<Node>, else_branch: Vec<Node>, }, Vitex(Vec<String>), Spa(Vec<Node>), Image { props: Vec<(String, String)>, },
}

Variants§

§

Text(String)

§

Code(String)

@code ... @endcode - trusted, inline Rust statements executed in the generated view function. It renders no output itself; use it for small local derivations consumed by later interpolations. This is a Rust escape hatch, never a PHP compatibility layer.

§

Interpolate

Fields

§expr: String
§escape: bool

{{ }} (HTML-escaped) vs {!! !!} (raw).

§

Js(String)

@js($expr) - serializes an arbitrary Serialize Rust value into JS-safe (not HTML-escaped) inline output, for dropping server data into an inline <script> block. A separate variant from Interpolate rather than a third state on its escape: bool: JS-safe JSON escaping is a fundamentally different transform than HTML-escaping, not a variation on it. See larust_view::runtime::js for the actual encoding (mirrors Laravel’s Illuminate\Support\ Js::from()).

§

If

Fields

§cond: String
§then_branch: Vec<Node>
§else_branch: Vec<Node>
§

Foreach

Fields

§binding: String

Any valid Rust pattern - a single identifier (post), or a tuple pattern ((key, item)) for keyed iteration, e.g. @foreach((key, item) in items.iter().enumerate()). Stored raw, same convention as iter/cond/expr elsewhere in this enum - larust-macros parses it as a real syn::Pat at codegen time (M4; a single bare identifier was the only M3 shape).

§iter: String
§body: Vec<Node>
§

Extends(String)

§

Section

Fields

§name: String
§body: Vec<Node>
§

Yield(String)

§

Push

@push('name') ... @endpush - unlike @section (single body, last-write-wins), multiple @pushes to the same name accumulate; see larust_view::resolve for how they’re collected across an @extends chain and spliced into the matching @stack.

Fields

§name: String
§body: Vec<Node>
§

Stack(String)

@stack('name') - renders every @push body pushed to that name, concatenated in the order they were collected. Laravel’s own pairing: @stack sits in the layout (usually <head> or right before </body>), @push calls live in whichever child templates have page-specific <script>/<meta>/etc. to contribute.

§

Csrf

@csrf - a hidden CSRF-token input field. Expands to a fixed <input> reading a csrf_token variable that must be present in the view’s context (a compile-time-checked convention, same as any other undeclared-variable-becomes-an-error interpolation).

§

Global

@global(name) or @global(name, fallback) - a page-overridable named placeholder, usually in a layout (<title>@global(title, "Larust")</title>). Single value, last-write-wins per name across the whole @extends chain - see Globals and larust_view::resolve for how a page sets it and why resolution is a whole-chain collect-then-substitute pass (same shape as Push/Stack), not a per-level one (unlike Section/Yield). fallback, like Globals’ own expr values, is a raw Rust expression string, used only when no @globals block anywhere in the chain sets name - if absent, an unset global renders empty, same convention as an unset @stack.

Fields

§name: String
§fallback: Option<String>
§

PersistGlobal

Substituted in by resolve::substitute_globals in place of an ordinary Global/Interpolate, for a persist-flagged @globals entry - see docs/MACROS.md‘s persist section for the full design. Unlike every other Global substitution, this one’s actual value isn’t knowable at compile time (it’s whatever cookie the current request carries, not a fixed expression), so it needs its own codegen arm emitting real runtime code - the same reason Wire gets its own arm instead of collapsing to Interpolate. cookie_name is the bare preference name (e.g. "theme", not the larust_pref_theme cookie name itself - that prefixing is larust_http::preferences’ own concern); fallback_expr is the @globals entry’s own raw expression, used when the browser never set that cookie.

Fields

§cookie_name: String
§fallback_expr: String
§

Globals(Vec<GlobalEntry>)

@globals ... @endglobals - one or more name = expr assignment lines on a page; each overrides the matching @global(name) placeholder anywhere in whichever layout(s) this page’s @extends chain reaches. expr is stored raw (same convention as Interpolate::expr) and is substituted into a real Interpolate node at resolve time, so it’s a genuine, type-checked Rust expression - any type implementing ToString, not just string literals. A persist-prefixed entry (persist theme = "dark") substitutes to Node::PersistGlobal instead - see its own doc comment for why that needs a different substitution target.

§

Wire

@wire('name') or @wire('name', { prop: expr, ... }) - a mount point for a server-state-backed reactive component (see larust-live; the crate keeps its original name, only the user-facing directive/trait/route surface renamed from @live to @wire, freeing @live/@endlive for a future genuinely server-pushed live-update feature - see docs/ARCHITECTURE.md). props are raw Rust expression strings, same convention as Globals’ (name, expr) pairs, evaluated and JSON-encoded at codegen time in larust-macros. Unlike Section/Push/Global, this node needs no resolve.rs pass at all - it renders positionally wherever it appears (including inside If/Foreach, deliberately unrestricted, since mounting is an ordinary runtime statement, not a compile-time collection pass).

Fields

§name: String
§props: Vec<(String, String)>
§

LarustScripts

@larustscripts - Livewire’s @livewireScripts equivalent: a layout-placed marker (conventionally right before </body>) that expands to the <script src="/__larust_wire/runtime.js" defer> tag only if this page actually mounts a @wire(...) component somewhere in its resolved tree (itself or, via @extends, the layout it’s rendered through) - otherwise it expands to nothing. Written once in a shared layout so no individual page needs its own <script> tag for wire:model/wire:click/wire:submit to work; see larust_macros::view::expand’s contains_wire check for how “only if” is decided at compile time, not a runtime branch.

§

LoadOnce(Vec<Node>)

@loadonce ... @endloadonce - sugar for wrapping body in a <div wire:ignore>...</div>. Content inside still renders on every server response (including every @wire(...) component re-render fragment) - the “once” is enforced client-side, by wire:ignore telling the DOM patcher to skip that subtree entirely after its first mount, not by the server omitting it. That distinction matters: if the server instead omitted this block from later fragments, the client’s positional child diffing (see larust-live/assets/wire-runtime.js) would misalign every sibling after it - this node exists specifically so callers get the “contained, load-once” ergonomics without hand-writing wire:ignore and without that correctness trap. Safe to use even outside a @wire(...) template: wire:ignore is simply inert markup on a page with no client runtime patching it.

§

Resource

@resource('name', { prop: expr, ... }) ... @endresource - a static, non-reactive, slot-capable template inclusion. Laravel’s real split: @wire(...) is the Livewire-equivalent reactive component (session state, AJAX round-trip); @resource(...) is the Blade-component equivalent (@component/@endcomponent) - props plus a slot, resolved once at render time, no session storage, no client JS, no round-trip at all.

Always a block (@endresource required, even with nothing between the tags) - deliberately unlike @wire(...)’s self-closing form, to avoid the parser needing lookahead to decide whether a body follows; matches Blade’s own @component/@endcomponent always-paired shape.

props become real let bindings in the included template’s own scope at codegen time (see larust-macros), not a serialized HashMap the way @wire(...)’s props are - there’s no session/JSON boundary to cross here, so this can just lean on Rust’s own type system directly, no serde_json round-trip needed at all. slot holds @resource(...)’s captured body exactly as written in the caller’s own template (its expressions still resolve against the caller’s own scope, not the included template’s) - codegen renders it first, into a plain String, and binds that as a slot variable the included template can place anywhere via the existing {!! slot !!} raw-interpolation mechanism. No new “slot placeholder” AST concept needed on the receiving side at all - it’s just another variable.

Fields

§name: String
§props: Vec<(String, String)>
§slot: Vec<Node>
§

Live

@live(channel_expr) ... @endlive - genuine server-pushed real-time updates (see larust-live::push’s own module doc for the full design), the third and last of this framework’s three template-inclusion directives: @wire(...) is client-initiated reactive (a user’s own action triggers an AJAX round-trip); @resource(...) is static compile-time inclusion (props + slot, no server round-trip at all); @live(...) is server-initiated push (a server-side event updates every currently-connected viewer, with no interaction required in any of their tabs - the live-chat/ live-notification case neither of the other two can express).

channel is a raw Rust expression (any ToString-yielding value), not a quoted string literal like @wire/@resource’s own name argument - parsed via parse_paren_expr, the same balanced-paren scanner @if/@foreach use. Unlike those two, there’s no compile-time file/registry lookup keyed on this value (a channel is just a runtime string handed to the broadcast/subscribe mechanism), so it can safely be dynamic - @live(format!("post.{}.comments", post.id)) scopes a channel per-resource, which a fixed literal couldn’t express.

body renders inline, once, at page-load time, in the caller’s own scope - same “no separate buffer, no runtime dispatch” shape @loadonce’s body has, not @wire’s stateful mount/render/call machinery. There’s no component struct here at all: the app is responsible for constructing new HTML (shaped the same way this node’s own codegen wraps body) and calling larust_support::push::broadcast whenever the state this channel represents actually changes - this node only handles the initial render and the <div data-live-channel="..."> wrapper the client runtime’s WebSocket patches into.

Fields

§channel: String
§body: Vec<Node>
§

Can

@can(expr) ... @endcan, optionally with @else ... @endcan - the larust_support::permission (backed by larust-permissions) template-level check that crate’s own doc comment names as “the one real gap this version leaves.” Renders then_branch if the request’s user currently has the permission expr names, else_branch (or nothing, with no @else) otherwise.

expr is a raw Rust expression - same convention as Node::Live’s own channel - not a quoted string literal like @wire/@resource’s name. This matters beyond consistency: it’s what carries larust_permissions’s own “names are compile-checked, assignment is not” hybrid design (see that crate’s module doc comment) all the way into templates. @can('edit-posts') would silently compile a typo’d permission name into an always-false string lookup; @can(Permission ::EditPosts) makes the same typo (EditPost, missing the s) a rustc error at the template’s own call site, the same guarantee every other Rust call in this codebase already has.

Requires a user: &U binding (U: Authenticatable) in the view! context, checked eagerly by larust-macros::view::expand - the same “requires a binding, checked eagerly, before codegen ever reaches this arm” shape Node::Wire’s own session requirement already establishes. Also like Node::Wire, this needs .await - the permission check is a real DB round trip, not a compile-time or pure-runtime-data lookup - so the view! call site must be an async fn returning a Result; a DB error while checking propagates as a real error (?), never silently rendered as “no permission” (matching this codebase’s “never silently swallow errors” convention - see larust_permissions::has_permission_to’s own Result-returning signature, which this codegen doesn’t downgrade to a bool-with-swallowed-error).

Fields

§permission: String
§then_branch: Vec<Node>
§else_branch: Vec<Node>
§

Role

@role(expr) ... @endrole, optionally with @else ... @endrole - Node::Can’s exact shape and reasoning, checking larust_support::permission::has_role instead of has_permission_to. expr is a raw Rust expression yielding a RoleName implementor (e.g. Role::Moderator), same “compile-check the name, not the assignment” reasoning as Node::Can’s own expr.

Fields

§role: String
§then_branch: Vec<Node>
§else_branch: Vec<Node>
§

Vitex(Vec<String>)

@vitex(['resources/css/app.css', 'resources/js/app.js']) - Larust’s own answer to Laravel’s @vite(...) directive: real integration with the app’s existing Vite/Tailwind toolchain (see larust_support::vitex’s own module doc for the full dev/ production dual-mode design), not a from-scratch bundler. Each entry is a plain string - the exact key Vite’s own manifest/dev server use - never a general expression, matching @vite’s own real syntax so a converted template reads the same way the original Laravel source did. No scope dependency at all (no session/csrf_token, unlike @wire/@csrf) - safe to use anywhere, including directly in WireComponent::render(&self).

§

Spa(Vec<Node>)

@spa ... @endspa - marks a layout’s single swappable SPA-navigation region. Renders body wrapped in a fixed sentinel <div id="__larust_spa_root">...</div>; the client runtime (larust-spa’s spa-runtime.js) replaces this div’s innerHTML wholesale on every intercepted same-origin link click or form submit, updates <title>, and pushes the new URL via the History API - see docs/MACROS.md’s @spa section for the full design.

Deliberately not @csrf’s bare, position-only shape, despite the directive’s own simple “just enable this” spirit: unlike @csrf, this needs to wrap a region (typically @yield('content')), and resolve.rs’s substitute_yields only knows how to replace a bare Node::Yield inline - there’s no “wrap this yield’s output” concept, and inventing one would be a bigger, more magic change than the feature warrants (it would also have to guess which yield is “the” content region, when the yield name is just a per-app convention, not hardcoded). So this follows the same open-body-close shape @loadonce/@resource/@live/@can/@role already use instead, letting the layout author place @spa/ @endspa explicitly around whatever they want swapped:

@spa
@yield('content')
@endspa

Because it has a body, resolve.rs’s substitute_yields (once Node::Spa is threaded through it, same as every other body-bearing node) reaches into that body and replaces the nested Node::Yield("content") exactly as it already does for Node::If/Node::Section bodies - this is what actually makes the design work, and is the concrete reason a bare tag doesn’t.

No scope dependency at all (no session/csrf_token/user, unlike @wire/@csrf/@can/@role) - it emits only static markup and (via @larustscripts) a static <script> tag, matching Node::Vitex’s own “safe to use anywhere” precedent. A resolved tree with more than one @spa block is a compile-time error (the sentinel id can’t be duplicated) - checked eagerly by larust-macros::view::expand’s count_spa scan.

§

Image

<Image src="..." width="..." height="..." alt="..." quality="..." /> - Larust’s own answer to Next.js’s <Image /> component; see larust-image’s own doc comment for the full design. Core framework surface, not a stand-in for an optional third-party Laravel package - the same tier as Node::Wire/Node::Spa/ Node::Vitex, never behind a Cargo feature flag.

props is the exact same (name, codegen-ready Rust expression) shape Node::Wire’s own props already use, straight from the same shared attribute parser (larust-view::parser::parse_tag_attrs). This node carries no special-cased fields of its own (no src/ width/height struct members), specifically so the parser needs no <Image>-specific attribute grammar at all. Validating which names are required/recognized, and coercing a plain numeric literal attribute (width="400", which the shared parser represents as the Rust string "400") back into a real integer expression, both happen in larust-macros’ own codegen for this node: a type- coercion concern, not a lexical-grammar one.

Fields

§props: Vec<(String, String)>

Trait Implementations§

Source§

impl Clone for Node

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Node

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Node

Source§

impl PartialEq for Node

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Node

Auto Trait Implementations§

§

impl Freeze for Node

§

impl RefUnwindSafe for Node

§

impl Send for Node

§

impl Sync for Node

§

impl Unpin for Node

§

impl UnsafeUnpin for Node

§

impl UnwindSafe for Node

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more