• Stars
    star
    157
  • Rank 238,399 (Top 5 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created over 6 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Efficiently render and re-render immutable data

dumdom - The dumb DOM component library

dumdom is a component library that renders (and re-renders) immutable data efficiently. It delivers on the basic value proposition of React and its peers while eschewing features like component local state and object oriented APIs, and embracing ClojureScript features like immutable data structures.

dumdom is API compatible with Quiescent, and can be used as a drop-in replacement for it so long as you don't use React features directly. Refer to differences from React for things to be aware of.

dumdom is currently a wrapper for Snabbdom, but that should be considered an implementation detail, and may be subject to change. Using snabbdom features not explicitly exposed by dumdom is not recommended.

dumdom aims to be finished, stable, and worthy of your trust. Breaking changes will never be intentionally introduced to the codebase. For this reason, dumdom does not adhere to the "semantic" versioning scheme.

In addition to being API compatible with Quiescent, dumdom supports:

  • Hiccup syntax for components
  • Event handlers as data
  • Rendering to strings (useful for server-side rendering from both the JVM and node.js)
  • Efficient "inflation" of server-rendered markup on the client side

Table of contents

Install

With tools.deps:

cjohansen/dumdom {:mvn/version "2023.04.26"}

With Leiningen:

[cjohansen/dumdom "2023.04.26"]

Example

Using hiccup-style data:

(require '[dumdom.core :as dumdom :refer [defcomponent]])

(defcomponent heading
  :on-render (fn [dom-node val old-val])
  [data]
  [:h2 {:style {:background "#000"}} (:text data)])

(defcomponent page [data]
  [:div
    [heading (:heading data)]
    [:p (:body data)]])

(dumdom/render
 [page {:heading {:text "Hello world"}
        :body "This is a web page"}]
 (js/document.getElementById "app"))

Using the Quiescent-compatible function API:

(require '[dumdom.core :as dumdom :refer [defcomponent]]
         '[dumdom.dom :as d])

(defcomponent heading
  :on-render (fn [dom-node val old-val])
  [data]
  (d/h2 {:style {:background "#000"}} (:text data)))

(defcomponent page [data]
  (d/div {}
    (heading (:heading data))
    (d/p {} (:body data))))

(dumdom/render
 (page {:heading {:text "Hello world"}
        :body "This is a web page"})
 (js/document.getElementById "app"))

Rationale

Of the many possible options, Quiescent is to me the perfect expression of "React in ClojureScript". It's simple, light-weight, does not allow component-local state, and pitches itself as strictly a rendering library, not a state management tool or UI framework.

While Quiescent has been done (as in "complete") for a long time, it is built on React, which is on a cycle of recurring "deprecations" and API changes, making it hard to keep Quiescent up to date with relevant security patches etc. At the same time, React keeps adding features which are of no relevance to the API Quiescent exposes, thus growing the total bundle size for no advantage to its users.

dumdom provides the same API as that of Quiescent, but does not depend on React. It aims to be as stable and complete as Quiescent, but still be able to ship occasional security patches as they are made to the underlying virtual DOM library. dumdom aims to reduce the amount of churn in your UI stack.

Limitations

Because dumdom is not based on React, you opt out of the "React ecosystem" entirely by using it. If you depend on a lot of open source/shared React components, or other React-oriented tooling, dumdom might not be the best fit for you.

Because dumdom does not offer any kind of component local state, it cannot be used as a holistic UI framework - it's just a rendering library. It does not come with any system for routing, dispatching actions, or managing state (either inside or outside of components), and is generally a batteries-not-included tool. I consider this a strength, others may see it differently.

Differences from Quiescent

Dumdom strives to be API compliant with Quiescent to the degree that it should be a drop-in replacement for Quiescent in any project that does not rely explicitly on any React APIs or third-party components. It does not necessarily commit to all the same restrictions that the Quiescent API imposes. The following is a list of minor differences between the two:

  • Quiescent does not allow the use of :on-render along with either of :on-mount and :on-update. Dumdom acknowledges that some components will implement :on-render and :on-mount or :on-update, and allows this.
  • Dumdom doesn't really care about TransitionGroup. You are free to use them, but the animation callbacks will work equally well outside TransitionGroup. This may cause breakage in some cases when porting from Quiescent to Dumdom. The risk is pretty low, and the upside is significant enough to allow Dumdom to take this liberty.

Differences from React

In React, onChange is really onInput. This is not true in dumdom. When swapping out Quiescent and React for dumdom, you must replace all occurrences of onChange with onInput to retain behavior.

Using with Devcards

Devcards is a system for rendering React components in isolation. Because dumdom components are not React components, they need some wrapping for Devcards to make sense of them.

You need to add dumdom-devcards as a separate dependency. Then use the dumdom.devcards namespace just like you would devcards.core:

(require '[dumdom.devcards :refer-macros [defcard]])

(defcard my-dumdom-card
  (my-dumdom-component {:value 0}))

Contribute

Feel free to report bugs and, even better, provide bug fixing pull requests! Make sure to add tests for your fixes, and make sure the existing ones stay green before submitting fixes.

make test

You can also run the tests in a browser with figwheel, which might be more useful during development:

clojure -A:dev:repl

Then open http://localhost:9595/figwheel-extra-main/tests.

If you're not yet sure how to formulate a test for your feature, fire up http://localhost:9595/ and play around in ./dev/dumdom/dev.cljs until you figure it out. More visually oriented code can be tested with devcards instead. Add a devcard to ./devcards/dumdom, and inspect the results at http://localhost:9595/devcards.html

If you have ideas for new features, please open an issue to discuss the idea and the API before implementing it to avoid putting lots of work into a pull request that might be rejected. I intend to keep dumdom a focused package, and don't want it to accrete a too wide/too loosely coherent set of features.

Running from Emacs

There is a .dir-locals.el file in the root of this repo to help you out. Run cider-jack-in-cljs, and you should get a REPL and figwheel running on port 9595:

Documentation

The vast majority of use-cases are covered by using hiccup-style markup for DOM elements, defining custom components with defcomponent, and rendering the resulting virtual DOM to an element with render:

(require '[dumdom.core :as dumdom :refer [defcomponent]])

(defcomponent my-component [data]
  [:div
    [:h1 "Hello world!"]
    [:p (:message data)]])

(dumdom/render
  (my-component {:message "Hello, indeed"})
  (js/document.getElementById "app"))

Components defined by defcomponent are functions, as demonstrated in the above example. You can also use them for hiccup markup, e.g.:

(dumdom/render
  [my-component {:message "Hello, indeed"}]
  (js/document.getElementById "app"))

The strength of hiccup markup is being able to represent DOM structures as pure data. Because functions are not data, there is no real benefit to using hiccup syntax for custom components, so I typically don't, but it doesn't make any difference either way.

Building virtual DOM

Virtual DOM elements are built with hiccup markup:

[tagname attr? children...]

tagname is always a keyword, attributes are in an optional map, and there might be one or more children, or a list of children. Beware that children should not be provided as a vector, lest it be interpreted as a new hiccup element.

Class names and ids can be inlined on the tag name selector (e.g. :div.someclass#someid). Classes inlined on the selector can even be combined with classes provided with :class:

[:div.mtm {:class (when open? :open)}]

Classes can be strings, keywords, or even collections of strings and keywords:

[:div#the-game
  {:class [:game
           (when (:offline? game) :offline)
           (when (:loading? game) :loading)]}]

For API compatibility with Quiescent, elements can also be created with the functions in dumdom.dom:

(dumdom.dom/div {:style {:border "1px solid red"}} "Hello world")

Note that with these functions, the attribute map is not optional, and must always be provided, even if empty.

Keys

You can specify the special attribute :key do help dumdom recognize DOM elements that move. :key should be set to a value that is unique among the element's siblings. For instance, if you are rendering lists of things, setting a key on each item means dumdom can update the rendered view by simply moving existing elements around in the DOM. Not setting the key will lead dumdom to work harder to align the DOM with the virtual representation:

(require '[dumdom.core :as dumdom :refer [defcomponent]])

(defcomponent list-item [fruit]
  [:li {:key fruit} fruit])

(def el (js/document.getElementById "app"))

(dumdom/render [:ul (map list-item ["Apples" "Oranges" "Kiwis"])] el)

;; This will now result in reordering the DOM elements, instead of recreating them
(dumdom/render [:ul (map list-item ["Oranges" "Apples" "Kiwis"])] el)

Event listeners

Event handlers can be attached to either camelCased or snaked-cased properties, e.g. :onClick and :on-click both work. :on-click is suggested as the idiomatic approach.

Event handlers can be either functions or data dispatched via a render-global event handler. Using data is suggested, as it improves dumdom's ability to compare data between calls to dumdom.core/render.

Event handler data

By setting a function with dumdom.core/set-event-handler!, you can have a single top-level handler that will be triggered for any virtual element event handler property that is not a function. In other words: event-handlers can be expressed as data.

(require '[dumdom.core :as dd])

(def el (document.getElementById "app"))

(dd/set-event-handler!
 (fn [e actions]
   (doseq [action actions]
     (prn "Triggered action" action (.-target e)))))

(dd/render
 [:div
  [:h1 "Fruits"]
  [:ul
   [:li {:on-click [[:fruit/select :apple]]} "Apple"]
   [:li {:on-click [[:fruit/select :banana]]} "Banana"]
   [:li {:on-click [[:fruit/select :kiwi]]} "Kiwi"]]]
 app)

The global event handler function is called with two arguments: the event object and the data assigned to the event handler attribute. dumdom does not impose any restrictions on the structure of event data - if an event handler is not a function, data is passed on to the top-level handler.

Event handler functions

Event handler functions will be called with one argument - the JavaScript event object:

[:a {:href "#"
     :onClick (fn [e]
                (.preventDefault e)
                (prn "You clicked me!"))} "Click me!"]

Custom event data dispatch

Sometimes you want to manually dispatch event data - perhaps you need to perform some runtime JavaScript before dispatching the event, or something similar. You can use dumdom.core/dispatch-event-data for this:

(require '[dumdom.core :as d])

[:a {:href "#"
     :onClick (fn [e]
                (.preventDefault e)
                (d/dispatch-event-data e [[:fruit/select :apple]]))}
  "Choose the apple!"]

Creating components

You create components with defcomponent or component - the first is just a convenience macro for def + component:

(require '[dumdom.core :refer [component defcomponent]])

(defcomponent my-component
  :on-render (fn [e] (js/console.log "Rendered" e))
  [data]
  [:div "Hello world"])

;; ...is the same as:

(def my-component
  (component
    (fn [data]
      [:div "Hello world"])
    {:on-render (fn [e] (js/console.log "Rendered" e))}))

Refer to the API docs for component for details on what options it supports, life-cycle hooks etc, and the API docs for defcomponent for more on how to use it.

A dumdom component is a function. When you call it with data it returns something that dumdom knows how to render, e.g.:

(dumdom.core/render (my-component {:id 42}) root-el)

You can also invoke the component with hiccup markup, although there is no real benefit to doing so - the result is exactly the same:

(dumdom.core/render [my-component {:id 42}] root-el)

Component arguments

When you call a dumdom component with data, it will recreate the virtual DOM node only if the data has changed since it was last called. However, this decision is based solely on the first argument passed to the component. So while you can pass any number of arguments to a component beware that only the first one is used to influence rendering decisions.

This design is inherited from Quiescent, and the idea is that you can pass along things like core.async message channels without having them interfering with the rendering decisions. When passing more than one argument to a dumdom component, make sure that any except the first one are constant for the lifetime of the component.

This only applies to components created with component/defcomponent, not virtual DOM functions, which take any number of DOM children.

CSS transitions

CSS transitions can be defined inline on components to animate the appearing or disappearing of elements. There are three keys you can use to achieve this effect:

  • :mounted-style - Styles that will apply after the element has been mounted
  • :leaving-style - Styles that will apply before the element is removed from its parent - the element will not be removed until all its transitions complete
  • :disappearing-style - Styles that will apply before the element is removed along with its parent element is being removed - the element will not be removed until all its transitions are complete

As an example, if you want an element to fade in, set its opacity to 0, and then its :mounted-style opacity to 1. To fade it out as well, set its :leaving-styles opacity to 0 again. Remember to enable transitions for the relevant CSS property:

[:div {:style {:opacity "0"
               :transition "opacity 0.25s"}
       :mounted-style {:opacity "1"}
       :leaving-style {:opacity "0"}}
  "I will fade both in and out"]

Class name transitions

In order to be API compatible with Quiescent, dumdom supports React's CSSTransitionGroup for doing enter/leave transitions with class names instead of inline CSS. Given the following CSS:

.example-leave {
  opacity: 1;
  transition: opacity 0.25s;
}

.example-leave-active {
  opacity: 0;
}

Then we could fade out an element with:

(require '[dumdom.core :refer [CSSTransitionGroup]])

(CSSTransitionGroup {:transitionName "example"}
  [[:div "I will fade out"]])

Note that CSSTransitionGroup takes a vector/seq of children. Refer to the API docs for CSSTransitionGroup for more details. In general, using inline CSS transitions will be more straight-forward, and is recommended.

Refs

A :ref on an element is like an :on-mount callback that you can attach from "the outside":

;; NB! Just an example, there are better ways to do this with CSS

(defn square-element [el]
  (set! (.. el -style -height) (str (.-offsetWidth el) "px")))

[:div {:style {:border "1px solid red"}
       :ref square-element} "I will be in a square box"]

The :ref function will be called only once, when the element is first mounted. Use this feature with care - do not use it with functions that behave differently at different times. Consider this example:

(defcomponent my-component [data]
  [:div
    [:h1 "Example"]
    [:div {:ref (when (:actionable? data)
                  setup-click-indicator)}
      "I might or might not be clickable"]])

While this looks reasonable, refs are only called when the element mounts. Thus, if the value of (:actionable? data) changes, the changes will not be reflected on the element. If you need to conditionally make changes to an element this way, create a custom component and use the :on-render hook instead, which is called every time data changes.

Server rendering

Dumdom supports rendering your components to strings on the server and then "inflating" the view client-side. Inflating consists of associating the resulting DOM elements with their respective virtual DOM nodes, so dumdom can efficiently update your UI, and adding client-side event handlers so users can interact with your app.

Even though it sounds straight-forward, using server rendering requires that you write your entire UI layer in a way that can be loaded on both the server and client. This is easier said than done.

To render your UI to a string on the server:

(require '[dumdom.string :as dumdom])

(defn body []
  (str "<html><body><div id=\"app\">"
       (dumdom/render [:div [:h1 "Hello world"]])
       "</div></body></html>"))

(defn index [req]
  {:status 200
   :headers {"content-type" "text/html"}
   :body (body)})

Then, on the client:

(require '[dumdom.inflate :as dumdom])

(dumdom/render
  [:div [:h1 "Hello world]]
  (js/document.getElementById "app"))

To update your view, either call dumdom.inflate/render again, or use dumdom.core/render.

API Docs

(dumdom.core/render component element)

Render the virtual DOM node created by the component into the specified DOM element. Component can be either hiccup-style data, like [:div {} "Hello"] or the result of calling component functions, e.g. (dumdom.dom/div {} "Hello").

(dumdom.core/unmount element)

Clear the element and discard any internal state related to it.

(dumdom.core/render-once component element)

Like dumdom.core/render, but entirely stateless. render needs to use memory in order for subsequent calls to render as little as possible as fast as possible. If you don't intend to update the rendered DOM structure, render-once is more efficient as it does not use any memory. Subsequent calls to this function with the same arguments will always destructively re-render the entire tree represented by the component.

(dumdom.core/component render-fn [opt])

Returns a component that uses the provided function for rendering. The resulting component will only call through to its rendering function when called with data that is different from the data that produced the currently rendered version of the component.

The rendering function can be called with any number of arguments, but only the first one will influence rendering decisions. You should call the component with a single immutable value, followed by any number of other arguments, as desired. These additional constant arguments are suitable for passing messaging channels, configuration maps, and other utilities that are constant for the lifetime of the rendered element.

The rendering function can return hiccup-style data or the result of calling component functions.

The optional opts argument is a map with additional properties:

:on-mount - A function invoked once, immediately after initial rendering. It is passed the rendered DOM node, and all arguments passed to the render function.

:on-update - A function invoked immediately after an updated is flushed to the DOM, but not on the initial render. It is passed the underlying DOM node, the value, and any constant arguments passed to the render function.

:on-render - A function invoked immediately after the DOM is updated, both on the initial render and subsequent updates. It is passed the underlying DOM node, the value, the old value, and any constant arguments passed to the render function.

:on-unmount - A function invoked immediately before the component is unmounted from the DOM. It is passed the underlying DOM node, the most recent value and the most recent constant args passed to the render fn.

:will-appear - A function invoked when this component is added to a mounting container component. Invoked at the same time as :on-mount. It is passed the underlying DOM node, a callback function, the most recent value and the most recent constant args passed to the render fn. The callback should be called to indicate that the element is done "appearing".

:did-appear - A function invoked immediately after the callback passed to :will-appear is called. It is passed the underlying DOM node, the most recent value, and the most recent constant args passed to the render fn.

:will-enter - A function invoked when this component is added to an already mounted container component. Invoked at the same time as :on.mount. It is passed the underlying DOM node, a callback function, the value and any constant args passed to the render fn. The callback function should be called to indicate that the element is done entering.

:did-enter - A function invoked after the callback passed to :will-enter is called. It is passed the underlying DOM node, the value and any constant args passed to the render fn.

:will-leave - A function invoked when this component is removed from its containing component. Is passed the underlying DOM node, a callback function, the most recent value and the most recent constant args passed to the render fn. The DOM node will not be removed until the callback is called.

:did-leave - A function invoked after the callback passed to :will-leave is called (at the same time as :on-unmount). Is passed the underlying DOM node, the most recent value and the most recent constant args passed to the render fn.

:name - A string representing the name of the component. Useful when rendering component comments during development.

(dumdom.core/defcomponent name & args)

Creates a component with the given name, a docstring (optional), any number of option->value pairs (optional), an argument vector and any number of forms body, which will be used as the rendering function to dumdom.core/component.

For example:

(defcomponent widget
  \"A Widget\"
  :on-mount #(...)
  :on-render #(...)
  [value constant-value]
  (some-child-components))

Is shorthand for:

(ns your.app)

(def widget (dumdom.core/component
  (fn [value constant-value] (some-child-components))
  {:name "your.app/widget"
   :on-mount #(...)
   :on-render #(...)}))

(dumdom.core/TransitionGroup opt children)

Exists solely for drop-in compatibility with Quiescent. Effectively does nothing. Do not use for new applications.

(dumdom.core/CSSTransitionGroup opt children)

Automates animation of entering and leaving elements via class names. If called with {:transitionName "example"} as opt, child elements will have class names set on them at appropriate times.

When the transition group mounts, all pre-existing children will have the class name example-enter set on them. Then, example-enter-active is set. When all transitions complete on the child node, example-enter-active will be removed again.

When elements are added to an already mounted transition group, they will have the class name example-appear added to them, if appear animations are enabled (they are not by default). Then the class name example-appear-active will be set, and then removed after all transitions complete.

When elements are removed from the transition group, the class name example-leave will be set, followed by example-leave-active, which is then removed after transitions complete.

You can control which transitions are used on elements, and how their classes are named with the following options:

transitionName

When set to a string: base-name for all classes. Can also be set to a map to control individual class names:

{:transitionName {:enter "entrance"}} ;; entrance / entrance-active
{:transitionName {:enter "enter" :enterActive "entering"}} enter / entering

And similarly for :leave/:leaveActive and :appear/:appearActive.

transitionEnter

Boolean, set to false to disable enter transitions. Defaults to true.

transitionAppear

Boolean, set to true to enable appear transitions. Defaults to false.

transitionLeave

Boolean, set to false to disable leave transitions. Defaults to true.

(dumdom.dom/[el] attr children)

Functions are defined for every HTML element:

(dumdom.dom/a {:href "https://cjohansen.no/"} "Blog")

Attributes are not optional, use an empty map if you don't have attributes. Children can be text, components, virtual DOM elements (like the one above), or a seq with a mix of those.

(dumdom.core/render-string component)

Renders component to string. Available on Clojure as well, and can be used to do server-side rendering of dumdom components.

(dumdom.inflate/render component el)

Renders the component into the provided element. If el contains server-rendered dumdom components, it will be inflated faster than a fresh render (which forcefully rebuilds the entire DOM tree).

NB! Currently, only string keys are supported. If a component uses non-string keys, inflating will not work, and it will be forcefully re-rendered. This limitation might be adressed in a future release.

dumdom.component/*render-eagerly?*

When this var is set to true, every existing component will re-render on the next call after a new component has been created, even if the input data has not changed. This can be useful in development - if you have any level of indirection in your rendering code (e.g. passing a component function as the "static arg" to another component, multi-methods, etc), you are not guaranteed to have all changed components re-render after a compile and hot swap. With this var set to true, changing any code that defines a dumdom component will cause all components to re-render.

The var defaults to false, in which case it has no effect. Somewhere in your development setup, add

(set! dumdom.component/*render-eagerly?* true)

dumdom.component/*render-comments?*

When this var is set to true (defaults to false), every named component will be prepended by a DOM comment displaying the component's name. Enabling this feature is particularly useful during development to get an overview of which component is responsible for rendering a given fragment of the DOM.

(set! dumdom.component/*render-comments?* true)

Examples

Unfortunately, there is no TodoMVC implementation yet, but there is Yahtzee! Please get in touch if you've used dumdom for anything and I'll happily include a link to your app.

Check out this cool dungeon crawler (source) made with dumdom.

Changelog

2023.04.26

  • Make dumdom actually work well in all of these combinations: figwheel or shadow-cljs with simple or advanced compilation.

2023.03.27

  • Support using dumdom with shadow-cljs.
  • Add dumdom.core/dispatch-event-data

2022.09.28

  • Fix bug where :class :keyword threw exception.

2022.09.15

  • Fix regression in the string render where inline JavaScript in event handler attributes where removed.
  • Throw an error when trying to use non-{string, keyword, collection} class names.

2022.09.12

  • Fix problems where :className would cause :classto be ignored.
  • Support specifying :class as a sequence of strings and/or keywords.

2022.04.12

  • Fix detection of non-function event handlers
  • Support mixing dots for hiccup classes with both :className and :class
  • Add support for global event handler outside the call to render

2022.02.03

  • Fix a bug where components rendering other components in the root positioned caused life-cycle hooks to not be called correctly in the nested component. Thanks to Anders Furset for solving this with me.

2021.10.29

2021.10.25

  • Properly render hiccup and Quiescent-style (d/div {} ,,,) with nested lists by flattening nested lists. In other words, (d/div {} (list (list "Hello"))) renders the same as (d/div {} "Hello").

2021.07.14

  • Enumerate keys, so duplicated keys on the same level in the virtual DOM tree do not cause elements or components to get mixed up with each other. This fixes a bug where two components sharing key and data would share a cache-entry, and only the first instance would have its life-cycle hooks called.

2021.06.29

  • Bugfix: When a component rendered another component directly (e.g. with no additional wrapping DOM elements), Dumdom would not call the inner component's :on-unmount hook - now it does.

  • Force elements with innerHTML to have a key. Works around shortcomings in Snabbdom related to innerHTML manipulation.

  • New feature: dumdom.core/unmount (see docs above).

  • New feature: dumdom.core/render-once (see docs above).

  • Major implementation change: Move Dumdom's vdom representation from Snabbdom's object model to Clojure maps. This moves more of the implementation from JavaScript to Clojure, and more importantly addresses some weird behavior in complex DOM layouts. The vdom objects created by Snabbdom are mutated by Snabbdom to maintain a reference to the rendered elements. In other words, the vdom objects we provide as input to Snabbdom ends up doubling as Snabbdom's internal state. Hanging on to these from the outside and feeding them back to Snabbdom at a later point (e.g. when should-component-update? is false) mostly works, but has been proven to cause very unfortunate behavior in certain bespoke situations.

    As always, there are no changes to the public API. However, this is a invasive change to Dumdom's implementation, so rigorous testing is advised.

    NB! The internal data-structure of virtual DOM nodes have changed as a consequence, both for Clojure (different map structure) and ClojureScript (maps, not Snabbdom objects). This is considered implementation changes, as these are not documented features of Dumdom. If you have somehow ended up relying on those you should investigate changes further.

2021.06.21

  • Render comment nodes in place of nils. This works around a quirk of Snabbdom (as compared to React) where replacing a nil with an element can prematurely cause transition effects due to how Snabbdom reuses DOM elements. See this issue for more information.

2021.06.18

  • Retracted due to a typo which made the artefact unusable. Sorry about that!

2021.06.16

  • BREAKING: Dumdom no longer bundles dumdom.devcards - add dumdom-devcards separately to your dependencies. This change only affects projects using dumdom.devcards, and requires no other changes to your code than adding the separate namespace. I'm very sorry for this breaking change, but including devcards was a mistake that required you to have devcards on path in production, which is not a good place to be.
  • Bug fix: {:dangerouslySetInnerHTML {:__html nil}} was mistakenly a noop. It now clears any previously set innerHTML.
  • Work around a bug in Snabbdom by temporarily bundling a patched version.

2021.06.11

  • Properly support data-attributes. Just include them with the data- prefix, and dumdom will render them: [:div {:data-id "123"} "Hello"].

2021.06.10

  • Upgrade Snabbdom to version 3.0.3
  • Make sure all style properties are strings. Fixes a strange glitch where :opacity 0 would not always set opacity to 0.

2021.06.08

  • Pixelize more styles, switch from a allow-list to a deny-list of properties to pixelize. Thanks to Magnar Sveen.

2021.06.07

  • Add feature to eagerly re-render components in development, see *render-eagerly?* above.

  • Pass old data to :on-render and :on-update functions. Previously, these would receive [dom-el data statics], now they fully match Quiescent's behavior, receiving [dom-el data old-data statics].

2021.06.02

  • Allow TransitionGroup to take either a single component or a seq of components.

2021.06.01

  • Bugfix: Don't throw exceptions when components return nil
  • If the root component returns nil, remove previously rendered content.

2021.05.31

  • Support pixel values for :border, :border-left, :border-right, :border-top, :border-bottom.

2021.05.28

  • Bug fix: Using non-primitive values (including keywords) with component keys (both inline :key and the result from :keyfn) would cause weird rendering issues. Any value can now be safely used as a component key.
  • Bug fix: CSS properties that take pixel values could only be specified as numbers when the camelCased property name was used. Now also supports this behavior with lisp-casing, e.g. :style {:margin-left 10} produces a 10 pixel left margin.
  • Bug fix: Support numbers for border-{bottom,top}-{left,right}-radius (and their camel cased counterparts).
  • Remove an attempt at a micro-optimization that instead caused a minimal performance penalty.

2021.05.07

  • Fix failing production builds of 2020.10.19 due to missing externs

2020.10.19

  • Add support for Shadow CLJS

2020.07.04

  • Properly render nested seqs to DOM strings

2020.06.21

  • Don't render :ref functions to the DOM
  • Don't render nil styles when rendering to strings

2020.06.04

  • Added support for styles as strings in hiccup elements, e.g. [:a {:style "padding: 2px"}]

2020.02.12

  • Bugfix: Don't trip on numbers when rendering to string
  • Bugfix: Don't trip on event handlers when rendering to string

2020.01.27

  • Added support for :dangerouslySetInnerHTML

2019.09.16

  • Fixed animation style properties, which where inadvertently broken in the previous release :'(

2019.09.05-1

  • Support using dashed cased attributes, e.g. :xlink-href, :view-box etc
  • When passing nil to an attribute, do not render that attribute with the string "null" or an empty value - remove the attribute

2019.09.05

  • Support :div.class#id style hiccup in server-rendering as well.

2019.02.03-3

  • Built jar with a different version of pack.alpha, so cljdoc is able to analyze it

2019.01.21

  • Document and launch :mounted-style, :leaving-style, and :disappearing-style

2019.01.19

  • Added support for hiccup-style data
  • Added rendering components to strings
  • Added inflating server-rendered DOM

2018.12.22

  • Added snabbdom externs that hold up during advanced compilation

2018.12.21

Initial release

Roadmap

  • Provide TodoMVC app
  • Port Snabbdom (roughly, not API compatibly) to ClojureScript

License

Copyright Β© 2018-2022 Christian Johansen

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

More Repositories

1

juicer

A command line tool for JavaScript and CSS developers
Ruby
632
star
2

portfolio

Clojure
222
star
3

twibot

Simple framework for creating Twitter bots, inspired by Sinatra
Ruby
178
star
4

.emacs.d

My Emacs config
Emacs Lisp
152
star
5

react-sweeper

Minesweeper in ES6 using React/immutable-js
JavaScript
106
star
6

replicant

A native ClojureScript virtual DOM renderer - render hiccup directly
Clojure
78
star
7

js-atom

Clojure(Script) atoms, in JavaScript
JavaScript
60
star
8

use_case

A small abstraction for encapsulating non-trivial business logic in Ruby applications
Ruby
48
star
9

courier

A high-level http client for Clojure and ClojureScript
Clojure
38
star
10

powerpack

A batteries-included static web site toolkit for Clojure
Clojure
35
star
11

validatious

Client side form validation with unobtrusive JavaScript
JavaScript
27
star
12

sinon-qunit

A small Sinon.JS adapter for QUnit that provides automatic sandboxing of mocks and stubs
JavaScript
26
star
13

om-sweeper

An (almost complete) implementation of Minesweeper in ClojureScript/Om (React.js)
Clojure
24
star
14

jstdutil

Small wrapper around Google's JsTestDriver to add colors, autotest and easier command invocation
Ruby
23
star
15

m1p

Map interpolation and DIY i18n/theming toolkit
Clojure
21
star
16

sinon-web

Single-page website for Sinon.JS
JavaScript
21
star
17

hiccup-find

Utilities to help you test hiccup markup generating functions
Clojure
19
star
18

phosphor-clj

Phosphor Icons as hiccup for Clojure and ClojureScript
Clojure
19
star
19

gadget-inspector

ClojureScript data browser - use from a Chrome extension or over a server in any browser
Clojure
18
star
20

powerblog

A step-by-step tutorial for building a static site with Stasis Powerpack
Clojure
16
star
21

auth0-ring

A ring middleware for OpenID Connect authentication with Auth0 in your Clojure app
Clojure
13
star
22

cjohansen-no

Source code and content for cjohansen.no doubling as a demo of Clojure/Stasis powered static sites.
JavaScript
13
star
23

imagine

Image engine for web apps: Crop, resize and filter images on the fly or to disk
Clojure
11
star
24

pharmacist

Declarative data fetching for Clojure(Script)
Clojure
9
star
25

when-rb

Ruby port of when.js
Ruby
9
star
26

form-app

Demonstrating how to build frontends around stateless, data-driven components
Clojure
8
star
27

sinon-nodeunit

A small Sinon.JS adapter for nodeunit that provides automatic sandboxing of mocks and stubs
JavaScript
8
star
28

asciidoclj

Clojure API for Asciidoc
Clojure
8
star
29

node-assert-extras

Additional assertions for Node.js
JavaScript
7
star
30

jscontext

Syntactical sugar for JsUnitTest tests - should and contexts
JavaScript
6
star
31

watch-tree

Watch directories recursively for changes using inotify for Linux
JavaScript
6
star
32

austin-repl-example

A sample Austin ClojureScript REPL setup
Clojure
6
star
33

spasm

"Just enough structure" for React-based single page web apps
JavaScript
6
star
34

Juicer2

Some initial thoughts and sketches for Juicer 2, partial rewrite of Juicer
Ruby
6
star
35

portable-text-clj

Render sanity.io Portable Text to HTML with Clojure
Clojure
6
star
36

em_pessimistic

popen with stderr and DeferrableChildProcess with errback for EventMachine
Ruby
6
star
37

fontawesome-clj

FontAwesome icons as hiccup for Clojure(Script)
Clojure
6
star
38

ubc_monitor

Monitor resource usage in OpenVZ backed VPS'.
Ruby
5
star
39

svn_import

Rake task for importing a brand new Rails project to subversion - ignoring certain files
5
star
40

live-search

A simple live search jQuery plugin, developed to showcase TDD with JavaScript
JavaScript
5
star
41

cullquery

Convenience functions for Cull.JS that use jQuery
JavaScript
4
star
42

funlines

A tiny function pipelining utility for Clojure
Clojure
4
star
43

mysql_foreign_keys

MySQL foreign keys for Rails migraitons
4
star
44

shufflify

A Spotify multi-playlist weighted shuffler
Go
3
star
45

internote

A fork of the abandoned Firefox plugin that allows sticky post-it notes on web pages
3
star
46

dumdom-devcards

Devcards for dumdom
Clojure
3
star
47

ezgravatar

A template that enables Gravatars for eZ Publish
3
star
48

clj-nats

Clojure wrapper for the official NATS.io Java SDK
Clojure
3
star
49

mmouse

Mouse movement utilities for JavaScript
JavaScript
3
star
50

clj-event-source

Clojure server-sent events Event Source client
Clojure
3
star
51

parrot

Stub HTTP responses for Clojure tests
Clojure
3
star
52

sasha

Stateless component libary for ClojureScript and dumdom
Clojure
2
star
53

libdolt

The core Dolt APIs, without any web framework dependencies
Ruby
2
star
54

chainable

A simple implementation of the chain of responsibility pattern in Ruby
2
star
55

ranger

Custom range input control for JavaScript
JavaScript
2
star
56

acts_as_prototype

A Rails plugin that adds a prototype and a property list to ActiveRecord objects (prototypes can be chained)
Ruby
2
star
57

dolt

Stand-alone Git repository browser
Ruby
2
star
58

cjohansen.github.io

Github pages
JavaScript
1
star
59

makeup

Markup and syntax highlighting
Ruby
1
star
60

cf-apply-template

A small wrapper for cloudfont create-stack/update-stack
Shell
1
star
61

revealjs-mode

An Emacs minor-mode for working with revealjs HTML files
Emacs Lisp
1
star
62

boosterconf2014

Node.js and React workshop at boosterconf.no 2014
1
star
63

om-tutorial-austin

The Om basic tutorial with the Austin browser repl
Clojure
1
star
64

cljs-compiler-woes

A reproduction of a ClojureScript compiler issue
Clojure
1
star
65

cider-figwheel-main

A demonstration of flaky CIDER/figwheel.main behavior
Clojure
1
star
66

facenode

Code from a live-coding talk. You had to be there.
JavaScript
1
star
67

docker-lein-slimer

A Docker image with Clojure, Leiningen and Slimer.JS
1
star
68

eval-lines.el

Evaluate Ruby code in Emacs. Lines ending in #=> will have the value of evaluating the line inserted after it
Emacs Lisp
1
star
69

validate

Form validation logic. Code is written for educational purposes, do not expect this to be maintained as a library.
JavaScript
1
star
70

origize.el

Emacs Lisp
1
star
71

cljsjs-moment-node

Demonstrating an issue with cljsjs/moment, node and optimizations: simple
Clojure
1
star
72

tddjs-com

The tddjs.com site
Shell
1
star
73

cljsns

Reproducing a surprising ClojureScript build snag
HTML
1
star
74

adventofcode2022

My Advent of code 2022 entries
Clojure
1
star
75

loose-client

A lean and relaxed chatting client
HTML
1
star
76

execration-no

execration.no
HTML
1
star
77

varnish-vmods-docker

A Varnish Dockerfile that includes compiled vmods
Dockerfile
1
star
78

tempcalc

A temperature calculator
Clojure
1
star
79

virtuoso-ui

Clojure
1
star
80

connect-four

The classic game of connect four
JavaScript
1
star
81

yahtzee-cljs

A functional programming implementation of Yahtzee, in ClojureScript
Clojure
1
star
82

em_rugged

Asynchronous Rugged (libgit2 bindings for Ruby) for EventMachine.
Ruby
1
star
83

compojure-api-tools-deps

Example of metosin/compojure-api not working with tools.deps
Clojure
1
star
84

replicant-sweeper

Minesweeper in Replicant
Clojure
1
star
85

recall-position.el

Remember position and window scroll in an Emacs buffer and restore it with a key-binding
Emacs Lisp
1
star
86

minesweeper-ui

JavaScript
1
star
87

uinit

An initialization system for your UI modules. Declare dependencies between features, scalars, data and elements and have you modules loaded as soon as possible.
JavaScript
1
star
88

ndc-2021

The code from my ClojureScript talk at NDC 2021: https://www.youtube.com/watch?v=yFVk3D76wQw
Clojure
1
star
89

lookup

Find content of interest in hiccup data
Clojure
1
star