{"id":25558914,"url":"https://github.com/davewm/larch","last_synced_at":"2025-04-12T03:51:05.173Z","repository":{"id":62433280,"uuid":"87563192","full_name":"DaveWM/larch","owner":"DaveWM","description":"An Elm-like ClojureScript framework","archived":false,"fork":false,"pushed_at":"2019-02-20T12:29:45.000Z","size":97,"stargazers_count":38,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T23:23:48.647Z","etag":null,"topics":["cljs","clojurescript","elm-architecture"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DaveWM.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-07T15:57:02.000Z","updated_at":"2023-02-01T01:00:22.000Z","dependencies_parsed_at":"2022-11-01T21:00:53.192Z","dependency_job_id":null,"html_url":"https://github.com/DaveWM/larch","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveWM%2Flarch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveWM%2Flarch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveWM%2Flarch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaveWM%2Flarch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DaveWM","download_url":"https://codeload.github.com/DaveWM/larch/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248514209,"owners_count":21116899,"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":["cljs","clojurescript","elm-architecture"],"created_at":"2025-02-20T16:26:17.976Z","updated_at":"2025-04-12T03:51:05.150Z","avatar_url":"https://github.com/DaveWM.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Larch (Alpha)  [![Clojars Project](https://img.shields.io/clojars/v/larch.svg)](https://clojars.org/larch)\n\n![The Larch](https://s-media-cache-ak0.pinimg.com/originals/17/ff/7f/17ff7f207250309896e0d1f859c9ed41.jpg)\n\nLarch is a very minimal SPA \"framework\", loosely following the Elm architecture. The main goal of Larch is to provide an easy-to-use, simple, and testable framework for web apps. Handling asynchronous operations is a high priority. Performance is a lesser concern - if performance is an issue, please check out [Om](https://github.com/omcljs/om) or [Re-Frame](https://github.com/Day8/re-frame).\n\n## Overview\n\nThe [Elm architecture](https://guide.elm-lang.org/architecture/) has 3 separate concerns: model, update and view. See [here](https://dennisreimann.de/articles/elm-architecture-overview.html) for a good explanation.\n\nLarch is only concerned with the \"update\" part of this. The way it does this is slightly different to Elm, but is conceptually quite similar. The overall flow goes like this:\n\n1. Every time an event happens that we care about (e.g. button click, browser window resize, etc.), we put a \"message\" on a channel.\n2. Larch takes this channel, and transforms each message into an \"update\" - a data representation of an update to the model. We'll go into how this done later on.\n3. Each update is used to update the model. For example, if our model is just a number, and we get an `:increment` update, we just increment our model.\n4. The view is rendered from the new model value.\n\n![Overall architecture](images/larch-architecture.png)\n\nNotes:\n* To make step 3 easier, it is recommended that you use [DataScript](https://github.com/tonsky/datascript) for your model, because then \"updates\" are just DataScript transactions. This isn't an absolute necessity though, Larch only assumes that your model is an atom.\n* Larch is unopinionated about how you render your view, you can use any library that can render html from your model.\n\n## So how does Larch work?\n\nLarch's only concern is to transform a channel of messages into a channel of updates (step 2 above). This transformation is easy when it is a pure, synchronous function. In fact, we could just use the `core.async` `pipeline` function on the message channel. However it becomes more difficult to manage, and test, when messages may trigger asynchronous actions (such as http requests), or need to interact with the (mutable) browser state (e.g. local storage). This is where Larch can help you. \n\nLarch tackles this problem in a similar way to Elm. You supply a `process-msg` function to Larch, which is similar to the `update` function in Elm. This function takes a message and the current model value, and returns a tuple of `[update, command]`. Since updates are data structures, the transformation from message to update is a pure, synchronous function. A \"command\" is an impure or async function, that returns a channel of messages. All your impure and asynchronous operations should be done here. The channel returned from a command is fed back in to the main message channel.\n\nCommands take 2 parameters: the current model value, and a map of dependencies. You provide these \"dependencies\" to Larch. All impure functions that commands call should be in this map - this makes commands testable. \n\nThis diagram illustrates the overall process:\n\n![msgs-\u003eupdates! diagram](images/larch-msgs-updates.png)\n\n## Sounds great, so how do I get going with Larch?\n\nFirst, add the dependency `[larch \"0.1.0\"]`.\n\nLarch consists of just a single function, `msgs-\u003eupdates!`, in the `larch.core` namespace. This basically transforms a channel of messages to a channel of updates, as described above. It takes the following arguments:\n\n* `msg-chan` - a channel of all the messages that your app cares about. These messages may be triggered by user interaction, or other global events (loss of internet connection, time changes, etc.). These events can be in any format whatsoever (e.g. strings, keywords, tuples), but it is recommended to use the format `[message-type payload...]`.\n* `model` - an atom containing the entire state of your app.\n* `dependencies` - a map containing any impure functions and references to mutable objects that your app needs. For example, if your app needs to make http requests, and also update local storage, your dependencies may look like: `{:fetch js/fetch :local-storage js/localStorage}`. These are provided to command functions.\n* `process-msg` - a function which takes an event and the model value (i.e. the value of the `model` atom), and returns a tuple of `[update, command]`. Both elements in the tuple are optional - if they are `nil` they will be ignored.\n\n## That sounds good, now how do I update my model?\n\nUpdating the model is easiest if you use DataScript. Each update will be a transaction, so you just have run `transact!` for each update. For example:\n\n``` clojure\n(go-loop []\n  (let [update (\u003c! updates-channel)]\n    (datascript.core/transact! model update)\n    (recur)))\n```\n\nYou can take a similar approach if your model is a standard Clojure data structure, but you will effectively have to implement `transact!` yourself, to update the model based on the update.\n\n## So far so good, but I'm still not seeing anything rendered...\n\nRendering the view depends on which library you use to render your html. If you're using Reagent, then you just have to re-render your root component on every model update, passing it the model value. You can do this by adding a watch to the model:\n\n``` clojure\n(defn reload []\n  (reagent/render [root-component @app-model]\n                  (.getElementById js/document \"app\")))\n\n(add-watch app-model :render reload)\n```\n\nThe same approach should work for all react-based view libraries.\n\n## Just one more thing - how we handle user interaction?\n\nLarch doesn't make any assumptions about how messages are actually created - all it knows about is the message channel. Therefore, we just need to somehow set up event handlers which `put!` messages onto the message channel. How you do this will depend on which view library you're using, but let's consider how it would work using Reagent.\n\nSuppose we want to do something when the user clicks the submit button in the `my-awesome-form` component. On the click event, we'll emit a `:my-awesome-form/submit` message. To do this, we pass the message channel to our root component, then down to the `my-awesome-form` component. The `my-awesome-form` component then puts a new message on the channel in the button click event, like so:\n\n``` clojure\n(defn my-awesome-form [message-chan]\n  [:form\n    [:button {:on-click #(go (put! message-chan [:my-awesome-form/submit %]))}]])\n```\n\n## Examples\n\nFor a reference example, please see [monzo-cljs](https://github.com/DaveWM/monzo-cljs).\n\n## License\n\nDistributed under the GPL V3 license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavewm%2Flarch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavewm%2Flarch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavewm%2Flarch/lists"}