{"id":17320822,"url":"https://github.com/lexofleviafan/mreframe","last_synced_at":"2025-08-07T07:26:27.307Z","repository":{"id":57303821,"uuid":"346459187","full_name":"LeXofLeviafan/mreframe","owner":"LeXofLeviafan","description":"A reagent/re-frame imitation that uses Mithril instead","archived":false,"fork":false,"pushed_at":"2023-06-20T15:53:12.000Z","size":379,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-03-14T20:52:39.548Z","etag":null,"topics":["coffeescript","js","mithriljs","re-frame","reagent","wisp"],"latest_commit_sha":null,"homepage":"https://lexofleviafan.github.io/mreframe/","language":"CoffeeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LeXofLeviafan.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}},"created_at":"2021-03-10T18:50:57.000Z","updated_at":"2023-07-31T03:17:31.000Z","dependencies_parsed_at":"2024-10-15T13:34:06.304Z","dependency_job_id":"98669ea7-7c6b-4314-b264-9816262a5222","html_url":"https://github.com/LeXofLeviafan/mreframe","commit_stats":{"total_commits":20,"total_committers":1,"mean_commits":20.0,"dds":0.0,"last_synced_commit":"4405389bc0bf971a5e935893951a4309d1255724"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeXofLeviafan%2Fmreframe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeXofLeviafan%2Fmreframe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeXofLeviafan%2Fmreframe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeXofLeviafan%2Fmreframe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LeXofLeviafan","download_url":"https://codeload.github.com/LeXofLeviafan/mreframe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248907140,"owners_count":21181287,"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":["coffeescript","js","mithriljs","re-frame","reagent","wisp"],"created_at":"2024-10-15T13:34:03.510Z","updated_at":"2025-04-14T15:36:25.765Z","avatar_url":"https://github.com/LeXofLeviafan.png","language":"CoffeeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[Homepage](https://lexofleviafan.github.io/mreframe)\n[![Unit tests](https://github.com/LeXofLeviafan/mreframe/actions/workflows/test.yml/badge.svg)](https://github.com/LeXofLeviafan/mreframe/actions/workflows/test.yml)\n[![Performance tests](https://github.com/LeXofLeviafan/mreframe/actions/workflows/perftest.yml/badge.svg)](https://github.com/LeXofLeviafan/mreframe/actions/workflows/perftest.yml)\n\n`mreframe` is a plain JavaScript re-implementation of [`reagent`](http://reagent-project.github.io) and\n[`re-frame`](https://day8.github.io/re-frame) libraries from [ClojureScript](https://clojurescript.org);\nit's a mini-framework for single-page apps (using Mithril as the base renderer, with some interop).\n\n* _Lightweight_, both in size and use: just load a small JavaScript file, `require` it as a library, and you're good to go\n* _No language/stack requirement_ – you can use JS directly, or any language that transpiles into it as long as it has interop\n* _Simple, data-centric API_ using native JS data structures and plain functions for rendering, event handling, and querying state\n* Components, events and queries have _no need to expose their inner workings_ beyond the level of a simple function call\n* _Improved performance of re-rendering large, complex UI_ by preventing recalculation of unchanged subtrees\n\nInstall: `npm i mreframe`/`yarn add mreframe` or `\u003cscript src=\"https://unpkg.com/mreframe/dist/mreframe.min.js\"\u003e\u003c/script\u003e`.\n\nHere's a full app code example:\n\n```js\nlet {reagent: r, reFrame: rf} = require('mreframe');\n\n// registering events\nrf.regEventDb('init',        () =\u003e ({counter: 0}));  // initial app state\nrf.regEventDb('counter-add', (db, [_, n]) =\u003e ({...db, counter: db.counter + n}));\n\n// registering state queries\nrf.regSub('counter', db =\u003e db.counter);\n\n// component functions\nlet IncButton = (n, caption) =\u003e\n  ['button', {onclick: () =\u003e rf.dispatch(['counter-add', n])},  // invoking counter-add event on button click\n    caption];\n\nlet Counter = () =\u003e\n  ['main',\n    ['span.counter', rf.dsub(['counter'])],  // accessing app state\n    \" \",\n    [IncButton, +1, \"increment\"]];\n\n// initializing the app\nrf.dispatchSync(['init']);  // app state needs to be initialized immediately, before first render\nr.render([Counter], document.body);\n```\n\nTutorial / live demo: [Reagent (components)](https://lexofleviafan.github.io/mreframe/reagent.html),\n[re-frame (events/state management)](https://lexofleviafan.github.io/mreframe/re-frame.html).\n\n* [Intro](#intro)\n* [Usage](#usage)\n* [Q \u0026 A](#q--a)\n* [Examples](#examples)\n* [API reference](#api-reference)\n\n\n# Intro\n\n[ClojureScript](https://clojurescript.org) has a very good functional interface to React (as third party libraries),\nallowing one to [model DOM using data literals](https://github.com/weavejester/hiccup),\nto [define components as plain functions (or functions returning functions)](http://reagent-project.github.io),\nand to [make best use of pure functions when defining calculations and decision-making logic](https://day8.github.io/re-frame).\n\n[Wisp](https://github.com/Gozala/wisp) is a lightweight Lisp variant based on ClojureScript; however, it's harder to use for\nSPAs as there's no similar library available for it. `mreframe` is meant to deal with this issue; however, after some thinking,\nI've decided to make it a regular JS library instead (since Wisp would interop with it seamlessly anyway).\n\nTo minimize dependencies (and thus keep the library lightweight as well, as well as make it easy to use), `mreframe` uses\n[Mithril](https://mithril.js.org) in place of React; it also has no other runtime dependencies. In current version, it has size\nof 10Kb (4Kb gzipped) by itself, and with required Mithril submodules included it merely goes up to 26Kb (9.5Kb gzipped).\n\nThe library includes two main modules: [`reagent`](docs/reagent.md) (function components modelling DOM with data literals),\nand [`re-frame`](docs/re-frame.md) (state/side-effects management). You can decide to only use one of these as they're mostly\nindependent of each other (although `re-frame` uses `reagent` atoms internally to trigger redraws on state updates). It also\nincludes [`atom`](docs/atom.md) module for operating state (though you can avoid operating state atoms directly), as well as\n[`util`](docs/util.md) module for non-mutating data updates (these were implemented internally to avoid external dependencies).\n\nBoth `reagent` and `re-frame` were implemented mostly based on their\n[`reagent.core`](http://reagent-project.github.io/docs/master/reagent.core.html) and\n[`re-frame.core`](https://day8.github.io/re-frame/api-intro) APIs respectively, with minor changes to account for the switch\nfrom ClojureScript to JS and from React to Mithril. The most major change would be that since Mithril relies on minimizing\ncalculations rather than keeping track of dependency changes, state atoms in `mreframe` don't support subscription mechanisms\n(they do however register themselves with the current component and its ancestors to enable re-rendering detection);\nalso, I omitted a few things like global interceptors and post-event callbacks from `re-frame` module, and added a couple\nhelper functions to make it easier to use in JS. And, of course, in cases where switching to camelCase would make an identifier\nmore convenient to use in JS, I did so.\n\nFor further information, I suggest checking out the original (ClojureScript) [`reagent`](https://reagent-project.github.io)\nand [`re-frame`](https://day8.github.io/re-frame/re-frame) libraries documentation. Code examples specific to `mreframe` can\nbe found in the following Examples section, as well as in the API reference.\n\n\n# Usage\n\nInstall the NPM package into a project with `npm i mreframe`/`yarn add mreframe`;  \nor, import as a script in webpage from a CDN: `\u003cscript src=\"https://unpkg.com/mreframe/dist/mreframe.min.js\"\u003e\u003c/script\u003e`.  \n(If you want [routing](https://mithril.js.org/route.html) as well, use this:\n`\u003cscript src=\"https://unpkg.com/mreframe/dist/mreframe-route.min.js\"\u003e\u003c/script\u003e`.)\n\nAccess in code by requiring either main module:\n```js\nlet {reFrame: rf, reagent: r, atom: {deref}, util: {getIn}} = require('mreframe');\n```\nor separate submodules:\n```js\nlet rf = require('mreframe/re-frame');\nlet {getIn} = require('mreframe/util');\n```\nIn case you're using nodeps bundle, or if you want to customize the equality function used by mreframe, run `_init` first:\n```js\nrf._init({eq: _.eq});\n```\n`_init` is exposed by `reagent` submodule (affects only the submodule itself), and also by `re-frame` and the main module\n(affects both `re-frame` and `reagent` submodules).\n\n[`mreframe/atom`](docs/atom.md) module implements a data storing mechanism called [atoms](https://clojure.org/reference/atoms);\nthe main operations provided by it are `deref(atom)` which returns current atom value, `reset(atom, value)` which replaces\nthe atom value, and `swap(atom, f, ...args)` which updates atom value (equivalent to `reset(atom, f(deref(atom), ...args))`).\n\nFor further information, see [API reference](#api-reference) below and the following tutorials / live demo pages:\n[Reagent (components)](https://lexofleviafan.github.io/mreframe/reagent.html),\n[re-frame (events/state management)](https://lexofleviafan.github.io/mreframe/re-frame.html).\n\n\n# Q \u0026 A\n\n* Q: It says I shouldn't mutate the data stored in atoms; how do I update it in that case?  \n  A: Non-mutating updates can be done using functions from [`mreframe/util`](docs/util.md), or a full-scale functional library\n  like [Lodash](https://lodash.com) / [Ramda](https://ramdajs.com) (/ [Rambda](https://selfrefactor.github.io/rambda)).\n* Q: How do I inject raw HTML?  \n  A: If you absolutely have to, use [`m.trust`](https://mithril.js.org/trust.html).\n  In `dist/mreframe.min.js` it can be accessed as `require('mithril/hyperscript').trust()`.\n* Q: What about routing?  \n  A: Use [Mitrhil routing API](https://mithril.js.org/route.html).\n  In `dist/mreframe-route.min.js` it can be accessed as `require('mithril/route')`.\n* Q: What if I want to use a custom version Mithril (e.g. full distribution or a different version)?  \n  A: If you're using JS files from CDN, pick `dist/mreframe-nodeps.min.js` instead, and load Mithril as a separate script;\n  then run [`rf._init`](docs/re-frame.md#_init-opts) to connect them.\n* Q: Are there any third-party libraries (components etc.) I can use with this?  \n  A: Yes, pretty much any [Mithril library](https://awesomeopensource.com/project/orbitbot/awesome-mithril) should be compatible.\n* Q: How stable is this API?  \n  A: The Reagent + re-frame combination has existed since 2015 without much change; as I'm reusing it pretty much directly,\n  there's no reason to change much for me either (the only breaking changes so far were in v0.1 update, where I properly\n  implemented subscription detection/redraw cutoff).\n* Q: And how performant is this thing?  \n  A: Mithril boasts high speed in raw rendering; `mreframe/reagent` naturally slows it down to an extent (up to several times),\n  but in v0.1 a redraw cutoff was added, which greatly reduces recalculated area in complex pages with large amount of components.\n  (See render performance comparison for [Mithril](https://lexofleviafan.github.io/mreframe/performance/mithril.html) and\n  [mreframe](https://lexofleviafan.github.io/mreframe/performance/mreframe.html) – though they're mostly testing raw render performance)\n* Q: I have a _huge_ amount of DB events per second in my app, can I disable the deep-equality check in `db` effect handler?  \n  A: Specify `eq` in [`rf._init`](docs/re-frame.md#_init-opts) to replace it with either [`eqShallow`](docs/util.md#eqShallow-a-b)\n  or [`indentical`](docs/util.md#identical-a-b).\n* Q: I hate commas and languages that aren't syntactical supersets of JS. Can I still use this somehow?  \n  A: Well if you _absolutely must_, you can [use JSX](docs/jsx-runtime.md). (Note that JSX is not exatly a great match for Reagent components.)\n\n\n# Examples\n\n* [Reagent form-2 components + Reagent/Mithril interop](examples/reagent.js.html) (scripted in JavaScript)\n  [[live]](https://lexofleviafan.github.io/mreframe/examples/reagent.js.html)\n* [Re-frame state/side-effects management with Reagent components](examples/re-frame.coffee.html) (scripted in CoffeeScript)\n  [[live]](https://lexofleviafan.github.io/mreframe/examples/re-frame.coffee.html)\n* [Routing using `m.route` (from external Mithril bundle, connected via `_init`)](examples/route.wisp.html) (scripted in Wisp)\n  [[live]](https://lexofleviafan.github.io/mreframe/examples/route.wisp.html)\n* [Routing using `mithril/route` (from `mreframe-route.js`)](examples/route.js.html) (scripted in JavaScript)\n  [[live]](https://lexofleviafan.github.io/mreframe/examples/route.js.html)\n* [Rendering HTML from Reagent components using `mithril-node-render`](examples/node-render.coffee) (scripted in CoffeeScript)\n* [JSX usage example](examples/reagent.jsx)\n\n\n# API reference\n\n[`mreframe`](docs/index.md) exposes following submodules:\n* [`util`](docs/util.md) includes utility functions (which were implemented in mreframe to avoid external dependencies\n  and were exposed so that it can be used without dependencies other than Mithril);\n* [`atom`](docs/atom.md) defines a simple equivalent for [Clojure atoms](https://clojure.org/reference/atoms), used for\n  controlled data updates (as holders for changing data);\n* [`reagent`](docs/reagent.md) defines an alternative,\n  [Hiccup](https://cljdoc.org/d/reagent/reagent/1.0.0/doc/tutorials/using-hiccup-to-describe-html)-based component interface\n  for Mithril;\n* [`re-frame`](docs/re-frame.md) defines a system for managing state/side-effects in a Reagent/Mithril application.\n\nThere's also [`jsx-runtime`](docs/jsx-runtime.md) which isn't included in main module (it implements JSX support).\n\nEach of these can be used separately (`require('mreframe/\u003cname\u003e')`), or as part of the main module\n(`require('mreframe').\u003cname\u003e`; `.reFrame` in case of `re-frame` module). Note that the nodeps bundle doesn't load\nMithril libraries by default (so you'll have to call the `_init` function which it also exports).\n\nAs most of these functions are based on existing ClojureScript equivalents, I'll provide links to respective CLJ docs\nfor anyone interested (although, if you're familiar with these concepts, you'll get the idea from the function name\nin most cases). A major difference, of course, is that instead of vectors, JS arrays are used, and dictionaries\n(plain objects) are used instead of maps; instead of keywords, strings are uses (`:foo` → `'foo'`).\nSince [Wisp](https://github.com/Gozala/wisp) does the same,\nusing `mreframe` with Wisp makes for mostly identical code to that of CLJS\n[`reagent`](http://reagent-project.github.io)/[`re-frame`](https://day8.github.io/re-frame) (at least in regular usecases).\n\n`mreframe` module API:\n* setup: [`_init`](docs/re-frame.md#_init-opts) (normally not needed);\n* submodules: [`reFrame`](docs/re-frame.md), [`reagent`](docs/reagent.md), [`atom`](docs/atom.md), [`util`](docs/util.md).\n\n`mreframe/re-frame` module API:\n* setup: [`rf._init`](docs/re-frame.md#_init-opts) (normally not needed);\n* events (decision-making logic defined as pure functions):\n  - registering functions ([`rf.regEventDb`](docs/re-frame.md#regEventDb-id-interceptors-handler),\n    [`rf.regEventFx`](docs/re-frame.md#regEventFx-id-interceptors-handler),\n    [`rf.regEventCtx`](docs/re-frame.md#regEventCtx-id-interceptors-handler)),\n  - dispatching functions ([`rf.dispatch`](docs/re-frame.md#dispatch-event),\n    [`rf.dispatchSync`](docs/re-frame.md#dispatchSync-event)),\n  - unregistering function for development ([`rf.clearEvent`](docs/re-frame.md#clearEvent-id)),\n  - helper function [`rf.purgeEventQueue`](docs/re-frame.md#purgeEventQueue-) (for cancelling scheduled events);\n* subscriptions (computations for views, with caching):\n  - registering function ([`rf.regSub`](docs/re-frame.md#regSub-id-computation)),\n  - querying functions ([`rf.subscribe`](docs/re-frame.md#subscribe-query), [`rf.dsub`](docs/re-frame.md#dsub-query)),\n  - unregistering function for development ([`rf.clearSub`](docs/re-frame.md#clearSub-id)),\n  - cache clearing function for development ([`rf.clearSubscriptionCache`](docs/re-frame.md#clearSubscriptionCache-));\n* effects (implementation of side-effects for use in events):\n  - registering function ([`rf.regFx`](docs/re-frame.md#regFx-id-handler)),\n  - unregistering function for development ([`rf.clearFx`](docs/re-frame.md#clearFx-id)),\n  - helper function [`rf.disp`](docs/re-frame.md#disp-event-args) (for dispatching `onSuccess`/`onFailure` events),\n  - builtin effects ([`db`](docs/re-frame.md#db-builtin-effect), [`fx`](docs/re-frame.md#fx-builtin-effect),\n    [`dispatchLater`](docs/re-frame.md#dispatchLater-builtin-effect), [`dispatch`](docs/re-frame.md#dispatch-builtin-effect));\n* interceptors (‘wrappers’ that alter event processing when used in event registering function):\n  - creator function ([`rf.toInterceptor`](docs/re-frame.md#toInterceptor-id-before-after)),\n  - predefined interceptors ([`rf.unwrap`](docs/re-frame.md#unwrap), [`rf.trimV`](docs/re-frame.md#trimV)) and generators\n    ([`rf.path`](docs/re-frame.md#path-path), [`rf.enrich`](docs/re-frame.md#enrich-f), [`rf.after`](docs/re-frame.md#after-f),\n    [`rf.onChanges`](docs/re-frame.md#onChanges-f-outPath-inPaths)),\n  - helper functions ([`rf.getCoeffect`](docs/re-frame.md#getCoeffect-context-key-notFound),\n    [`rf.assocCoeffect`](docs/re-frame.md#assocCoeffect-context-key-value),\n    [`rf.getEffect`](docs/re-frame.md#getEffect-context-key-notFound),\n    [`rf.assocEffect`](docs/re-frame.md#assocEffect-context-key-value),\n    [`rf.enqueue`](docs/re-frame.md#enqueue-context-interceptors));\n* coeffects (‘external’ input getters for events, used as interceptors):\n  - registering function ([`rf.regCofx`](docs/re-frame.md#regCofx-id-handler)),\n  - interceptor creator function ([`rf.injectCofx`](docs/re-frame.md#injectCofx-id-arg)),\n  - unregistering function for development ([`rf.clearCofx`](docs/re-frame.md#clearCofx-id)).\n\n`mreframe/reagent` module API:\n* setup: [`r._init`](docs/reagent.md#_init-opts) (normally not needed);\n* atoms: [`r.atom`](docs/reagent.md#atom-x) (triggers redraw on update), [`r.cursor`](docs/reagent.md#cursor-src-path)\n  (‘wrapper’ atom);\n* component creation functions:\n  - [`r.adaptComponent`](docs/reagent.md#adaptComponent-c) for using Mithril components,\n  - [`r.createClass`](docs/reagent.md#createClass-spec) for creating Reagent components with hooks;\n* component rendering functions:\n  - [`r.createElement`](docs/reagent.md#createElement-type-props-children) for directly invoking Mithril hyperscript,\n  - [`r.asElement`](docs/reagent.md#asElement-form) for rendering Hiccup,\n  - [`r.with`](docs/reagent.md#with-meta-form) for supplying metadata (props) to Reagent components,\n  - [`r.render`](docs/reagent.md#render-form-container) for mounting Reagent/Hiccup view on DOM,\n  - [`r.resetCache`](docs/reagent.md#resetCache-) for clearing function components cache (for development);\n* component helper functions:\n  - [`r.classNames`](docs/reagent.md#classNames-classes) for generating/combining CSS classes lists,\n  - [`r.curentComponent`](docs/reagent.md#currentComponent-) for accessing Mithril component from Reagent views,\n  - component data accessors ([`r.children`](docs/reagent.md#children-vnode), [`r.props`](docs/reagent.md#props-vnode),\n    [`r.argv`](docs/reagent.md#argv-vnode), [`r.stateAtom`](docs/reagent.md#stateAtom-vnode)),\n  - Reagent component state reader function ([`r.state`](docs/reagent.md#state-vnode) and updater functions\n    ([`r.setState`](docs/reagent.md#setState-vnode-newState), [`r.replaceState`](docs/reagent.md#replaceState-vnode-newState)).\n\n`mreframe/atom` module API:\n* regular atom creator function ([`atom`](docs/atom.md#atom-x));\n* atom state reader ([`deref`](docs/atom.md#deref-atom));\n* atom state updaters ([`reset`](docs/atom.md#reset-atom-value), [`resetVals`](docs/atom.md#resetVals-atom-value),\n  [`swap`](docs/atom.md#swap-atom-f-args), [`swapVals`](docs/atom.md#swapVals-atom-f-args),\n  [`compareAndSet`](docs/atom.md#compareAndSet-atom-oldval-newval)).\n\n`mreframe/util` module API:\n* general-use functions ([`identity`](docs/util.md#identity-x), [`eq`](docs/util.md#eq-a-b), [`eqShallow`](docs/util.md#eqShallow-a-b),\n  [`indentical`](docs/util.md#identical-a-b), [`chain`](docs/util.md#chain-x-fns), [`repr`](docs/util.md#repr-x));\n* type check functions ([`type`](docs/util.md#type-x), [`isArray`](docs/util.md#isArray-x), [`isDict`](docs/util.md#isDict-x),\n  [`isFn`](docs/util.md#isFn-x));\n* functions for arrays ([`chunks`](docs/util.md#chunks-xs-n), [`flatten`](docs/util.md#flatten-xs));\n* functions for dicts ([`dict`](docs/util.md#dict-kvs), [`entries`](docs/util.md#entries-o), [`keys`](docs/util.md#keys-o),\n  [`vals`](docs/util.md#vals-o));\n* functions manipulating collections ([`merge`](docs/util.md#merge-os), [`assoc`](docs/util.md#assoc-o-k-v),\n  [`dissoc`](docs/util.md#dissoc-o-ks), [`update`](docs/util.md#update-o-k-f-args), [`getIn`](docs/util.md#getIn-o-path),\n  [`assocIn`](docs/util.md#assocIn-o-path-v), [`updateIn`](docs/util.md#updateIn-o-path-f-args));\n* a simple [multimethods](https://clojure.org/reference/multimethods) implementation\n  ([`multi`](docs/util.md#multi-dispatchIdentity)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flexofleviafan%2Fmreframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flexofleviafan%2Fmreframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flexofleviafan%2Fmreframe/lists"}