NOTE: This gist uses the master branch of ClojureScript. Clone ClojureScript and from the checkout run
./script/bootstrap
and./script/uberjar
. This will producetarget/cljs.jar
which you can use to follow this guide.
As client applications become larger it becomes desirable to load only the code actually
required to run a particular logical screen. Previously ClojureScript :modules
compiler option
permitted such code splitting, but this feature only worked under :advanced
compilation
and users would still have to manage loading these splits. :modules
also required manual
explicit placement of many entries to produce optimal splits otherwise dependencies
would get moved to :cljs-base
.
All of these issues are now addressed directly in ClojureScript. This guide will walk you through code splitting a simple project and demonstrate these new enhancements.
Create a project folder:
mkdir -p hello-modules
cd hello-modules
mkdir src
Copy the cljs.jar
into this directory.
Create a REPL script:
touch repl.clj
Edit this script to look like the following:
(require '[cljs.repl :as r])
(require '[cljs.build.api :as b])
(require '[cljs.repl.browser :as rb])
(def opts
{:watch "src"
:output-dir "out"
:asset-path "/out"
:optimizations :none
:modules {:foo {:entries '#{foo.core}
:output-to "out/foo.js"}
:bar {:entries '#{bar.core}
:output-to "out/bar.js"}}
:browser-repl true
:verbose true})
(b/build "src" opts)
(r/repl* (rb/repl-env) opts)
Now make an index.html
file:
<html>
<body>
<button id="button">Load Bar!</button>
<script src="out/cljs_base.js" type="text/javascript"></script>
<script src="out/foo.js" type="text/javascript"></script>
</body>
</html>
Create the foo.core
namespace:
mkdir -p src/foo
touch src/foo/core.cljs
Edit this file to look like the following:
(ns foo.core
(:require [goog.dom :as gdom]
[goog.events :as events]
[cljs.loader :as loader])
(:import [goog.events EventType]))
(enable-console-print!)
(println "I'm foo!")
(events/listen (gdom/getElement "button") EventType.CLICK
(fn [e]
(loader/load :bar
(fn []
((resolve 'bar.core/woz))))))
Notice the unfamiliar namespace cljs.loader
. This namespace provides a Google
Closure ModuleManager singleton to manage the loading of code splits. This manager
will be initialized with whatever module graph you have defined in :modules
.
When the user clicks the button we load the :bar
module and invoke a function
that exists in the bar.core
namespace. Notice that we use resolve
. This is because
we cannot directly call something we never required. If we tried to do this
without resolve
the ClojureScript compiler would emit an undeclared var warning
during compilation.
Create the bar.core
namespace:
mkdir -p src/bar
touch src/bar/core.clj
(ns bar.core
(:require [cljs.loader :as loader]))
(enable-console-print!)
(println "I'm bar!")
(defn woz []
(println "WOZ!"))
Run the REPL script:
rlwrap java -cp cljs.jar:src clojure.main repl.clj
You will be prompted to connect via browser. Navigate to http://localhost:9000/index.html.
Click the button. You will see that the :bar
module gets loaded and the function in the other namespace gets invoked.
Make a new script release.clj
:
touch release.clj
Make it look like the following:
(require '[cljs.build.api :as b])
(b/build "src"
{:output-dir "out"
:asset-path "/out"
:optimizations :advanced
:verbose true
:modules {:foo {:entries '#{foo.core}
:output-to "out/foo.js"}
:bar {:entries '#{bar.core}
:output-to "out/bar.js"}}})
(System/exit 0)
Build your project:
java -cp cljs.jar:src clojure.main release.clj
Start a browser REPL just for serving content from current directory:
java -jar cljs.jar -m cljs.repl.browser
Navigate to http://localhost:9000/index.html. Your application should function correctly even though advanced compiled.
Change the foo.core
to take a new require like cljs.reader
. Rebuild.
You should see that cljs.reader
gets moved into the :foo
module but not :bar
.
If you examine the split files in out
you will see that foo.js
is larger than bar.js
.
Hi,
What if the bar module needs access to some shared data from the foo module?
Thanks