# Components & Preprocessing As part of the rendering lifecycle, `mancha` first preprocesses the HTML. This includes resolving includes and registering custom components. ## Includes You can mix and match content by processing `` tags: ```html
``` ### Attribute Forwarding Attributes placed on the `` tag are automatically copied to the root element of the included content. This is useful for styling and accessibility: ```html ``` This pattern works well for SVG icons that use `stroke="currentColor"` or `fill="currentColor"`, allowing you to control the icon color via CSS utility classes like `text-blue-500`. ### Remote Includes You can include content from remote URLs: ```html ``` ### Dynamic Icons with SVG Sprites For reusable icon components where the icon name is determined at usage time, use SVG sprites with a custom component: ```html ``` The `:data` attribute passes variables to the component's scope, making `name` available in expressions like `:attr:href`. SVG sprite sheets bundle multiple icons into a single file, with each icon defined as a ``: ```html ``` This approach loads the sprite sheet once and references individual icons by their `id`, making it efficient for pages with many icons. ## Custom Components `mancha` supports custom components, which can be defined using the template tag. ```html Click Me ``` ### Component Registries The components can live in their own, separate files. A common pattern is to separate each component into their own file, and import them all in a single "roll-up" file. ``` src/ ├─ components/ | ├─ footer.tpl.html | ├─ my-red-button.tpl.html | ├─ my-custom-component.tpl.html ├─ index.html ``` Instead of importing the components individually, you can create a single file `registry.tpl.html` which imports all the custom components: ```html ``` Then in `index.html`: ```html Click Me! ``` ## Initialization with `:render` The `:render` attribute provides **element-level initialization** by linking any HTML element to a JavaScript ES module. This is useful when you need to initialize third-party libraries (like charts, maps, or video players) on specific elements. > [!TIP] > `:render` is for **element-level** initialization, not page-level initialization. For page-level initialization, use the Script Tag with `init` or `initMancha()`. See [Initialization](./02_initialization.md) for a complete comparison of all initialization methods. ```html ``` The module's default export is called with the element and renderer: ```js // chart-init.js export default function (elem, renderer) { new Chart(elem, { type: "bar" }); } ``` ### Using with Custom Components The `:render` attribute works naturally inside custom component templates. This is the recommended pattern for creating reusable components that need JavaScript initialization: ``` src/ ├─ components/ │ ├─ chart-widget.tpl.html │ ├─ chart-widget.js │ ├─ registry.tpl.html ├─ index.html ``` ```html ``` ```js // components/chart-widget.js export default function (elem, renderer) { // Access data passed via :data attribute on the component const { labels, values } = renderer.$; new Chart(elem, { type: "bar", data: { labels: labels || ["A", "B", "C"], datasets: [{ data: values || [1, 2, 3] }], }, }); } ``` Relative paths like `./chart-widget.js` are automatically resolved based on where the template is defined (`/components/`), not where the component is used. ### Accessing Renderer State The init function receives the renderer instance, giving you access to reactive state: ```js // counter-canvas.js export default function (elem, renderer) { const ctx = elem.getContext("2d"); // Access current state const count = renderer.$.count; // Draw based on state ctx.fillText(`Count: ${count}`, 10, 50); // Watch for changes using the renderer's effect system renderer.effect(function () { ctx.clearRect(0, 0, elem.width, elem.height); ctx.fillText(`Count: ${this.$.count}`, 10, 50); }); } ``` ### Server-Side Rendering Compatibility during server-side rendering (SSR), the `:render` attribute's path is resolved but the JavaScript module is not executed. The module is only executed when the HTML is hydrated in the browser, making this feature fully compatible with SSR workflows. ### Setting Undefined Variables A powerful pattern is using `:render` to set variables that are already referenced in your template but not pre-defined. ```html

{{ pageTitle }}

  • {{ item.name }}: {{ item.value }}

Loading...

``` ```js // data-loader.js export default async function (elem, renderer) { // Set loading state. await renderer.set("loading", true); // Fetch data from an API. const response = await fetch("/api/data"); const data = await response.json(); // Set the variables - the template will reactively update. await renderer.set("pageTitle", data.title); await renderer.set("dataItems", data.items); await renderer.set("loading", false); } ``` You can set multiple variables concurrently with `Promise.all([renderer.set("a", 1), renderer.set("b", 2)])`.