Top Related Projects
Quick Overview
Rum is a simple, fast, and efficient client-side web framework for ClojureScript. It provides a declarative approach to building interactive user interfaces, focusing on performance and simplicity.
Pros
- Simplicity: Rum has a minimal API and a straightforward approach to building web applications, making it easy to learn and use.
- Performance: Rum is designed to be fast and efficient, with a focus on optimizing the rendering process.
- Flexibility: Rum can be easily integrated with other ClojureScript libraries and tools, allowing developers to build complex applications.
- Functional Programming: Rum embraces the functional programming paradigm, which can lead to more maintainable and testable code.
Cons
- ClojureScript-specific: Rum is designed for ClojureScript, which may limit its adoption by developers who are not familiar with the Clojure ecosystem.
- Smaller Community: Compared to larger web frameworks like React or Angular, Rum has a smaller community, which may result in fewer resources and third-party libraries.
- Learning Curve: While Rum is relatively simple, developers who are new to ClojureScript and functional programming may face a steeper learning curve.
- Limited Documentation: The official documentation for Rum could be more comprehensive, which may make it harder for new users to get started.
Code Examples
Here are a few examples of how to use Rum in your ClojureScript projects:
Creating a Simple Component
(ns my-app.core
(:require [rum.core :as rum]))
(rum/defc hello-component [name]
[:div "Hello, " name "!"])
(rum/mount (hello-component "World") (.getElementById js/document "app"))
This code defines a simple Rum component that displays a greeting message. The rum/defc
macro is used to define the component, and the rum/mount
function is used to render the component to the DOM.
Using State
(ns my-app.core
(:require [rum.core :as rum]))
(rum/defc counter-component < rum/reactive
[state]
[:div
"Count: " (:count @state)
[:button {:on-click #(swap! state update :count inc)} "Increment"]])
(defn init-app []
(rum/mount (counter-component (rum/cursor-in (atom {:count 0}) [:count]))
(.getElementById js/document "app")))
This example demonstrates how to use state in a Rum component. The rum/reactive
mixin is used to make the component reactive to changes in the state. The rum/cursor-in
function is used to create a cursor into the state atom, which allows the component to access and update the :count
key.
Lifecycle Hooks
(ns my-app.core
(:require [rum.core :as rum]))
(rum/defc lifecycle-component < rum/reactive
[state]
[:div
"Count: " (:count @state)
[:button {:on-click #(swap! state update :count inc)} "Increment"]]
{:did-mount (fn [state]
(js/console.log "Component mounted!")
state)
:will-unmount (fn [state]
(js/console.log "Component will unmount!")
state)})
This example demonstrates how to use lifecycle hooks in a Rum component. The did-mount
and will-unmount
hooks are used to log messages when the component is mounted and unmounted, respectively.
Getting Started
To get started with Rum, you'll need to have ClojureScript and a build tool like Leiningen or Shadow-CLJS set up in your project. Here's a basic example of how to set up a new Rum project using Leiningen:
- Create a new Leiningen project:
lein new app my-app
- Add Rum to your project dependencies in the
project.clj
file:
(defproject my-app "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/
Competitor Comparisons
The Clojure Interactive Development Environment that Rocks for Emacs
Pros of CIDER
- Provides a comprehensive Clojure development environment within Emacs, including a REPL, debugger, and code evaluation tools.
- Offers seamless integration with Clojure projects, allowing for efficient development and testing workflows.
- Supports a wide range of Clojure libraries and frameworks, making it a versatile choice for Clojure developers.
Cons of CIDER
- Requires a significant investment in learning Emacs, which can be a steep learning curve for some developers.
- May not be as user-friendly or intuitive as some other Clojure development tools, especially for beginners.
- Dependent on the stability and ongoing maintenance of the Emacs ecosystem.
Code Comparison
CIDER:
(defun cider-eval-buffer ()
"Evaluate the current buffer."
(interactive)
(cider-eval-region (point-min) (point-max)))
Rum:
(defn counter [props]
[:div
[:h1 (:title props)]
[:p "Count: " (:count props)]
[:button {:on-click (:on-click props)} "Increment"]])
A minimalistic ClojureScript interface to React.js
Pros of Reagent
- Reagent is a popular and well-established library for building React-based applications in ClojureScript, with a large and active community.
- Reagent provides a simple and intuitive API for defining and composing UI components, making it easy to build complex UIs.
- Reagent integrates well with other ClojureScript libraries and tools, such as re-frame and Figwheel, providing a comprehensive ecosystem for building ClojureScript applications.
Cons of Reagent
- Reagent is primarily focused on ClojureScript, which may be a barrier for developers who are more familiar with JavaScript or other programming languages.
- The learning curve for Reagent can be steeper than some other React-based libraries, especially for developers who are new to ClojureScript.
- Reagent may have a smaller ecosystem and fewer third-party libraries compared to some other React-based libraries.
Code Comparison
Reagent:
(defn my-component []
[:div
[:h1 "Hello, Reagent!"]
[:p "This is a Reagent component."]])
Rum:
(defclass my-component < rum/component
(render [this]
[:div
[:h1 "Hello, Rum!"]
[:p "This is a Rum component."]]))
Both examples define a simple React-like component, but the syntax and approach differ. Reagent uses a more declarative, data-oriented style, while Rum uses a more object-oriented, class-based approach.
A ClojureScript framework for building user interfaces, leveraging React
Pros of re-frame
- re-frame provides a well-structured and opinionated approach to building Clojure/ClojureScript applications, which can be beneficial for larger projects.
- re-frame has a rich ecosystem of libraries and tools that can enhance development productivity.
- re-frame's focus on reactive programming and the Flux architecture can lead to more predictable and maintainable application state management.
Cons of re-frame
- re-frame has a steeper learning curve compared to Rum, especially for developers new to the Clojure/ClojureScript ecosystem.
- re-frame's opinionated nature may not align with the preferences of all developers, limiting flexibility.
- re-frame's dependency on the Reagent library can add complexity for developers who are not familiar with it.
Code Comparison
Rum:
(defn counter [n]
[:div
[:h1 "Counter: " n]
[:button {:on-click #(rum/with-state this (update :n inc))} "Increment"]])
(rum/defc app []
(rum/with-state this {:n 0}
(counter (:n this))))
re-frame:
(defn counter []
(let [n (re-frame/subscribe [:counter])]
[:div
[:h1 "Counter: " @n]
[:button {:on-click #(re-frame/dispatch [:increment])} "Increment"]]))
(defn app []
[:div
[counter]])
Convert
designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Rum is a client/server library for HTML UI. In ClojureScript, it works as React wrapper, in Clojure, it is a static HTML generator.
Table of Contents
- Principles
- Comparison to other frameworks
- Whoâs using Rum?
- Using Rum
- Support
- Acknowledgements
- License
Principles
Simple semantics: Rum is arguably smaller, simpler and more straightforward than React itself.
Decomplected: Rum is a library, not a framework. Use only the parts you need, throw away or replace what you donât need, combine different approaches in a single app, or even combine Rum with other frameworks.
No enforced state model: Unlike Om, Reagent or Quiescent, Rum does not dictate where to keep your state. Instead, it works well with any storage: persistent data structures, atoms, DataScript, JavaScript objects, localStorage or any custom solution you can think of.
Extensible: the API is stable and explicitly defined, including the API between Rum internals. It lets you build custom behaviours that change components in significant ways.
Minimal codebase: You can become a Rum expert just by reading its source code (~900 lines).
Comparison to other frameworks
Rum:
- does not dictate how to store your state,
- has server-side rendering,
- is much smaller.
Whoâs using Rum?
- Arc Studio, collaborative screenwriting app
- Cognician, coaching platform
- Attendify, mobile app builder
- PartsBox.io, inventory management
- kasta.ua, online marketplace
- ChildrensHeartSurgery.info, heart surgery statistics
- Mighty Hype, cinema platform (server-side rendering)
- ÐезопаÑнÑеÐоÑоги.ÑÑ, road data aggregator
- TourneyBot, frisbee tournament app
- PurposeFly, HR 2.0 platform
- Simply, Simple direct life insurance
- Oscaro.com, online autoparts retailer
- Lupapiste, building permit issuance and management
- Newsroom AI, personalised content delivery platform
- Lambdahackers, reddit-like groups for programmers
- Breast Predict, predicting survival after adjuvant treatment for breast cancer
- Prostate Predict, prognostic model for men newly diagnosed with non-metastatic prostate cancer
- Wobaka, CRM system
- Gatheround, online events
- Carrot / OpenCompany, company updates
- UXBOX, the open-source solution for design and prototyping
- Takeoff, automated grocery fulfillment solution
Using Rum
Add to project.clj: [rum "0.12.11"]
API Docs & Articles
Defining a component
Use rum.core/defc
(short for âdefine componentâ) to define a function that returns component markup:
(require [rum.core :as rum])
(rum/defc label [text]
[:div {:class "label"} text])
Rum uses Hiccup-like syntax for defining markup:
[<tag-n-selector> <attrs>? <children>*]
<tag-n-selector>
defines a tag, its id and classes:
:span
:span#id
:span.class
:span#id.class
:span.class.class2
By default, if you omit the tag, div
is assumed:
:#id === :div#id
:.class === :div.class
<attrs>
is an optional map of attributes:
- Use kebab-case keywords for attributes (e.g.
:allow-full-screen
forallowFullScreen
) - You can include
:id
and:class
there as well :class
can be a string or a sequence of strings:style
, if needed, must be a map with kebab-case keywords- event handlers should be arity-one functions
[:input { :type "text"
:allow-full-screen true
:id "comment"
:class ["input_active" "input_error"]
:style { :background-color "#EEE"
:margin-left 42 }
:on-change (fn [e]
(js/alert (.. e -target -value))) }]
<children>
is a zero, one or many elements (strings or nested tags) with the same syntax:
[:div {} "Text"] ;; tag, attrs, nested text
[:div {} [:span]] ;; tag, attrs, nested tag
[:div "Text"] ;; omitted attrs
[:div "A" [:em "B"] "C"] ;; 3 children, mix of text and tags
Children can include lists or sequences which will be flattened:
[:div (list [:i "A"] [:b "B"])] === [:div [:i "A"] [:b "B"]]
By default all text nodes are escaped. To embed an unescaped string into a tag, add the :dangerouslySetInnerHTML
attribute and omit children:
[:div { :dangerouslySetInnerHTML {:__html "<span></span>"}}]
Rendering component
Given this code:
(require [rum.core :as rum])
(rum/defc repeat-label [n text]
[:div (replicate n [:.label text])])
First, we need to create a component instance by calling its function:
(repeat-label 5 "abc")
Then we need to pass that instance to (rum.core/mount comp dom-node)
:
(rum/mount (repeat-label 5 "abc") js/document.body)
And we will get this result:
<body>
<div>
<div class="label">abc</div>
<div class="label">abc</div>
<div class="label">abc</div>
<div class="label">abc</div>
<div class="label">abc</div>
</div>
</body>
Usually, mount
is used just once in an app lifecycle to mount the top of your component tree to a page. After that, for a dynamic applications, you should either update your components or rely on them to update themselves.
Performance
Daiquiri, Rum's Hiccup compiler, pre-compiles certain Clojure forms that return Hiccup (for a list of these forms see compile-form
implementations) into React calls. When the compiler is not able to pre-compile a form it defers this operation to the runtime. Runtime interpretation is slower, the suggestion is to use Clojure forms that are handled by compile-form
, when it makes sense.
(rum/defc component []
[:ul
(for [n (range 10)]
[:li n]) ;; `for` is a known form with a well defined syntax, thus Hiccup is pre-compiled
(map (fn [n]
[:li n]) ;; `map` is a generic higher-order function, can't reliably pre-compile, falling back to interpretation
(range 10))])
To be informed about such code there's compiler flag that enables build warnings
(rum.core/set-warn-on-interpretation! true)
Updating components manually
The simplest way to update your app is to mount it again:
(rum/defc timer []
[:div (.toISOString (js/Date.))])
(rum/mount (timer) js/document.body)
(js/setInterval
#(rum/mount (timer) js/document.body)
1000)
Reactive components
Rum offers mixins as a way to hook into a componentâs lifecycle and extend its capabilities or change its behaviour.
One very common use-case is for a component to update when some reference changes. Rum has a rum.core/reactive
mixin just for that:
(def count (atom 0))
(rum/defc counter < rum/reactive []
[:div { :on-click (fn [_] (swap! count inc)) }
"Clicks: " (rum/react count)])
(rum/mount (counter) js/document.body)
Two things are happening here:
- Weâre adding the
rum.core/reactive
mixin to the component. - Weâre using
rum.core/react
instead ofderef
in the component body.
This will set up a watch on the count
atom and will automatically call rum.core/request-render
on the component each time the atom changes.
Componentâs local state
Sometimes you need to keep track of some mutable data just inside a component and nowhere else. Rum provides the rum.core/local
mixin. Itâs a little trickier to use, so hold on:
- Each component in Rum has internal state associated with it, normally used by mixins and Rum internals.
rum.core/local
creates a mixin that will put an atom into the componentâs state.rum.core/defcs
is used instead ofrum.core/defc
. It allows you to get hold of the componentsâs state in the render function (it will be passed as a first argument).- You can then extract that atom from the componentâs state and
deref
/swap!
/reset!
it as usual. - Any change to the atom will force the component to update.
In practice, itâs quite convenient to use:
(rum/defcs stateful < (rum/local 0 ::key)
[state label]
(let [local-atom (::key state)]
[:div { :on-click (fn [_] (swap! local-atom inc)) }
label ": " @local-atom]))
(rum/mount (stateful "Click count") js/document.body)
Optimizing with shouldComponentUpdate
If your component accepts only immutable data structures as arguments, it may be a good idea to add the rum.core/static
mixin:
(rum/defc label < rum/static [n text]
[:.label (replicate n text)])
rum.core/static
will check if the arguments of a componentâs constructor have changed (using Clojureâs -equiv
semantic), and if they are the same, avoid re-rendering.
(rum/mount (label 1 "abc") body)
(rum/mount (label 1 "abc") body) ;; render wonât be called
(rum/mount (label 1 "xyz") body) ;; this will cause a re-render
(rum/mount (label 1 "xyz") body) ;; this wonât
Note that this is not enabled by default because a) comparisons can be expensive, and b) things will go wrong if you pass a mutable reference as an argument.
Writing your own mixin
Many applications have very specific requirements and custom optimization opportunities, so odds are youâll be writing your own mixins.
Letâs see what a Rum component really is. Each Rum component has:
- A render function
- One or more mixins
- An internal state map
- A corresponding React component
For example, if we have this component defined:
(rum/defc input [label value]
[:label label ": "
[:input { :value value }]])
(input "Your name" "")
It will have the following state:
{ :rum/args ["Your name" ""]
:rum/react-component <react-component> }
You can read the internal state by using the rum.core/defcs
(short for âdefine component [and pass] stateâ) macro instead of rum.core/defc
. It will pass state
to the render function as the first argument:
(rum/defcs label [state label value]
[:div "My args:" (pr-str (:rum/args state))])
(label "A" 3) ;; => <div>My args: ["A" 3]</div>
The internal state cannot be directly manipulated, except at certain stages of a componentâs lifecycle. Mixins are functions that are invoked at these stages to give you and opportunity to modify the state and/or do side effects to the world.
The following mixin will record the componentâs mount time:
(rum/defcs time-label
< { :will-mount (fn [state]
(assoc state ::time (js/Date.))) }
[state label]
[:div label ": " (str (::time state))])
As you can see, :will-mount
is a function from state
to state
. It gives you a chance to populate, clean or modify state map the moment before the component has been mounted.
Another useful thing you can do in a mixin is to decide when to update a component. If you can get ahold of React component (notice that thatâs different from Rum component, unfortunately; sorry), you can call rum.core/request-render
to schedule this componentâs update. To get React component, just look up :rum/react-component
key in a state.
This mixin will update a component each second:
(def periodic-update-mixin
{ :did-mount (fn [state]
(let [comp (:rum/react-component state)
callback #(rum/request-render comp)
interval (js/setInterval callback 1000)]
(assoc state ::interval interval)))
:will-unmount (fn [state]
(js/clearInterval (::interval state))
(dissoc state ::interval)) })
(rum/defc timer < periodic-update-mixin []
[:div (.toISOString (js/Date.))])
(rum/mount (timer) js/document.body)
Hereâs a full list of callbacks you can define in a mixin:
{ :init ;; state, props â state
:will-mount ;; state â state
:before-render ;; state â state
:wrap-render ;; render-fn â render-fn
:render ;; state â [pseudo-dom state]
:did-catch ;; state, err, info â state
:did-mount ;; state â state
:after-render ;; state â state
:will-remount ;; old-state, state â state
:should-update ;; old-state, state â boolean
:will-update ;; state â state
:did-update ;; state â state
:will-unmount } ;; state â state
Each component can have any number of mixins:
(rum/defcs component
< rum/static
rum/reactive
(rum/local 0 ::count)
(rum/local "" ::text)
[state label]
(let [count-atom (::count state)
text-atom (::text state)]
[:div])
One gotcha: donât forget to return state
from the mixin functions. If youâre using them for side-effects only, just return an unmodified state
.
Working with atoms
Since Rum relies a lot at components being able to efficiently update themselves in reaction to events, it includes two facilities to build architectures around Atoms and watchers.
Cursors
If you have a complex state and need a component to interact with only a part of it, create a cursor using (rum.core/cursor-in ref path)
. Given atom with deep nested value and path inside it, cursor-in
will create an atom-like structure that can be used separately from main atom, but will sync changes both ways:
(def state (atom { :color "#cc3333"
:user { :name "Ivan" } }))
(def user-name (rum/cursor-in state [:user :name]))
@user-name ;; => "Ivan"
(reset! user-name "Oleg") ;; => "Oleg"
@state ;; => { :color "#cc3333"
;; :user { :name "Oleg" } }
Cursors implement IAtom
and IWatchable
and interface-wise are drop-in replacement for regular atoms. They work well with rum/reactive
and rum/react
too.
Derived atoms
Use derived atoms to create âchainsâ and acyclic graphs of dependent atoms. derived-atom
will:
- Take N âsourceâ refs
- Set up a watch on each of them
- Create âsinkâ atom
- When any of source refs changes:
- re-run function
f
, passing N dereferenced values of source refs reset!
result off
to the sink atom
- re-run function
- return sink atom
(def *a (atom 0))
(def *b (atom 1))
(def *x (derived-atom [*a *b] ::key
(fn [a b]
(str a \":\" b))))
(type *x) ;; => clojure.lang.Atom
@*x ;; => 0:1
(swap! *a inc)
@*x ;; => 1:1
(reset! *b 7)
@*x ;; => 1:7
Derived atoms are like cursors, but can âdepend onâ multiple references and wonât sync changes back to the source if you try to update derived atom (donât).
Interop with React
Native React component
You can access the raw React component by reading the stateâs :rum/react-component
attribute:
{ :did-mount (fn [state]
(let [comp (:rum/react-component state)
dom-node (js/ReactDOM.findDOMNode comp)]
(set! (.-width (.-style dom-node)) "100px"))
state) }
React keys and refs
Thereâre three ways to specify React keys:
- If you need a key on Sablono tag, put it into attributes:
[:div { :key "x" }]
- If you need a key on Rum component, use
with-key
:
(rum/defc my-component [str]
...)
(rum/with-key (my-component "args") "x")
- or, you can specify
:key-fn
in a mixin to calculate key based on args at component creation time:
(rum/defc my-component
< { :key-fn (fn [x y z]
(str x "-" y "-" z)) }
[x y z]
...)
(my-component 1 2 3) ;; => key == "1-2-3"
:key-fn
must accept same arguments your render function does.
Refs work the same way as options 1 and 2 for keys work:
[:div { :ref "x" }]
(rum/with-ref (my-component) "x")
Accessing DOM
[:div {:ref (fn [node] ...)}]
;; or
(let [ref (rum/create-ref)]
[:input
{:ref ref
:on-change #(.log js/console (rum/deref ref))}])
â ï¸ The helpers below are deprecated since usage of string refs has been deprecated in React itself. Instead use the API described above.
Thereâre couple of helpers that will, given state map, find stuff in it for you:
(rum/dom-node state) ;; => top-level DOM node
(rum/ref state "x") ;; => ref-ed React component
(rum/ref-node state "x") ;; => top-level DOM node of ref-ed React component
Custom class properties
To define arbitrary properties and methods on a component class, specify a :class-properties
map in a mixin:
(rum/defc comp
< { :class-properties { ... } }
[:div]))
To define static properties on a component class, specify a :static-properties
map in a mixin:
(rum/defc comp
< { :static-properties { ... } }
[:div]))
React context
New API
(rum/defcontext *context*)
(rum/defc context-consumer []
(rum/with-context [value *context*]
value)) ;; "hello"
(rum/defc context-provider []
(rum/bind-context [*context* "hello"]
(context-consumer))
Legacy API
â ï¸ This API is deprecated in React and will be removed in future versions of Rum
To define child context
- Add dependency
[cljsjs/prop-types "15.5.10-1"]
(require [cljsjs.prop-types])
- Specify a
:child-context
function taking state and returning context map in a mixin:
(rum/defc theme
< { :child-context
(fn [state]
(let [[color] (:rum/args state)]
{ :color color }))
:static-properties
{ :childContextTypes {:color js/PropTypes.string} } }
[color child]
child)
React Hooks
There are Rum wrappers for the various React hooks. See doc strings for examples, and the React hooks reference for more details.
â ï¸ Hooks can be used only in
defc
components with optionalrum/static
mixin. Using any other mixin or form of declaring a component will generate class-based React components that are not compatible with hooks. You should use either hooks or mixins in one component, two can't work together.
;; Takes initial value or value returning fn and returns a tuple of [value set-value!],
;; where `value` is current state value and `set-value!` is a function that schedules re-render.
(let [[x set-x!] (rum/use-state 0)]
(set-x! (inc x)))
;; Takes reducing function and initial state value.
;; Returns a tuple of [value dispatch!], where `value` is current state value and `dispatch` is a function that schedules re-render.
(rum/use-reducer reducer-fn initial-value)
;; Takes setup-fn that executes either on the first render or after every update.
;; The function may return cleanup-fn to cleanup the effect, either before unmount or before every next update.
;; Calling behavior is controlled by deps argument.
(rum/use-effect!
(fn []
(.addEventListener js/document "keydown" js/console.log)
#(.removeEventListener js/document "keydown" js/console.log))
[])
;; Takes callback function and returns memoized variant, memoization is done based on provided deps collection.
(rum/defc component [x]
(let [on-change (rum/use-callback #(js/console.log % x) [x])]
[input-field {:on-change on-change}]))
;; Takes a function, memoizes it based on provided deps collection and executes immediately returning a result.
(let [x (rum/use-memo #(expensive-computation v) [v])])
;; Takes a value and puts it into a mutable container which is persisted for the full lifetime of the component.
(rum/defc component []
(let [ref (rum/use-ref nil)]
(rum/use-effect!
#(.log js/console (rum/deref ref)))
[:input {:ref ref}]))
React Fragment
Using :<>
as the tag in a markup vector creates a React Fragment, allowing you to render multiple components without a wrapping element.
[:<>
[:span]
[:div]
[:span]]
;; <span></span><div></div><span></span>
Server-side rendering
When used from cljs Rum delegates serialization to ReactDOM library. If used from clj/cljc, Rum works as a traditional template engine à la Hiccup:
- Rumâs
project.clj
dependency becomes[rum "0.12.11" :exclusions [cljsjs/react cljsjs/react-dom]
- Import
rum.core
as usual. - Define components using
rum/defc
or other macros as usual. - Instead of mounting, call
rum/render-html
to render into a string. - Generate the HTML page using that string.
- On the client side, mount (but using
rum/hydrate
) the same component over the node where you rendered your server-side component.
(require '[rum.core :as rum])
(rum/defc my-comp [s]
[:div s])
;; on a server
(rum/render-html (my-comp "hello"))
;; => "<div data-reactroot=\"\">hello</div>"
;; on a client
(rum/hydrate (my-comp "hello") js/document.body)
Use rum/render-static-markup
if youâre not planning to connect your page with React later:
(rum/render-static-markup (my-comp "hello")) ;; => <div>hello</div>
Rum server-side rendering does not use React or Sablono, it runs completely in JVM, without involving JavaScript at any stage.
As of [rum "0.8.3"]
and [hiccup "1.0.5"]
, Rum is ~3Ã times faster than Hiccup.
Server-side components do not have full lifecycle support, but :init
and :will-mount
from mixins would be called at the componentâs construction time.
Support
- Join #rum on Clojurians Slack (grab invite here)
- Check out our wiki
Talks
- Rum workshop at Cognician, by me
- Norbert Wójtowicz talk at Lambda Days 2015 where he explains benefits of web development with ClojureScript and React, and how Rum emulates all main ClojureScript frameworks
App templates
Libraries
- Reforms, Bootstrap 3 forms
- rum-mdl, Material design lite components
- derivatives, creates chains of derived values from an atom
- citrus, state coordination framework (previously known as scrum)
- Antizer Ant Design component library
- data-frisk-rum, display current value of data
Examples
- In this repo see examples/rum/examples/
- DataScript Chat app
- DataScript ToDo app
- DataScript Menu app
Acknowledgements
Rum was build on inspiration from Quiescent, Om and Reagent.
All heavy lifting done by React and ClojureScript.
License
Copyright © 2014 Nikita Prokopov, 2020 Roman Liutikov
Licensed under Eclipse Public License (see LICENSE).
Top Related Projects
Convert
designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot