Experimenting bundling JavaScript projects with shadow-cljs


#1

I my example, I trying to bundle a very simple React page with shadow-cljs, in both dev mode and release mode:

based on new shadow-cljs of importing JavaScript files from a local directory:

https://code.thheller.com/blog/shadow-cljs/2017/11/10/js-dependencies-in-practice.html

{:resolve {"my-thing" {:target :file
                       :file "path/to/file.js"}}}

which is cool, because shadow-cljs uses Google Closure Compiler to optimize JavaScript.

This is only an experiment though. I asked thheller about that before, shadow-cljs was not designed for JavaScript. So I’m only trying to prove that js dependencies work well. When there are errors or file changes in js, shadow-cljs does not prettify it anyway.

Interesting though these days there are still new bundlers coming out in JavaScript community like http://parceljs.org and https://github.com/developit/microbundle .


#2

Confusing to me that the main.js file is quite large(88k) even though react and react-dom has been moved to lib.js(104k).

(ns main
  (:require ["app" :as app]))

(defn main! []
  (app/main))

(defn reload! []
  (app/reload))

(set! js/window.onload main!)

I thought everything would be optimized away since not code is really used in this file. But it has the size of 88k then something from ClojureScript should be there.


#3

main.js is basically just cljs.core which doesn’t get fully eliminated. As I said before I have zero interest in making shadow-cljs something you would use to build pure JS projects so don’t expect it to be optimized for that.

However I do want to fully support “hybrids” and make use of some JS while the bulk of the app is written in CLJS.

You can greatly simplify your project if you move everything to the src path and include it via relative paths. No need for the custom :resolve then.

(ns main
  (:require ["./app" :as app]))

Relative paths are not supported by CLJS itself and I’m still working out some details on this myself so consider this feature very alpha. I do want easier interop between CLJS<->JS though and this is one important thing left to finish. It is almost ready but the GCC is getting in the way sometimes.


#4

Updated. so many hidden features in shadow-cljs…


#5

I was looking at this as well and thought the same but at the same time the CLJS code seems so basic that I don’t see why most of core would not get eliminated? It’s definitely possible to build 5kb .js files from ClojureScript.

@jiyinyiyong I wonder what how big main.js is if you change main.cljs to this:

(ns main
  (:require ["app" :as app]))

(defn main! []
  (js/console.log "starting"))

(defn reload! []
  (js/console.log "reloading"))

(set! js/window.onload main!)

Maybe also try with and without the require. Would be weird if that affects dead code elimination though, wouldn’t it?


#6

Setting :build-options {:print-fn :none} (ie. don’t call built-in enable-console-print!) and removing all prn might make things smaller.

If you really want you can build without any CLJS code (JS only) which means cljs.core won’t even be included so it doesn’t have to be removed in the first place.

Either way there really is very little to gain trying to optimize this. Any real CLJS app will use a lot from cljs.core so it will stay alive regardless.


#7

The results are the same no matter what I do, add :print-fn :none, writing js/console.log, it’s all:

=>> du -ah dist
4.0K	dist/index.html
104K	dist/lib.js
 88K	dist/main.js
4.0K	dist/manifest.edn
200K	dist

#8

I noticed that in the js file I can’t import React as I did in Webpack:

import React from "react"

leads to React.Component error, which compiles to x$index.default.Component, while x$index.default is undefined. But instead x$index.Component is the code I wanted it to get.

As a result I have to change that to:

import {Component} from "react"

#9

Yeah this situation is a bit fuzzy right now. "react" is not distributed as ES6 and CommonJS<->ES6 interop is still being worked on (not usable from CLJS yet).

I might go back to using babel for all ES6 files since I’m currently using Closure for ES6 inside the project itself but babel for everything from node_modules. I wanted to test Closure for project files since they should be :advanced compatible (in theory). babel works a bit better for the moment.

I can’t find the link but the “official” spec for ES6/CJS interop for node is probably how this will work in the future.


#10