{"id":13753729,"url":"https://github.com/maciejhirsz/kobold","last_synced_at":"2025-05-15T01:05:40.871Z","repository":{"id":37006009,"uuid":"223939380","full_name":"maciejhirsz/kobold","owner":"maciejhirsz","description":"Easy declarative web interfaces.","archived":false,"fork":false,"pushed_at":"2025-04-13T11:49:20.000Z","size":1150,"stargazers_count":394,"open_issues_count":8,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-13T22:39:36.974Z","etag":null,"topics":["declarative","rust","ui","wasm","wasm-bindgen"],"latest_commit_sha":null,"homepage":"https://docs.rs/kobold/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maciejhirsz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-11-25T12:06:00.000Z","updated_at":"2025-03-30T01:36:04.000Z","dependencies_parsed_at":"2023-11-08T03:37:32.663Z","dependency_job_id":"ac693bd6-786c-4b95-bd5f-017c1f360b5e","html_url":"https://github.com/maciejhirsz/kobold","commit_stats":{"total_commits":358,"total_committers":4,"mean_commits":89.5,"dds":0.06703910614525144,"last_synced_commit":"fe288137b0ba620ac99e64d3f24457633a9aa376"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejhirsz%2Fkobold","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejhirsz%2Fkobold/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejhirsz%2Fkobold/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejhirsz%2Fkobold/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maciejhirsz","download_url":"https://codeload.github.com/maciejhirsz/kobold/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254254039,"owners_count":22039792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["declarative","rust","ui","wasm","wasm-bindgen"],"created_at":"2024-08-03T09:01:28.304Z","updated_at":"2025-05-15T01:05:40.850Z","avatar_url":"https://github.com/maciejhirsz.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# Kobold\n\n\u003cimg src=\"https://raw.githubusercontent.com/maciejhirsz/kobold/master/kobold.svg?sanitize=true\" alt=\"Kobold logo\" width=\"250\" align=\"right\" style=\"max-width: 40vw;\"\u003e\n\n[![Join Discord](https://img.shields.io/discord/1092732324610850816?label=\u0026logo=discord\u0026labelColor=6A7EC2\u0026logoColor=ffffff\u0026color=7389D8)](https://discord.gg/tgNX5VYSWF)\n[![Test](https://img.shields.io/github/actions/workflow/status/maciejhirsz/kobold/ci.yml?label=tests)](https://github.com/maciejhirsz/kobold/actions/workflows/ci.yml)\n[![Docs](https://img.shields.io/docsrs/kobold/latest)](https://docs.rs/kobold)\n[![Crates.io](https://img.shields.io/crates/v/kobold.svg)](https://crates.io/crates/kobold)\n[![MPL-2.0](https://img.shields.io/crates/l/kobold.svg?label=)](https://www.mozilla.org/en-US/MPL/)\n\n_Easy declarative web interfaces._\n\nKey features:\n\n* Declarative [`view!`](https://docs.rs/kobold/latest/kobold/macro.view.html) macro that uses HTML-esque syntax complete with optional closing tags.\n* Functional [components](https://docs.rs/kobold/latest/kobold/attr.component.html) with optional parameters.\n* State management and event handling.\n* High performance and consistently the lowest Wasm footprint in the Rust ecosystem.\n\n### Zero-Cost Static HTML\n\nThe `view!` macro produces opaque `impl View` types that by default do no allocations.\nAll static [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) elements compile to\ninline JavaScript code that constructs them. Expressions are injected into the constructed DOM on first render.\nKobold keeps track of the DOM node references for these expressions.\n\nSince the exact types the expressions evaluate to are known to the Rust compiler, update calls can diff them by\nvalue (or pointer) and surgically update the DOM should they change. Changing a\nstring or an integer only updates the exact [`Text` node](https://developer.mozilla.org/en-US/docs/Web/API/Text)\nthat string or integer was rendered to.\n\n_If the `view!` macro invocation contains DOM elements with no expressions, the constructed `View`\ntype will be zero-sized, and its `View::update` method will be empty, making updates of static\nDOM literally zero-cost._\n\n### Hello World!\n\nComponents in **Kobold** are created by annotating a _render function_ with a `#[component]` attribute.\n\n```rust\nuse kobold::prelude::*;\n\n#[component]\nfn hello(name: \u0026str) -\u003e impl View + '_ {\n    view! {\n        \u003ch1\u003e\"Hello \"{ name }\"!\"\u003c/h1\u003e\n    }\n}\n\nfn main() {\n    kobold::start(view! {\n        \u003c!hello name=\"Kobold\"\u003e\n    });\n}\n```\n\nThe component function must return a type that implements the `View` trait. Since the `view!` macro\nproduces transient locally defined types the best approach here is to always use the opaque `impl View` return type.\n\nEverything here is statically typed and the macro doesn't delete any information when manipulating the\ntoken stream, so the Rust compiler can tell you when you've made a mistake:\n\n```text\nerror[E0560]: struct `Hello` has no field named `nam`\n  --\u003e examples/hello_world/src/main.rs:12:16\n   |\n12 |         \u003c!hello nam=\"Kobold\"\u003e\n   |                 ^^^ help: there is a method with a similar name: `name`\n```\n\nYou can even use [rust-analyzer](https://rust-analyzer.github.io/) to refactor component or field names,\nand it will change the invocations inside the macros for you.\n\n### State management\n\nThe `stateful` function can be used to create views that own and manipulate\ntheir state:\n\n```rust\nuse kobold::prelude::*;\n\n#[component]\nfn counter(init: u32) -\u003e impl View {\n    stateful(init, |count| {\n        bind! { count:\n            // Create an event handler with access to `\u0026mut u32`\n            let onclick = move |_event| *count += 1;\n        }\n\n        view! {\n            \u003cp\u003e\n                \"You clicked the \"\n                // `{onclick}` here is shorthand for `onclick={onclick}`\n                \u003cbutton {onclick}\u003e\"Button\"\u003c/button\u003e\n                \" \"{ count }\" times.\"\n            \u003c/p\u003e\n        }\n    })\n}\n\nfn main() {\n    kobold::start(view! {\n        \u003c!counter init={0}\u003e\n    });\n}\n```\n\nThe `stateful` function takes two parameters:\n\n* State constructor that implements the `IntoState` trait. **Kobold** comes with default\n  implementations for most primitive types, so we can use `u32` here.\n* The anonymous render closure that uses the constructed state, in our case its argument is `\u0026Hook\u003cu32\u003e`.\n\nThe `Hook` here is a smart pointer to the state itself that allows non-mutable access to the\nstate. The `bind!` macro can be invoked for any `Hook` to create closures with `\u0026mut` references to the\nunderlying state.\n\nFor more details visit the [`stateful` module documentation](https://docs.rs/kobold/latest/kobold/stateful/index.html).\n\n### Optional parameters\n\nUse `#[component(\u003cparam\u003e?)]` syntax to set a component parameter as default:\n\n```rust\n// `code` will default to `200` if omitted\n#[component(code?: 200)]\nfn status(code: u32) -\u003e impl View {\n    view! {\n        \u003cp\u003e \"Status code was \"{ code }\n    }\n}\n\nview! {\n    // Status code was 200\n    \u003c!status\u003e\n    // Status code was 404\n    \u003c!status code={404}\u003e\n}\n```\n\nFor more details visit the [`#[component]` macro documentation](https://docs.rs/kobold/latest/kobold/attr.component.html#optional-parameters-componentparam).\n\n### Conditional Rendering\n\nBecause the `view!` macro produces unique transient types, `if` and `match` expressions that invoke\nthe macro will naturally fail to compile.\n\nUsing the `auto_branch` flag on the `#[component]` attribute\n**Kobold** will scan the body of of your component render function, and make all `view!` macro invocations\ninside an `if` or `match` expression, and wrap them in an enum making them the same type:\n\n```rust\n#[component(auto_branch)]\nfn conditional(illuminatus: bool) -\u003e impl View {\n    if illuminatus {\n        view! { \u003cp\u003e \"It was the year when they finally immanentized the Eschaton.\" }\n    } else {\n        view! { \u003cblockquote\u003e \"It was love at first sight.\" }\n    }\n}\n```\n\nFor more details visit the [`branching` module documentation](https://docs.rs/kobold/latest/kobold/branching/index.html).\n\n### Lists and Iterators\n\nTo render an iterator use the `for` keyword:\n\n```rust\nuse kobold::prelude::*;\n\n#[component]\nfn iterate_numbers(count: u32) -\u003e impl View {\n    view! {\n        \u003cul\u003e\n        {\n            for (1..=count).map(|n| view! { \u003cli\u003e \"Item #\"{n} })\n        }\n    }\n}\n```\n\nOn updates the iterator is consumed once and all items are diffed with the previous version.\nNo allocations are made by **Kobold** when updating such a list, unless the rendered list needs\nto grow past its original capacity.\n\nFor more information about keywords visit the [`keywords` module documentation](https://docs.rs/kobold/latest/kobold/keywords/index.html).\n\n### Borrowed Values\n\n`View` types are truly transient and only need to live for the duration of the initial render,\nor for the duration of the subsequent update. This means that you can easily and cheaply render borrowed\nstate without unnecessary clones:\n\n```rust\n#[component]\nfn users\u003c'a\u003e(names: \u0026'a [\u0026'a str]) -\u003e impl View + 'a {\n    view! {\n        \u003cul\u003e\n        {\n            for names.iter().map(|name| view! { \u003cli\u003e { name } })\n        }\n    }\n}\n```\n\n### Components with Children\n\nIf you wish to capture children from parent `view!` invocation, simply change\n`#[component]` to `#[component(children)]`:\n\n```rust\nuse kobold::prelude::*;\n\n#[component(children)]\nfn header(children: impl View) -\u003e impl View {\n    view! {\n        \u003cheader\u003e\u003ch1\u003e{ children }\u003c/h1\u003e\u003c/header\u003e\n    }\n}\n\nfn main() {\n    kobold::start(view! {\n        \u003c!header\u003e\"Hello Kobold\"\u003c/!header\u003e\n    });\n}\n```\n\nYou can change the name of the parameter used and even set it to a concrete:\n\n```rust\nuse kobold::prelude::*;\n\n// Capture children into the argument `n`\n#[component(children: n)]\nfn add_ten(n: i32) -\u003e i32 {\n    // integers implement `View` so they can be passed by value\n    n + 10\n}\n\nfn main() {\n    kobold::start(view! {\n        \u003cp\u003e\n            \"Meaning of life is \"\n            \u003c!add_ten\u003e{ 32 }\u003c/!add_ten\u003e\n        \u003c/p\u003e\n    });\n}\n```\n\n## More Examples\n\nTo run **Kobold** you'll need to install [`trunk`](https://trunkrs.dev/):\n```sh\ncargo install --locked trunk\n```\n\nYou might also need to add the Wasm target to Rust:\n```sh\nrustup target add wasm32-unknown-unknown\n```\n\nThen just run an example:\n```sh\n## Go to an example\ncd examples/todomvc\n\n## Run with trunk\ntrunk serve\n```\n\n## Acknowledgements\n\n+ [Pedrors](https://pedrors.pt/) for the **Kobold** logo.\n\n## License\n\nKobold is free software, and is released under the terms of the [Mozilla Public License](https://www.mozilla.org/en-US/MPL/) version 2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaciejhirsz%2Fkobold","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaciejhirsz%2Fkobold","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaciejhirsz%2Fkobold/lists"}