{"id":18854962,"url":"https://github.com/roman01la/prum","last_synced_at":"2025-04-14T10:50:47.522Z","repository":{"id":62434032,"uuid":"74488424","full_name":"roman01la/prum","owner":"roman01la","description":"ClojureScript's Rum with Preact.js instead of React","archived":false,"fork":false,"pushed_at":"2018-05-08T10:57:05.000Z","size":2608,"stargazers_count":64,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"preact","last_synced_at":"2025-04-11T02:59:30.340Z","etag":null,"topics":["clojurescript","preact","prum","react","rum"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roman01la.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-11-22T15:52:20.000Z","updated_at":"2023-05-09T15:32:49.000Z","dependencies_parsed_at":"2022-11-01T21:15:45.064Z","dependency_job_id":null,"html_url":"https://github.com/roman01la/prum","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roman01la%2Fprum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roman01la%2Fprum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roman01la%2Fprum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roman01la%2Fprum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roman01la","download_url":"https://codeload.github.com/roman01la/prum/tar.gz/refs/heads/preact","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248868826,"owners_count":21174754,"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":["clojurescript","preact","prum","react","rum"],"created_at":"2024-11-08T03:52:23.046Z","updated_at":"2025-04-14T10:50:47.489Z","avatar_url":"https://github.com/roman01la.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"_Prum (Pale Rum) is a fork of Rum library that uses Preact.js as an underlying UI rendering facility_\n\n`[org.roman01la/prum \"0.10.8-11\"]`\n\n### Differences to Rum/React\n\n#### No Hiccup interpretation\n\nSimilar to Rum, Prum is using Hiccup compiler to transform Hiccup into JavaScript calls to Preact's API. However Hiccup interpretation is disabled in Prum, since it adds performance overhead.\n\nDue to this restrictions are the following:\n\n- Do not generate Hiccup elements programmatically\n- Wrap Hiccup elements with `prum.compiler/html` macro when returning Hiccup from a function\n- A list of forms that may contain Hiccup and will be handled by the compiler: [see here](https://github.com/rauhs/hicada/blob/master/src/hicada/compiler.clj#L111-L184)\n- If the first argument after the tag is a variable, it’s assumed to be the first child\n\n#### Creating ReactNodes from React class components\n\n```clojure\n[:\u003e ReactSelect {:value \"value\" :options options}]\n\n;; (preact/createElement ReactSelect #js {:value value :options options})\n```\n\n#### No string refs\n\nPreact supports only function _refs_. However string refs is still useful and easier to use in ClojureScript. To handle this properly there's a new helper function `rum.core/use-ref`\n\n```clojure\n(rum/defc input []\n  [:input {}])\n\n(rum/defcc form \u003c\n  {:after-render\n   (fn [state]\n     (rum/ref state :btn) ;; returns DOM node of the element\n     (rum/ref state :input) ;; returns component\n     (rum/ref-node state :input) ;; returns top-level DOM node of the component\n     state)}\n\n  [comp]\n\n  [:form {}\n    (rum/with-ref (input) (rum/use-ref comp :input))\n    [:button {:ref (rum/use-ref comp :btn)} \"text\"]])\n```\n\n#### Context API\n\nPreact components doesn't implement `contextTypes` and `childContextTypes` as in React. This means that in Prum there's no need to declare `:contextTypes` and `:childContextTypes` in `:class-properties` mixin.\n\nAlso there's a helper function to read from context `rum.core/context`.\n\n```clojure\n(rum/defcc rum-context-comp [comp]\n  [:span\n   {:style {:color (rum/context comp :color)}}\n   \"Child component uses context to set font color.\"])\n\n(rum/defc context \u003c\n  {:child-context (fn [state] {:color @core/*color})}\n  []\n  [:div {}\n   [:div {} \"Root component implicitly passes data to descendants.\"]\n   (rum-context-comp)])\n```\n\n#### Re-rendering\n\nWhen re-rendering from the root, by default Preact appends to root DOM node. To re-render properly `rum.core/mount` accepts optional third argument, which is the root node to replace.\n\n```clojure\n(def root (rum/mount (app) dom-node)) ;; returns root DOM node\n\n(rum/mount (app) dom-node root) ;; pass in the root node to render and replace\n```\n\n#### Collections of child elements\n\nWhen rendering a list of values, a collection of elements _should not be a vector_.\n\n```clojure\n[:ul {} (mapv render-item items)] ;; this is wrong\n[:ul {} (map render-item items)] ;; this is ok\n\n[:ul {} [[:li {} \"#1\"] [:li {} \"#2\"]]] ;; this is wrong\n[:ul {} '([:li {} \"#1\"] [:li {} \"#2\"])] ;; this is ok\n```\n\n#### Dynamic CSS via `:css` attribute\n\nProvided by [Clojure Style Sheets](https://github.com/roman01la/cljss) library.\n\n```clojure\n[:button {:css {:font-size \"12px\"}}]\n``` \n\n#### DOM Events\n\nPreact use native (in-browser) event system instead of Synthetic Events system as in React, thus it doesn't change behaviour of DOM events. However to stay compatible with Rum/React, Prum translates `:on-change` handlers into `:on-input` as React does.\n\n_Below is original unmodified documentation of Rum_\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"http://s.tonsky.me/imgs/rum_logo.svg\" style=\"height: 400px;\"\u003e\u003c/p\u003e\n\nRum is a client/server library for HTML UI. In ClojureScript, it works as React wrapper, in Clojure, it is a static HTML generator.\n\n### Principles\n\n**Simple semantics**: Rum is arguably smaller, simpler and more straightforward than React itself.\n\n**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.\n\n**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.\n\n**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.\n\n**Minimal codebase**: You can become a Rum expert just by reading its source code (~900 lines).\n\n### Comparison to other frameworks\n\nRum:\n\n- does not dictate how to store your state,\n- has server-side rendering,\n- is much smaller.\n\n### Who’s using Rum?\n\n- [Cognician](https://www.cognician.com), coaching platform\n- [Attendify](https://attendify.com), mobile app builder\n- [PartsBox.io](https://partsbox.io), inventory management\n- [modnaKasta](https://modnaKasta.ua), online shopping\n- [ChildrensHeartSurgery.info](http://childrensheartsurgery.info), heart surgery statistics\n- [Mighty Hype](http://mightyhype.com/), cinema platform (server-side rendering)\n- [БезопасныеДороги.рф](https://xn--80abhddbmm5bieahtk5n.xn--p1ai/), road data aggregator\n- [TourneyBot](http://houstonindoor.com/2016), frisbee tournament app\n- [PurposeFly](https://www.purposefly.com/), HR 2.0 platform\n\n## Using Rum\n\nAdd to project.clj: `[rum \"0.10.8\"]`\n\n### Defining a component\n\nUse `rum.core/defc` (short for “define component”) to define a function that returns component markup:\n\n```clojure\n(require [rum.core :as rum])\n\n(rum/defc label [text]\n  [:div {:class \"label\"} text])\n```\n\nRum uses Hiccup-like syntax for defining markup:\n\n```clojure\n[\u003ctag-n-selector\u003e \u003cattrs\u003e? \u003cchildren\u003e*]\n```\n\n`\u003ctag-n-selector\u003e` defines a tag, its id and classes:\n\n```clojure\n  :span\n  :span#id\n  :span.class\n  :span#id.class\n  :span.class.class2\n```\n\nBy default, if you omit the tag, `div` is assumed:\n\n```\n  :#id    === :div#id\n  :.class === :div.class\n```\n\n`\u003cattrs\u003e` is an optional map of attributes:\n\n- Use kebab-case keywords for attributes (e.g. `:allow-full-screen` for `allowFullScreen`)\n- You can include `:id` and `:class` there as well\n- `:class` can be a string or a sequence of strings\n- `:style`, if needed, must be a map with kebab-case keywords\n- event handlers should be arity-one functions\n\n```clojure\n[:input { :type      \"text\"\n          :allow-full-screen true\n          :id        \"comment\"\n          :class     [\"input_active\" \"input_error\"]\n          :style     { :background-color \"#EEE\"\n                       :margin-left      42 }\n          :on-change (fn [e]\n                       (js/alert (.. e -target -value))) }]\n```\n\n`\u003cchildren\u003e` is a zero, one or many elements (strings or nested tags) with the same syntax:\n\n```clojure\n  [:div {} \"Text\"]         ;; tag, attrs, nested text\n  [:div {} [:span]]        ;; tag, attrs, nested tag\n  [:div \"Text\"]            ;; omitted attrs\n  [:div \"A\" [:em \"B\"] \"C\"] ;; 3 children, mix of text and tags\n```\n\nChildren can include lists or sequences which will be flattened:\n\n```clojure\n  [:div (list [:i \"A\"] [:b \"B\"])] === [:div [:i \"A\"] [:b \"B\"]]\n```\n\nBy default all text nodes are escaped. To embed an unescaped string into a tag, add the `:dangerouslySetInnerHTML` attribute and omit children:\n\n```clojure\n  [:div { :dangerouslySetInnerHTML {:__html \"\u003cspan\u003e\u003c/span\u003e\"}}]\n```\n\n### Rendering component\n\nGiven this code:\n\n```clojure\n(require [rum.core :as rum])\n\n(rum/defc repeat-label [n text]\n  [:div (repeat n [:.label text])])\n```\n\nFirst, we need to create a component instance by calling its function:\n\n```\n(repeat-label 5 \"abc\")\n```\n\nThen we need to pass that instance to `(rum.core/mount comp dom-node)`:\n\n```clojure\n(rum/mount (repeat-label 5 \"abc\") js/document.body)\n```\n\nAnd we will get this result:\n\n```html\n  \u003cbody\u003e\n    \u003cdiv\u003e\n      \u003cdiv class=\"label\"\u003eabc\u003c/div\u003e\n      \u003cdiv class=\"label\"\u003eabc\u003c/div\u003e\n      \u003cdiv class=\"label\"\u003eabc\u003c/div\u003e\n      \u003cdiv class=\"label\"\u003eabc\u003c/div\u003e\n      \u003cdiv class=\"label\"\u003eabc\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/body\u003e\n```\n\nUsually, `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.\n\n### Updating components manually\n\nThe simplest way to update your app is to mount it again:\n\n```clojure\n(rum/defc timer []\n  [:div (.toISOString (js/Date.))])\n\n(rum/mount (timer) js/document.body)\n\n(js/setInterval\n  #(rum/mount (timer) js/document.body)\n  1000)\n```\n\n### Reactive components\n\nRum offers mixins as a way to hook into a component’s lifecycle and extend its capabilities or change its behaviour.\n\nOne very common use-case is for a component to update when some reference changes. Rum has a `rum.core/reactive` mixin just for that:\n\n```clojure\n(def count (atom 0))\n\n(rum/defc counter \u003c rum/reactive []\n  [:div { :on-click (fn [_] (swap! count inc)) }\n    \"Clicks: \" (rum/react count)])\n\n(rum/mount (counter) js/document.body)\n```\n\nTwo things are happening here:\n\n1. We’re adding the `rum.core/reactive` mixin to the component.\n2. We’re using `rum.core/react` instead of `deref` in the component body.\n\nThis 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.\n\n\n### Component’s local state\n\nSometimes 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:\n\n1. Each component in Rum has internal state associated with it, normally used by mixins and Rum internals.\n2. `rum.core/local` creates a mixin that will put an atom into the component’s state.\n3. `rum.core/defcs` is used instead of `rum.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).\n4. You can then extract that atom from the component’s state and `deref`/`swap!`/`reset!` it as usual.\n5. Any change to the atom will force the component to update.\n\nIn practice, it’s quite convenient to use:\n\n```clojure\n(rum/defcs stateful \u003c (rum/local 0 ::key)\n  [state label]\n  (let [local-atom (::key state)]\n    [:div { :on-click (fn [_] (swap! local-atom inc)) }\n      label \": \" @local-atom]))\n\n(rum/mount (stateful \"Click count\") js/document.body)\n```\n\n### Optimizing with shouldComponentUpdate\n\nIf your component accepts only immutable data structures as arguments, it may be a good idea to add the `rum.core/static` mixin:\n\n```clojure\n(rum/defc label \u003c rum/static [n text]\n  [:.label (repeat n text)])\n```\n\n`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.\n\n```clojure\n(rum/mount (label 1 \"abc\") body)\n(rum/mount (label 1 \"abc\") body) ;; render won’t be called\n(rum/mount (label 1 \"xyz\") body) ;; this will cause a re-render\n(rum/mount (label 1 \"xyz\") body) ;; this won’t\n```\n\nNote 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.\n\n### Writing your own mixin\n\nMany applications have very specific requirements and custom optimization opportunities, so odds are you’ll be writing your own mixins.\n\nLet’s see what a Rum component really is. Each Rum component has:\n\n- A render function\n- One or more mixins\n- An internal state map\n- A corresponding React component\n\nFor example, if we have this component defined:\n\n```clojure\n(rum/defc input [label value]\n  [:label label \": \"\n    [:input { :value value }]])\n\n(input \"Your name\" \"\")\n```\n\nIt will have the following state:\n\n```clojure\n{ :rum/args [\"Your name\" \"\"]\n  :rum/react-component \u003creact-component\u003e }\n```\n\nYou 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:\n\n```clojure\n(rum/defcs label [state label value]\n  [:div \"My args:\" (pr-str (:rum/args state))])\n\n(label \"A\" 3) ;; =\u003e \u003cdiv\u003eMy args: [\"A\" 3]\u003c/div\u003e\n```\n\nThe 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.\n\nThe following mixin will record the component’s mount time:\n\n```clojure\n(rum/defcs time-label \u003c { :will-mount (fn [state]\n                                       (assoc state ::time (js/Date.))) }\n  [state label]\n  [:div label \": \" (str (::time state))])\n```\n\nAs 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.\n\nAnother 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 at next frame (Rum uses `requestAnimationFrame` to batch and debounce component update calls). To get React component, just look up `:rum/react-component` key in a state.\n\nThis mixin will update a component each second:\n\n```clojure\n(def periodic-update-mixin\n  { :did-mount    (fn [state]\n                    (let [comp      (:rum/react-component state)\n                          callback #(rum/request-render comp)\n                          interval  (js/setInterval callback 1000)]\n                       (assoc state ::interval interval)))\n    :will-unmount (fn [state]\n                    (js/clearInterval (::interval state))\n                    (dissoc state ::interval)) })\n\n(rum/defc timer \u003c periodic-update-mixin []\n  [:div (.toISOString (js/Date.))])\n\n(rum/mount (timer) js/document.body)\n```\n\nHere’s a full list of callbacks you can define in a mixin:\n\n```clojure\n{ :init                 ;; state, props     ⇒ state\n  :will-mount           ;; state            ⇒ state\n  :before-render        ;; state            ⇒ state\n  :wrap-render          ;; render-fn        ⇒ render-fn\n  :render               ;; state            ⇒ [pseudo-dom state]\n  :did-mount            ;; state            ⇒ state\n  :after-render         ;; state            ⇒ state\n  :did-remount          ;; old-state, state ⇒ state\n  :should-update        ;; old-state, state ⇒ boolean\n  :will-update          ;; state            ⇒ state\n  :did-update           ;; state            ⇒ state\n  :will-unmount }       ;; state            ⇒ state\n```\n\nEach component can have any number of mixins:\n\n```clojure\n(rum/defcs component \u003c rum/static\n                       rum/reactive\n                       (rum/local 0 ::count)\n                       (rum/local \"\" ::text)\n  [state label]\n  (let [count-atom (::count state)\n        text-atom  (::text state)]\n    [:div])\n```\n\nOne 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`.\n\n\n### Working with atoms\n\nSince 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.\n\n**Cursors**\n\nIf 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:\n\n```clojure\n(def state (atom { :color \"#cc3333\"\n                   :user { :name \"Ivan\" } }))\n\n(def user-name (rum/cursor-in state [:user :name]))\n\n@user-name ;; =\u003e \"Ivan\"\n\n(reset! user-name \"Oleg\") ;; =\u003e \"Oleg\"\n\n@state ;; =\u003e { :color \"#cc3333\"\n       ;;      :user  { :name \"Oleg\" } }\n```\n\nCursors implement `IAtom` and `IWatchable` and interface-wise are drop-in replacement for regular atoms. They work well with `rum/reactive` and `rum/react` too.\n\n**Derived atoms**\n\nUse derived atoms to create “chains” and acyclic graphs of dependent atoms. `derived-atom` will:\n\n- Take N “source” refs\n- Set up a watch on each of them\n- Create “sink” atom\n- When any of source refs changes:\n  - re-run function `f`, passing N dereferenced values of source refs\n  - `reset!` result of `f` to the sink atom\n- return sink atom\n\n```clojure\n  (def *a (atom 0))\n  (def *b (atom 1))\n  (def *x (derived-atom [*a *b] ::key\n            (fn [a b]\n              (str a \\\":\\\" b))))\n  (type *x) ;; =\u003e clojure.lang.Atom\n  @*x     ;; =\u003e 0:1\n  (swap! *a inc)\n  @*x     ;; =\u003e 1:1\n  (reset! *b 7)\n  @*x     ;; =\u003e 1:7\n```\n\nDerived 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).\n\n\n### Interop with React\n\n**Native React component**\n\nYou can access the raw React component by reading the state’s `:rum/react-component` attribute:\n\n```clojure\n{ :did-mount (fn [state]\n               (let [comp     (:rum/react-component state)\n                     dom-node (js/ReactDOM.findDOMNode comp)]\n                 (set! (.-width (.-style dom-node)) \"100px\"))\n               state) }\n```\n\n**React keys and refs**\n\nThere’re three ways to specify React keys:\n\n1. If you need a key on Sablono tag, put it into attributes: `[:div { :key \"x\" }]`\n2. If you need a key on Rum component, use `with-key`:\n\n  ```clojure\n  (rum/defc my-component [str]\n    ...)\n\n  (rum/with-key (my-component \"args\") \"x\")\n  ```\n3. or, you can specify `:key-fn` in a mixin to calculate key based on args at component creation time:\n\n  ```clojure\n  (rum/defc my-component \u003c { :key-fn (fn [x y z]\n                                       (str x \"-\" y \"-\" z)) }\n    [x y z]\n    ...)\n\n  (my-component 1 2 3) ;; =\u003e key == \"1-2-3\"\n  ```\n\n`:key-fn` must accept same arguments your render function does.\n\nRefs work the same way as options 1 and 2 for keys work:\n\n1. `[:div { :ref \"x\" }]`\n2. `(rum/with-ref (my-component) \"x\")`\n\n\n**Accessing DOM**\n\nThere’re couple of helpers that will, given state map, find stuff in it for you:\n\n```clojure\n(rum/dom-node state)     ;; =\u003e top-level DOM node\n(rum/ref      state \"x\") ;; =\u003e ref-ed React component\n(rum/ref-node state \"x\") ;; =\u003e top-level DOM node of ref-ed React component\n```\n\n**Custom class properties**\n\nTo define arbitrary properties and methods on a component class, specify a `:class-properties` map in a mixin:\n\n```clojure\n(rum/defc comp \u003c { :class-properties { ... } }\n  [:div]))\n```\n\n**React context**\n\nTo define child context, specify a `:child-context` function taking state and returning context map in a mixin:\n\n```clojure\n(rum/defc theme \u003c { :child-context\n                    (fn [state]\n                      (let [[color] (:rum/args state)]\n                        { :color color }))\n                    :class-properties\n                    { :childContextTypes {:color js/React.PropTypes.string} } }\n  [color child]\n  child)\n```\n\n### Server-side rendering\n\nIf used from clj/cljc, Rum works as a traditional template engine à la Hiccup:\n\n1. Import `rum.core` as usual.\n2. Define components using `rum/defc` or other macros as usual.\n3. Instead of mounting, call `rum/render-html` to render into a string.\n4. Generate the HTML page using that string.\n5. On the client side, mount _the same_ component over the node where you rendered your server-side component.\n\n```clojure\n(require '[rum.core :as rum])\n\n(rum/defc my-comp [s]\n  [:div s])\n\n;; on a server\n(rum/render-html (my-comp \"hello\"))\n;; =\u003e \"\u003cdiv data-reactroot=\\\"\\\" data-reactid=\\\"1\\\" data-react-checksum=\\\"-857140882\\\"\u003ehello\u003c/div\u003e\"\n\n;; on a client\n(rum/mount (my-comp \"hello\") js/document.body)\n```\n\nUse `rum/render-static-markup` if you’re not planning to connect your page with React later:\n\n```clojure\n(rum/render-static-markup (my-comp \"hello\")) ;; =\u003e \u003cdiv\u003ehello\u003c/div\u003e\n```\n\nRum server-side rendering does not use React or Sablono, it runs completely in JVM, without involving JavaScript at any stage.\n\nAs of `[rum \"0.8.3\"]` and `[hiccup \"1.0.5\"]`, Rum is ~3× times faster than Hiccup.\n\nServer-side components do not have full lifecycle support, but `:init` and `:will-mount` from mixins would be called at the component’s construction time.\n\n## Resources\n\n- Ask for help on [Gitter chat](https://gitter.im/tonsky/rum)\n- Check out [our wiki](https://github.com/tonsky/rum/wiki)\n\n### Talks\n\n- [Rum workshop](https://www.youtube.com/watch?v=RqHnxkU9TZE) at Cognician, by me\n- [Norbert Wójtowicz talk at Lambda Days 2015](https://vimeo.com/122316380) where he explains benefits of web development with ClojureScript and React, and how Rum emulates all main ClojureScript frameworks\n- [Hangout about Rum](https://www.youtube.com/watch?v=8evDKjD5vt4) (in Russian)\n\n### App templates\n\n- [Tenzing](https://github.com/martinklepsch/tenzing)\n\n### Libraries\n\n- [Reforms](http://bilus.github.io/reforms/), Bootstrap 3 forms\n- [rum-mdl](http://ajchemist.github.io/rum-mdl/), Material design lite components\n- [derivatives](https://github.com/martinklepsch/derivatives), creates chains of derived values from an atom\n- [scrum](https://github.com/roman01la/scrum), state coordination framework\n- [Antizer](https://github.com/priornix/antizer) Ant Design component library\n\n### Examples\n\n- In this repo see [examples/rum/examples/](examples/prum/examples/). [Live version](http://tonsky.me/rum/)\n- [DataScript Chat app](https://github.com/tonsky/datascript-chat)\n- [DataScript ToDo app](https://github.com/tonsky/datascript-todo)\n- [DataScript Menu app](https://github.com/tonsky/datascript-menu)\n\n## Changes\n\n### 0.10.8\n\n- React 15.4.2-0, Sablono 0.7.7\n- Render boolean `aria-*` values as strings (thx [r0man](https://github.com/r0man), PR #114)\n- Escape attributes during server-side rendering (thx [Alexander Solovyov](https://github.com/piranha), PR #115)\n\n### 0.10.7\n\n- Fixed server-side rendering discrepancy (issue #99)\n- Sablono 0.7.5, React 15.3.1-0\n\n### 0.10.6\n\n- Sablono 0.7.4 [fixes the issue](https://github.com/r0man/sablono/pull/129) with controlling components refusing to change value if non-string value was used\n- React 15.3.0-0\n- Throw error when `\u003c` is misplaced in `defc` (thx [Martin Klepsch](https://github.com/martinklepsch), issue #88, PR #90)\n\n### 0.10.5\n\n- Sablono 0.7.3 fixes the issue when IE lost keystrokes in controlled inputs/textarea (#86)\n- React 15.2.1-1\n- Warn when `rum.core/react` is used without `rum.core/reactive` (thx [Martin Klepsch](https://github.com/martinklepsch), issue #82, PR #87)\n\n### 0.10.4\n\n- Ability to use `:pre` and `:post` checks in `rum.core/defc` (thx [Martin Klepsch](https://github.com/martinklepsch), PR #81)\n\n### 0.10.3\n\n- Fixed regression of `displayName` in 0.10.0\n- Bumped React to 15.2.0\n\n### 0.10.2\n\n- Fixed a bug when `:before-render` and `:will-update` weren’t called on subsequent renders\n\n### 0.10.1\n\n- Made `rum.core/state` public again\n- `:before-render` should be called on server-side rendering too (thx [Alexander Solovyov](https://github.com/piranha), PR #79)\n\n### 0.10.0\n\nA big cleanup/optmization/goodies release with a lot breaking changes. Read carefully!\n\n- [ BREAKING ] `cursor` got renamed to `cursor-in`. New `cursor` method added that takes single key (as everywhere in Clojure)\n- [ BREAKING ] `rum/mount` returns `nil` (because you [shouldn’t rely on return value of ReactDOM.render](https://github.com/facebook/react/issues/4936))\n- [ BREAKING ] `:transfer-state` is gone. All of component’s state is now transferred by default. If you still need to do something fancy on `componentWillReceiveProps`, new callback is called `:did-remount` callback\n- [ BREAKING ] removed `cursored` and `cursored-watch` mixins. They felt too unnatural to use\n- [ BREAKING ] removed `rum/with-props` (deprecated since 0.3.0). Use `rum/with-key` and `rum/with-ref` instead\n- [ BREAKING ] server-side rendering no longer calls `:did-mount` (obviously, that was a mistake)\n- [ BREAKING ] `:rum/id` is gone. If you need an unique id per component, allocate one in `:init` as store it in state under namespaced key\n\nWhen upgrading to 0.10.0, check this migration checklist:\n\n- Change all `rum/cursor` calls to `rum/cursor-in`\n- Find all `:transfer-state` mixins.\n  - If the only thing they were doing is something like `(fn [old new] (assoc new ::key (::key old)))`, just delete them.\n  - If not, rename to `:did-remount`\n- Check if you were using `rum/mount` return value. If yes, find another way to obtain component (e.g. via `ref`, `defcc` etc)\n- Replace `rum/with-props` with `rum/with-key`, `rum/with-ref` or `:key-fn`\n- Check that you weren’t relying on `:did-mount` in server-side rendering\n\nNow for the good stuff:\n\n- Cursors now support metadata, `alter-meta!` etc\n- Cursors can be used from Clojure\n- Added `:key-fn` to mixins. That function will be called before element creation, with same arguments as render fn, and its return value will be used as a key on that element\n- Mixins can specify `:before-render` (triggered at `componentWillMount` and `componentWillUpdate`) and `:after-render` (`componentDidMount` and `componentDidUpdate`) callback\n- Added `rum/ref` and `rum/ref-node` helpers, returning backing component and DOM node\n- Some client-side API functions added to server version (`dom-node`, `unmount`, `request-render` etc). Their implementation just throws an exception. This is to help you write less conditional directives in e.g. `:did-mount` or `:will-unmount` mixins. They will never be called, but won’t stop code from compiling either.\n\nAnd couple of optimizations:\n\n- Rum now makes use of staless components (nothing for you to do, if your component is defined via `defc` with no mixins, it’ll be automatically compiled to stateless component)\n- Rum will use React’s batched updates to perform rendering on `requestAnimationFrame` in a single chunk\n- Streamlined internals of component construction, removed `render-\u003emixin`, `args-\u003estate`, `element` and `ctor-\u003eclass`\n\n\n### 0.9.1\n\n- Added `rum.core/derived-atom`, a function that let you build reactive chains and directed acyclic graphs of dependent atoms. E.g. you want `*c` to always contain a value of `*a` plus a value of `*b` and update whenever any of them changes\n- Added `rum.core/dom-node` helper that takes state and finds corresponding top DOM node of a component. Can be called in mixins after initial render only\n- Fixed compatibility of `with-key` on nil-returning component in server rendering (thx [Alexander Solovyov](https://github.com/piranha), PR #73)\n\n### 0.9.0\n\n- Better support for server-side rendering of SVG\n- [ BREAKING ] Rum used to support multiple ways to specify attributes. You would expect that both `:allow-full-screen`, `:allowFullScreen` and `\"allowFullScreen\"` would be normalized to `allowfullscreen`. As a result, you have to face three problems:\n  - how do I decide which variant to use?\n  - how do I ensure consistency accross my team and our codebase?\n  - find \u0026 replace become harder\n\nStarting with 0.9.0, Rum will adopt “There’s Only One Way To Do It” policy. All attributes MUST be specified as kebab-cased keywords:\n\n| Attribute | What to use | What not to use |\n| --------- | ----------- | --------------- |\n| class     | `:class`    | ~~`:class-name`~~ ~~`:className`~~ |\n| for       | `:for`      | ~~`:html-for`~~ ~~`:htmlFor`~~ |\n| unescaped innerHTML | `:dangerouslySetInnerHTML { :__html { \"...\" }}` | |\n| uncontrolled value | `:default-value` | ~~`:defaultValue`~~ |\n| uncontrolled checked | `:default-checked` | ~~`:defaultChecked`~~ |\n| itemid, classid | `:item-id`, `:class-id` | ~~`:itemID`~~ ~~`:itemId`~~ ~~`:itemid`~~|\n| xml:lang etc | `:xml-lang` | ~~`:xml/lang`~~ ~~`:xmlLang`~~ ~~`\"xml:lang\"`~~ |\n| xlink:href etc | `:xlink-href` | ~~`:xlink/href`~~ ~~`:xlinkHref`~~ ~~`\"xlink:href\"`~~ |\n| xmlns | not supported |  |\n\nTo migrate to 0.9.0 from earlier versions, just do search-and-replace for non-standard variants and replace them with recommended ones.\n\n### 0.8.4\n\n- Improved server-side rendering for inputs (issue #67 \u0026 beyond)\n- Compatible server-side rendering of components that return nil (issue #64)\n- Upgraded React to 15.1.0\n\n### 0.8.3\n\n- `rum/render-static-markup` call for pure HTML templating. Use it if you’re not planning to connect your page with React later\n- `rum/def*` macros now correctly retain metadata that already exists on a symbol (thx [aJchemist](https://github.com/aJchemist), PR #62)\n\n### 0.8.2\n\n- Add `rum.core/unmount` function (thx [emnh](https://github.com/emnh), issue #61)\n\n### 0.8.1\n\n- Retain `:arglists` metadata on vars defined by `rum/def*` macros (thx [aJchemist](https://github.com/aJchemist), PR #60)\n\n### 0.8.0\n\n- Migrated to React 15.0.1\n- Optimized server-side rendering (~4× faster than Rum 0.7.0, ~2-3× faster than Hiccup 1.0.5)\n\n### 0.7.0\n\n- Server-side rendering via `rum/render-html` (thx [Alexander Solovyov](https://github.com/piranha))\n\n### 0.6.0\n\n- [ BREAKING ] Updated to [React 0.14.3](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html) (thx [Andrey Antukh](https://github.com/niwinz), PR #53)\n\n### 0.5.0\n\n- Added `:class-properties` to define arbitrary properties on a React class (thx [Karanbir Toor](https://github.com/currentoor), PR #44)\n- [ BREAKING ] Removed support for `:child-context-types` and `:context-types`. Use `{ :class-properties { :childContextTypes ..., :contextTypes ... } }` instead.\n\n### 0.4.2\n\n- Check for `setTimeout` in global scope instead of in window (thx [Alexander Solovyov](https://github.com/piranha), PR #43)\n\n### 0.4.1\n\n- Fixed bug with rum macros emitting wrong namespace. You can now require `rum.core` under any alias you want (thx [Stuart Hinson](https://github.com/stuarth), PR #42)\n\n### 0.4.0\n\n- [ BREAKING ] Core namespace was renamed from `rum` to `rum.core` to supress CLJS warnings\n\n### 0.3.0\n\n- Upgraded to React 0.13.3, Sablono 0.3.6, ClojueScript 1.7.48\n- New API to access context: `child-context`, `child-context-types`, `context-types` (thx [Karanbir Toor](https://github.com/currentoor), PR #37)\n- New `defcc` macro for when you only need React component, not the whole Rum state\n- [ BREAKING ] Component inner state (`:rum/state`) was moved from `props` to `state`. It doesn’t change a thing if you were using Rum API only, but might break something if you were relaying on internal details\n- Deprecated `rum/with-props` macro, use `rum/with-key` or `rum/with-ref` fns instead\n\n### 0.2.7\n\n- Allow components to refer to themselves (thx [Kevin Lynagh](https://github.com/lynaghk), pull request #30)\n- Support for multi-arity render fns (issue #23)\n\n### 0.2.6\n\n- Added `local` mixin\n\n### 0.2.5\n\n- Fixed argument destructuring in defc macro (issue #22)\n\n### 0.2.4\n\n- `will-update` and `did-update` lifecycle methods added (thx [Andrey Vasenin](https://github.com/avasenin), pull request #18)\n\n### 0.2.3\n\n- Components defined via `defc/defcs` will have `displayName` defined (thx [Ivan Dubrov](https://github.com/idubrov), pull request #16)\n- Not referencing `requestAnimationFrame` when used in headless environment (thx @[whodidthis](https://github.com/whodidthis), pull request #14)\n\n### 0.2.2\n\n- Compatibility with clojurescript 0.0-2758, macros included automatically when `(:require rum)`\n\n### 0.2.1\n\n- Updated deps to clojurescript 0.0-2727, react 0.12.2-5 and sablono 0.3.1\n\n### 0.2.0\n\n- [ BREAKING ] New syntax for mixins: `(defc name \u003c mixin1 mixin2 [args] body...)`\n- New `defcs` macro that adds additional first argument to render function: `state`\n- Ability to specify `key` and `ref` to rum components via `with-props`\n\n### 0.1.1\n\n- Fixed a bug when render-loop tried to `.forceUpdate` unmounted elements\n- Fixed a cursor leak bug in `reactive` mixin\n- Removed `:should-update` from `reactive`, it now will be re-rendered if re-created by top-level element\n- Combine `reactive` with `static` to avoid re-rendering if component is being recreated with the same args\n\n## Acknowledgements\n\nRum was build on inspiration from [Quiescent](https://github.com/levand/quiescent), [Om](https://github.com/swannodette/om) and [Reagent](https://github.com/reagent-project/reagent).\n\nAll heavy lifting done by [React](http://facebook.github.io/react/), [Ŝablono](https://github.com/r0man/sablono) and [ClojureScript](https://github.com/clojure/clojurescript).\n\n## License\n\nCopyright © 2014–2016 Nikita Prokopov\n\nLicensed under Eclipse Public License (see [LICENSE](LICENSE)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froman01la%2Fprum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froman01la%2Fprum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froman01la%2Fprum/lists"}