On-demand (code splitting) in Clojurescript

Clojurescript is excellent at SPAs. However, sometimes SPAs have more parts than you want or need to include in a single download. My example is my persanal website. I want to start including a growing collection of examples and implementations of various things in Clojurescript. However, I don’t want my visitors to have to download the code for every single thing as soon as they hit any page.

In React we see solutions to this problem where things are included on-demand as the user routes to them. This seems more difficult in Clojurescript because of its hosted nature. Does anyone know of a clean solution?

Standard JS and React solutions:

https://single-spa.js.org/docs/separating-applications/

Have a look at shadow-cljs’ modules and code-splitting:
https://shadow-cljs.github.io/docs/UsersGuide.html#_modules

See

https://code.thheller.com/blog/shadow-cljs/2019/03/03/code-splitting-clojurescript.html

2 Likes

before I recognized Shadow as a cool replacement for Figwheel in pure CLJS projects. But after reading this and the work of code splitting in ClojureScript, it raises Shadow to a whole new level of accomplishment. Awesome work, thank you @thheller !

1 Like

Code splitting is already available to CLJS, see for example:

https://clojurescript.org/guides/code-splitting
https://clojurescript.org/news/2017-07-10-code-splitting

So what exactly does Shadow CLJS give you that you can’t do already with just the standard compiler?

There are many differences regarding code splitting to the regular CLJS implementation. shadow-cljs actually predates that implementation by quite a while and I’d argue has been used more extensively. In the beginning it was actually the reason I created the tool (then shadow-build) in the first place.

Regarding actual differences:

  • Basic code splitting for npm packages. Granted regular CLJS can’t import these at all but with :target :bundle you only get one JS package that contains all npm dependencies. In shadow-cljs each module/chunk will only contain the npm packages it actually uses. This can be a rather significant difference in larger projects, since npm packages can be moved to edges and lazy loaded.
  • Helper utils like shadow.lazy (which require compiler support) make things like the lazy-component described in my blogpost possible.
  • Enhanced compiler support so that things like (cljs.loader/set-loaded! :foo) are not required and added automatically
  • A couple error messages when your config leads to invalid results (such as empty modules)
  • There is no implicit base module. You have more control over your splits.
  • Overall easier config I’d argue
  • I consider Build reports essential for figuring out what your modules/chunks look like after optimizations
  • Built in support for cacheable output, which is otherwise rather difficult since you cannot change the filenames after

There might be other things I’m forgetting. You can get most of this done with the regular CLJS compiler but it’ll take a little more work.

4 Likes

Hi @thheller

I just read the documentation for cached output and subsequently the section on the build manifest (mapping module names to filenames). I’m wondering what the best practice is for actually loading the correct javascript filenames if they include hashes.

I’m imagining a piece of js/cljs that runs in the browser, that first loads the manifest, parses it, and translates they requested module name to a js resources, and then loads that. This seems like code that everybody would need, ie a good candidate for open source. Does this function exist already or am I expected to create it myself?

Is this even how you imagine a cljs dev would use the manifest? Is it possible to update the docs to provide a little bit of guidance on what the recommende approach would be to load the correct js names without having to manually patch hardcoded filenames as part of a release?

No, not at all. This is something you’d be doing on your server serving the initial HTML. The client side scripts already know about the hashes and will already load all the correct files. The CLJS side doesn’t need to do anything with the manifest ever. If it does it is doing something incorrectly.

The server however can just load the manifest and emit the proper script tag since it gets the hashed name out of that. So instead of <script src="/js/main.js"> it can generate <script src="/js/main.asdfakjlsdfa.js">. How that looks is entirely dependent on what your server looks like. It is maybe a couple lines of code for a regular CLJ server.

2 Likes

Good to know, and thanks a lot for the detailed description. I did not know about shadow-cljs predating code splitting in regular CLJS, and the Build reports feature you mention sounds very useful.

1 Like