Skip to content

TodoMVC built with Independently Connected Components pattern with clojurescript + re-frame

License

Notifications You must be signed in to change notification settings

siriniok/todomvc-icc-cljs

Repository files navigation

clojurescript + re-frame TodoMVC example (with ICC)

This is an example demonstrating Independently Connected Components pattern with clojurescript + re-frame.

Based on the original Reframe TodoMVC and React + Redux + Reselect TodoMVC Example (with ICC) example.

The example was extended to handle several lists (to show the additional comlexity). Have a look at exercises below.

There is also a slide deck from Alexey Migutsky's talk on ICC at FDConf 2017 (the slides are 2D, you can go down in some sections).

Things to notice

  1. Passing IDs through components (main-section) rather than whole entities
  2. Binding components to specific handlers (toggle-todo-checkbox, delete-todo-button)
  3. Specializing components and reusing a template (todo-input-new and todo-input-edit)
  4. Domains (todo, list and filter) and linking domains (list-todo, filter-list-todo)
  5. Render props (todo-item)
  6. Dependent events using add-post-event-callback and :dispatch effect.

Dependent events are tricky in re-frame as they are async, so you need to prepare for intermediate states on the view layer. A potential source of errors. Maybe there is a better way to do this, like reversing the events chain, but I would like to postpone such design desicions.

What are the benefits?

This approach provides a clean structure for components and store.
Changing the app and moving things around are much easier than with other approaches.

If you want to feel the benefits, you can fork this repo and make these exercise changes to the app:

  1. Rename the list to "Home".
  2. Add a second list called "Work". It should work independently from "Home" list.
  3. Make task counts work per list
  4. Add a third todo list called "All" containing all tasks from both "Home" and "Work" lists.
  5. When todo item is in "All" list, add a label to every todo item showing which list ("Home" or "Work") it belongs to.
  6. Extract filters (All, Active, Completed) into components and remove them from "Home" and "Work" list.
  7. Move a chevron (complete all tasks) to the footer of "All" list. Move completed todos counter to the header of "All" list.
  8. Make "Work"/"Home" and "All" lists opaque when any todo is edited in "Home"/"Work" list (you probably need a new domain?).
  9. Add drag'n'drop between "Work" and "Home" lists.
  10. Add drag'n'drop sorting inside "Work" and "Home" lists.
  11. Measure the performance. Try passing primitives instead of todo model in props
  12. Make it possible to have 10000 items in any list
    • generate test data on app start
    • use virtualized list

The exercises are listed by increasing complexity

Getting Started

Project Overview

Directory structure

Editor/IDE

Use your preferred editor or IDE that supports Clojure/ClojureScript development. See Clojure tools for some popular options.

Environment Setup

  1. Install JDK 8 or later (Java Development Kit)
  2. Install Leiningen (Clojure/ClojureScript project task & dependency management)
  3. Install Node.js (JavaScript runtime environment) which should include NPM or if your Node.js installation does not include NPM also install it.
  4. Install karma-cli (test runner):
    npm install -g karma-cli
  5. Install Chrome or Chromium version 59 or later (headless test environment)
    • For Chromium, set the CHROME_BIN environment variable in your shell to the command that launches Chromium. For example, in Ubuntu, add the following line to your .bashrc:
      export CHROME_BIN=chromium-browser
  6. Install clj-kondo (linter)
  7. Clone this repo and open a terminal in the todomvc-icc-cljs project root directory
  8. (Optional) Download project dependencies:
    lein deps
  9. (Optional) Setup lint cache:
    clj-kondo --lint "$(lein classpath)"
  10. Setup linting in your editor

Browser Setup

Browser caching should be disabled when developer tools are open to prevent interference with shadow-cljs hot reloading.

Custom formatters must be enabled in the browser before CLJS DevTools can display ClojureScript data in the console in a more readable way.

Chrome/Chromium

  1. Open DevTools (Linux/Windows: F12 or Ctrl-Shift-I; macOS: ⌘-Option-I)
  2. Open DevTools Settings (Linux/Windows: ? or F1; macOS: ? or Fn+F1)
  3. Select Preferences in the navigation menu on the left, if it is not already selected
  4. Under the Network heading, enable the Disable cache (while DevTools is open) option
  5. Under the Console heading, enable the Enable custom formatters option

