DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Mounting Your First Application

Project Code:https://github.com/euv-dev/euv

The mount function is the entry point of every euv application. It connects your Rust code to the browser's DOM and kicks off the rendering pipeline. This article explores how mounting works, how to structure your application entry point, and how the rendering engine keeps your UI up to date.

The Mount Function

At its simplest, mount takes two arguments: a CSS selector string and a function that returns a VirtualNode:

use euv::*;

fn app() -> VirtualNode {
    html! {
        div {
            h1 { "Hello, euv!" }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

The first argument "#app" is a CSS selector that identifies the DOM element where your application will be rendered. The second argument app is a function that produces the virtual DOM tree. When mount is called, euv:

  1. Finds the DOM element matching the selector
  2. Calls the app function to produce a VirtualNode
  3. Renders the virtual DOM tree into the real DOM
  4. Sets up the reactive system to re-render when state changes

Choosing a Mount Target

You can mount your application to any valid CSS selector:

// Mount to an element by ID
mount("#app", app);

// Mount to an element by class
mount(".main-content", app);

// Mount to an element by tag name
mount("body", app);
Enter fullscreen mode Exit fullscreen mode

The most common pattern is to use an ID selector targeting a dedicated <div> element in your HTML:

<div id="app"></div>
Enter fullscreen mode Exit fullscreen mode

This keeps your application contained and avoids conflicts with other content on the page.

Structuring the Application Entry Point

As your application grows, you'll want to organize your entry point into smaller, focused functions. A good pattern is to have your main app function delegate to sub-components:

use euv::*;

fn app() -> VirtualNode {
    html! {
        div {
            {header()}
            {main_content()}
            {footer()}
        }
    }
}

fn header() -> VirtualNode {
    html! {
        header {
            h1 { "My Application" }
        }
    }
}

fn main_content() -> VirtualNode {
    html! {
        main {
            p { "Welcome to euv!" }
        }
    }
}

fn footer() -> VirtualNode {
    html! {
        footer {
            p { "Copyright 2024" }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Each function returns a VirtualNode, making them composable building blocks. You can embed them inside the html! macro using curly braces {header()} to insert their output into the parent tree.

Mounting with Reactive State

The real power of mounting comes when you combine it with reactive signals. When a signal changes, euv automatically re-renders the affected parts of the DOM:

use euv::*;

fn app() -> VirtualNode {
    let count: Signal<i32> = use_signal(|| 0);

    html! {
        div {
            h1 { "Counter Example" }
            p { "Count: " {count} }
            button {
                onclick: move || { count.set(count.get() + 1); }
                "Increment"
            }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

Here, use_signal(|| 0) creates a reactive signal initialized to 0. The {count} syntax in the html! macro automatically subscribes to the signal, so when count.set() is called through the button's onclick handler, euv re-renders the affected text node.

Understanding the Rendering Pipeline

When mount is called, euv sets up the following pipeline:

  1. Initial Render — The app function is called to produce the initial VirtualNode tree, which is then rendered to the DOM.
  2. Reactive Tracking — During rendering, euv tracks which signals are read. This creates a dependency graph.
  3. Diffing — When a signal changes, euv calls the app function again to produce a new VirtualNode tree, then compares it with the previous tree using its diffing algorithm.
  4. Patching — Only the differences between the old and new trees are applied to the real DOM. This is what makes euv efficient.

The renderer uses incremental rendering to avoid unnecessary work. It also employs Keyed Diffing for list rendering, which uses keys to track individual items across renders, minimizing DOM operations.

The VirtualNode Type

Understanding the VirtualNode variants helps you reason about what your app function produces:

  • Element — Represents an HTML element with a tag name and children. Created by standard HTML tags in the html! macro.
  • Text — Represents a plain text node. Created by string content inside elements.
  • Fragment — A collection of nodes without a wrapper element. Useful for returning multiple nodes from a function.
  • Dynamic — A node whose content can change based on reactive state. Created when you embed signals in the html! macro.
  • Empty — Renders nothing. Useful for conditional rendering when you want to show nothing.

The Tag enum distinguishes between standard HTML elements and custom components:

  • Element(String) — A standard HTML tag like "div" or "span"
  • Component(String) — A custom component name

Mounting Multiple Applications

You can mount multiple euv applications on the same page, each targeting a different DOM element:

use euv::*;

fn nav_app() -> VirtualNode {
    html! {
        nav {
            a { href: "#home", "Home" }
            a { href: "#about", "About" }
        }
    }
}

fn content_app() -> VirtualNode {
    html! {
        main {
            h1 { "Welcome" }
            p { "This is the main content area." }
        }
    }
}

mount("#navigation", nav_app);
mount("#content", content_app);
Enter fullscreen mode Exit fullscreen mode

This pattern is useful for gradually migrating an existing page to euv or for creating independent widgets that don't share state.

Using Lifecycle Hooks

euv provides hooks for running code at specific points in a component's lifecycle:

  • use_cleanup — Register a cleanup function that runs when the component is unmounted
  • use_window_event — Listen to window-level events
  • use_interval — Set up a recurring timer
use euv::*;

fn app() -> VirtualNode {
    use_cleanup(move || {
        // This runs when the component is unmounted
    });

    use_window_event("hashchange", move || {
        // This runs when the URL hash changes
    });

    let handle: IntervalHandle = use_interval(1000, move || {
        // This runs every 1000 milliseconds
    });

    html! {
        div {
            p { "Application with lifecycle hooks" }
            button {
                onclick: move || { handle.clear(); }
                "Stop Timer"
            }
        }
    }
}

mount("#app", app);
Enter fullscreen mode Exit fullscreen mode

The use_interval function returns an IntervalHandle with a clear() method to stop the timer. The use_window_event function takes an event name and a closure, making it easy to respond to browser events like "hashchange".

Best Practices for Mounting

  1. Keep the mount call simple — The mount call should be a single line at the top level of your entry file. Avoid putting logic inside the mount arguments.

  2. Use a dedicated mount element — Always have a dedicated <div id="app"> (or similar) in your HTML. This prevents euv from accidentally modifying other parts of the page.

  3. Separate concerns — Keep your app function focused on composition. Delegate specific UI sections to separate functions.

  4. Initialize signals at the top level — Create your signals at the beginning of the component function, before the html! macro call. This makes the data flow clear.

  5. Use batch for multiple updates — When you need to update multiple signals at once, wrap them in a batch call to avoid unnecessary intermediate renders:

batch(|| {
    count.set(1);
});
Enter fullscreen mode Exit fullscreen mode

Summary

The mount function is the bridge between your Rust code and the browser DOM. It takes a CSS selector and a component function, renders the initial UI, and sets up the reactive system for automatic updates. By understanding how mounting works and following the patterns described in this article, you can build well-structured euv applications that are easy to maintain and extend.


Project Code:https://github.com/euv-dev/euv

Top comments (0)