Shadow-cljs Example: Replacing React with Preact for Smaller Bundle Size
A while back I came across Preact, which advertises itself as "Fast 3kB alternative to React with the same modern API". I thought I'd try it out with ClojureScript, shadow-cljs and reagent.
Let's start out with shadow-cljs browser quickstart template. After cloning the repo, we need to install preact:
$ npm install preact
In shadow-cljs.edn inside our build we need to add
:js-options
{:resolve
{"react" {:target :npm :require "preact/compat"}
"react-dom" {:target :npm :require "preact/compat"}}}
Adding also reagent as a dependency, the file should look like this:
{:source-paths
["src/dev"
"src/main"
"src/test"]
:dependencies
[[reagent "1.2.0"]]
:dev-http
{8020 "public"}
:builds
{:app
{:target :browser
:output-dir "public/js"
:asset-path "/js"
:js-options
{:resolve
{"react" {:target :npm :require "preact/compat"}
"react-dom" {:target :npm :require "preact/compat"}}}
:modules
{:main ; becomes public/js/main.js
{:init-fn starter.browser/init}}}}}
Then we can just npx shadow-cljs watch app
and open it in browser. Checking the console we can see the messages from js/console.log
statements as defined in browser.cljs
.
Let's add some minimal functionality to browser.cljs
to see that reagent still works:
(ns starter.browser
(:require [reagent.core :as r]
[reagent.dom :as rdom]))
(defn component-main []
(let [state (r/atom {})]
(fn []
[:div
[:input
{:type "text"
:on-change (fn [e]
(reset! state (-> e .-target .-value)))}]
[:div "Reversed: "
(reverse @state)]])))
;; start is called by init and after code reloading finishes
(defn ^:dev/after-load start []
(js/console.log "start")
(rdom/render [component-main]
(js/document.getElementById "app")))
(defn init []
;; init is called ONCE when the page loads
;; this is called in the index.html and must be exported
;; so it is available even in :advanced release builds
(js/console.log "init")
(start))
;; this is called before any code is reloaded
(defn ^:dev/before-load stop []
(js/console.log "stop"))
You can view the full thing on github.
The resulting js-file (npx shadow-cljs release app
) is roughly 182 KB. I tried the same using react and react-dom (version "17.0.2" for both) and the resulting file was 274 KB.
Note: if you're planning on migrating your current project from React to Preact make sure you go through differences to React and test properly to ensure nothing breaks.
Edit: Chris McCormick already had a post about this: Replacing React with Preact in ClojureScript. I highly recommend his writings.