Firefox

  1. Open Developer Tools (Linux/Windows: F12 or Ctrl-Shift-I; macOS: ⌘-Option-I)
  2. Open Developer Tools Settings (Linux/macOS/Windows: F1)
  3. Under the Advanced settings heading, enable the Disable HTTP Cache (when toolbox is open) option

Unfortunately, Firefox does not yet support custom formatters in their devtools. For updates, follow the enhancement request in their bug tracker: 1262914 - Add support for Custom Formatters in devtools.

Development

Running the App

Start a temporary local web server, build the app with the dev profile, and serve the app, browser test runner and karma test runner with hot reload:

lein watch

Please be patient; it may take over 20 seconds to see any output, and over 40 seconds to complete.

When [:app] Build completed appears in the output, browse to http://localhost:8280/.

shadow-cljs will automatically push ClojureScript code changes to your browser on save. To prevent a few common issues, see Hot Reload in ClojureScript: Things to avoid.

Opening the app in your browser starts a ClojureScript browser REPL, to which you may now connect.

Connecting to the browser REPL from Emacs with CIDER

Connect to the browser REPL:

M-x cider-jack-in-cljs

See Shadow CLJS User's Guide: Emacs/CIDER for more information. Note that the mentioned .dir-locals.el file has already been created for you.

Connecting to the browser REPL from other editors

See Shadow CLJS User's Guide: Editor Integration. Note that lein watch runs shadow-cljs watch for you, and that this project's running build ids is app, browser-test, karma-test, or the keywords :app, :browser-test, :karma-test in a Clojure context.

Alternatively, search the web for info on connecting to a shadow-cljs ClojureScript browser REPL from your editor and configuration.

For example, in Vim / Neovim with fireplace.vim

  1. Open a .cljs file in the project to activate fireplace.vim
  2. In normal mode, execute the Piggieback command with this project's running build id, :app:
    :Piggieback :app

Connecting to the browser REPL from a terminal

  1. Connect to the shadow-cljs nREPL:

    lein repl :connect localhost:8777

    The REPL prompt, shadow.user=>, indicates that is a Clojure REPL, not ClojureScript.

  2. In the REPL, switch the session to this project's running build id, :app:

    (shadow.cljs.devtools.api/nrepl-select :app)

    The REPL prompt changes to cljs.user=>, indicating that this is now a ClojureScript REPL.

  3. See user.cljs for symbols that are immediately accessible in the REPL without needing to require.

Running Tests

Build the app with the prod profile, start a temporary local web server, launch headless Chrome/Chromium, run tests, and stop the web server:

lein ci

Please be patient; it may take over 15 seconds to see any output, and over 25 seconds to complete.

Or, for auto-reload:

lein watch

Then in another terminal:

karma start

Compiling CSS with lein-garden

Use Clojure and Garden to edit styles in .clj files located in the src/clj/todomvc_icc_cljs/ directory. CSS files are compiled automatically on dev or prod build.

Manually compile CSS files:

lein garden once

The resources/public/css/ directory is created, containing the compiled CSS files.

Compiling CSS with Garden on change

Enable automatic compiling of CSS files when source .clj files are changed:

lein garden auto

Running shadow-cljs Actions

See a list of shadow-cljs CLI actions:

lein run -m shadow.cljs.devtools.cli --help

Please be patient; it may take over 10 seconds to see any output. Also note that some actions shown may not actually be supported, outputting "Unknown action." when run.

Run a shadow-cljs action on this project's build id (without the colon, just app):

lein run -m shadow.cljs.devtools.cli <action> app

Debug Logging

The debug? variable in config.cljs defaults to true in dev builds, and false in prod builds.

Use debug? for logging or other tasks that should run only on dev builds:

(ns todomvc-icc-cljs.example
  (:require [todomvc-icc-cljs.config :as config])

(when config/debug?
  (println "This message will appear in the browser console only on dev builds."))

Production

Build the app with the prod profile:

lein release

Please be patient; it may take over 15 seconds to see any output, and over 30 seconds to complete.

The resources/public/js/compiled directory is created, containing the compiled app.js and manifest.edn files.

The resources/public directory contains the complete, production web front end of your app.

Always inspect the resources/public/js/compiled directory prior to deploying the app. Running any lein alias in this project after lein watch will, at the very least, run lein clean, which deletes this generated directory. Further, running lein watch will generate many, much larger development versions of the files in this directory.