A reagent/re-frame imitation that uses Mithril instead
npm install mreframeHomepage


mreframe is a plain JavaScript re-implementation of reagent andre-frame libraries from ClojureScript;
it's a mini-framework for single-page apps (using Mithril as the base renderer, with some interop).
* _Lightweight_, both in size and use: just load a small JavaScript file, require it as a library, and you're good to go
* _No language/stack requirement_ – you can use JS directly, or any language that transpiles into it as long as it has interop
* _Simple, data-centric API_ using native JS data structures and plain functions for rendering, event handling, and querying state
* Components, events and queries have _no need to expose their inner workings_ beyond the level of a simple function call
* _Improved performance of re-rendering large, complex UI_ by preventing recalculation of unchanged subtrees
Install: npm i mreframe/yarn add mreframe or .
Here's a full app code example:
``js
let {reagent: r, reFrame: rf} = require('mreframe');
// registering events
rf.regEventDb('init', () => ({counter: 0})); // initial app state
rf.regEventDb('counter-add', (db, [_, n]) => ({...db, counter: db.counter + n}));
// registering state queries
rf.regSub('counter', db => db.counter);
// component functions
let IncButton = (n, caption) =>
['button', {onclick: () => rf.dispatch(['counter-add', n])}, // invoking counter-add event on button click
caption];
let Counter = () =>
['main',
['span.counter', rf.dsub(['counter'])], // accessing app state
" ",
[IncButton, +1, "increment"]];
// initializing the app
rf.dispatchSync(['init']); // app state needs to be initialized immediately, before first render
r.render([Counter], document.body);
`
Tutorial / live demo: Reagent (components),
re-frame (events/state management).
* Intro
* Usage
* Q & A
* Examples
* API reference
ClojureScript has a very good functional interface to React (as third party libraries),
allowing one to model DOM using data literals,
to define components as plain functions (or functions returning functions),
and to make best use of pure functions when defining calculations and decision-making logic.
Wisp is a lightweight Lisp variant based on ClojureScript; however, it's harder to use for
SPAs as there's no similar library available for it. mreframe is meant to deal with this issue; however, after some thinking,
I've decided to make it a regular JS library instead (since Wisp would interop with it seamlessly anyway).
To minimize dependencies (and thus keep the library lightweight as well, as well as make it easy to use), mreframe uses
Mithril in place of React; it also has no other runtime dependencies. In current version, it has size
of 10Kb (4Kb gzipped) by itself, and with required Mithril submodules included it merely goes up to 26Kb (9.5Kb gzipped).
The library includes two main modules: reagent (function components modelling DOM with data literals),
and re-frame (state/side-effects management). You can decide to only use one of these as they're mostly
independent of each other (although re-frame uses reagent atoms internally to trigger redraws on state updates). It alsoatom
includes module for operating state (though you can avoid operating state atoms directly), as well asutil module for non-mutating data updates (these were implemented internally to avoid external dependencies).
Both reagent and re-frame were implemented mostly based on theirreagent.core andre-frame.core APIs respectively, with minor changes to account for the switchmreframe
from ClojureScript to JS and from React to Mithril. The most major change would be that since Mithril relies on minimizing
calculations rather than keeping track of dependency changes, state atoms in don't support subscription mechanismsre-frame
(they do however register themselves with the current component and its ancestors to enable re-rendering detection);
also, I omitted a few things like global interceptors and post-event callbacks from module, and added a couple
helper functions to make it easier to use in JS. And, of course, in cases where switching to camelCase would make an identifier
more convenient to use in JS, I did so.
For further information, I suggest checking out the original (ClojureScript) reagent
and re-frame libraries documentation. Code examples specific to mreframe can
be found in the following Examples section, as well as in the API reference.
Install the NPM package into a project with npm i mreframe/yarn add mreframe;
or, import as a script in webpage from a CDN: .
(If you want routing as well, use this:.)
Access in code by requiring either main module:
`js`
let {reFrame: rf, reagent: r, atom: {deref}, util: {getIn}} = require('mreframe');`
or separate submodules:js`
let rf = require('mreframe/re-frame');
let {getIn} = require('mreframe/util');_init
In case you're using nodeps bundle, or if you want to customize the equality function used by mreframe, run first:`js`
rf._init({eq: _.eq});_init is exposed by reagent submodule (affects only the submodule itself), and also by re-frame and the main modulere-frame
(affects both and reagent submodules).
mreframe/atom module implements a data storing mechanism called atoms;
the main operations provided by it are deref(atom) which returns current atom value, reset(atom, value) which replacesswap(atom, f, ...args)
the atom value, and which updates atom value (equivalent to reset(atom, f(deref(atom), ...args))).
For further information, see API reference below and the following tutorials / live demo pages:
Reagent (components),
re-frame (events/state management).
* Q: It says I shouldn't mutate the data stored in atoms; how do I update it in that case?
A: Non-mutating updates can be done using functions from mreframe/util, or a full-scale functional library
like Lodash / Ramda (/ Rambda).
* Q: How do I inject raw HTML?
A: If you absolutely have to, use m.trust.
In dist/mreframe.min.js it can be accessed as require('mithril/hyperscript').trust().dist/mreframe-route.min.js
* Q: What about routing?
A: Use Mitrhil routing API.
In it can be accessed as require('mithril/route').dist/mreframe-nodeps.min.js
* Q: What if I want to use a custom version Mithril (e.g. full distribution or a different version)?
A: If you're using JS files from CDN, pick instead, and load Mithril as a separate script;rf._init
then run to connect them.mreframe/reagent
* Q: Are there any third-party libraries (components etc.) I can use with this?
A: Yes, pretty much any Mithril library should be compatible.
* Q: How stable is this API?
A: The Reagent + re-frame combination has existed since 2015 without much change; as I'm reusing it pretty much directly,
there's no reason to change much for me either (the only breaking changes so far were in v0.1 update, where I properly
implemented subscription detection/redraw cutoff).
* Q: And how performant is this thing?
A: Mithril boasts high speed in raw rendering; naturally slows it down to an extent (up to several times),db
but in v0.1 a redraw cutoff was added, which greatly reduces recalculated area in complex pages with large amount of components.
(See render performance comparison for Mithril and
mreframe – though they're mostly testing raw render performance)
* Q: I have a _huge_ amount of DB events per second in my app, can I disable the deep-equality check in effect handler? eq
A: Specify in rf._init to replace it with either eqShallowindentical
or .mreframe
* Q: I hate commas and languages that aren't syntactical supersets of JS. Can I still use this somehow?
A: Well if you _absolutely must_, you can use JSX. (Note that JSX is not exatly a great match for Reagent components.)
* Q: I want to use features of in multiple independent parts of my webpage; can I implement them as separate "apps"? mreframe/re-frame
A: You can produce an isolated copy of by invoking inNamespace on it.mreframe
(The main module also exposes a function by the same name; it has similar effect, but produces a "copy" of the main module.)
* Reagent form-2 components + Reagent/Mithril interop (scripted in JavaScript)
[[live]](https://lexofleviafan.github.io/mreframe/examples/reagent.js.html)
* Re-frame state/side-effects management with Reagent components (scripted in CoffeeScript)
[[live]](https://lexofleviafan.github.io/mreframe/examples/re-frame.coffee.html)
* Routing using m.route (from external Mithril bundle, connected via _init) (scripted in Wisp)
[[live]](https://lexofleviafan.github.io/mreframe/examples/route.wisp.html)
* Routing using mithril/route (from mreframe-route.js) (scripted in JavaScript)
[[live]](https://lexofleviafan.github.io/mreframe/examples/route.js.html)
* Rendering HTML from Reagent components using mithril-node-render (scripted in CoffeeScript)
* JSX usage example
mreframe exposes following submodules:
* util includes utility functions (which were implemented in mreframe to avoid external dependencies
and were exposed so that it can be used without dependencies other than Mithril);
* atom defines a simple equivalent for Clojure atoms, used for
controlled data updates (as holders for changing data);
* reagent defines an alternative,
Hiccup-based component interface
for Mithril;
* re-frame defines a system for managing state/side-effects in a Reagent/Mithril application.
There's also jsx-runtime which isn't included in main module (it implements JSX support).
Additionally, invoking inNamespace( produces a copy of the module with isolated re-frame (within the same page).
Each of these can be used separately (require('mreframe/), or as part of the main modulerequire('mreframe').
(; .reFrame in case of re-frame module). Note that the nodeps bundle doesn't load_init
Mithril libraries by default (so you'll have to call the function which it also exports).
As most of these functions are based on existing ClojureScript equivalents, I'll provide links to respective CLJ docs
for anyone interested (although, if you're familiar with these concepts, you'll get the idea from the function name
in most cases). A major difference, of course, is that instead of vectors, JS arrays are used, and dictionaries
(plain objects) are used instead of maps; instead of keywords, strings are uses (:foo → 'foo').mreframe
Since Wisp does the same,
using with Wisp makes for mostly identical code to that of CLJSreagent/re-frame (at least in regular usecases).
mreframe module API:_init
* setup: (normally not needed);reFrame
* submodules: , reagent, atom, util.rf.inNamespace
* namespacing: (for implementing isolated widgets);
mreframe/re-frame module API:rf._init
* setup: (normally not needed);rf.inNamespace
* namespacing: (for implementing isolated widgets);rf.regEventDb
* events (decision-making logic defined as pure functions):
- registering functions (,rf.regEventFx
,rf.regEventCtx
),rf.dispatch
- dispatching functions (,rf.dispatchSync
),rf.clearEvent
- unregistering function for development (),rf.purgeEventQueue
- helper function (for cancelling scheduled events);rf.regSub
* subscriptions (computations for views, with caching):
- registering function (),rf.subscribe
- querying functions (, rf.dsub),rf.clearSub
- unregistering function for development (),rf.clearSubscriptionCache
- cache clearing function for development ();rf.regFx
* effects (implementation of side-effects for use in events):
- registering function (),rf.clearFx
- unregistering function for development (),rf.disp
- helper function (for dispatching onSuccess/onFailure events),db
- builtin effects (, fx,dispatchLater
, dispatch);rf.toInterceptor
* interceptors (‘wrappers’ that alter event processing when used in event registering function):
- creator function (),rf.unwrap
- predefined interceptors (, rf.trimV) and generatorsrf.path
(, rf.enrich, rf.after,rf.onChanges
),rf.getCoeffect
- helper functions (,rf.assocCoeffect
,rf.getEffect
,rf.assocEffect
,rf.enqueue
);rf.regCofx
* coeffects (‘external’ input getters for events, used as interceptors):
- registering function (),rf.injectCofx
- interceptor creator function (),rf.clearCofx
- unregistering function for development ().
mreframe/reagent module API:r._init
* setup: (normally not needed);r.atom
* atoms: (triggers redraw on update), r.cursorr.adaptComponent
(‘wrapper’ atom);
* component creation functions:
- for using Mithril components,r.createClass
- for creating Reagent components with hooks;r.createElement
* component rendering functions:
- for directly invoking Mithril hyperscript,r.asElement
- for rendering Hiccup,r.with
- for supplying metadata (props) to Reagent components,r.render
- for mounting Reagent/Hiccup view on DOM,r.resetCache
- for clearing function components cache (for development);r.classNames
* component helper functions:
- for generating/combining CSS classes lists,r.curentComponent
- for accessing Mithril component from Reagent views,r.children
- component data accessors (, r.props,r.argv
, r.stateAtom),r.state
- Reagent component state reader function ( and updater functionsr.setState
(, r.replaceState).
mreframe/atom module API:atom
* regular atom creator function ();deref
* atom state reader ();reset
* atom state updaters (, resetVals,swap
, swapVals,compareAndSet
).
mreframe/util module API:identity
* general-use functions (, eq, eqShallow,indentical
, chain, repr);type
* type check functions (, isArray, isDict,isFn
);chunks
* functions for arrays (, flatten);dict
* functions for dicts (, entries, keys,vals
);merge
* functions manipulating collections (, assoc,dissoc
, update, getIn,assocIn
, updateIn);multi`).
* a simple multimethods implementation
(