Hey,
I'd like to propose a new feature for the ClojureScript Compiler, which requires a little bit of restructuring and possibly requires some new compiler options and might deprecate some current ones. Work started
here but I'll try to outline the intent.
Purpose:
Support splitting the
optimized CLJS javascript output into multiple "
modules" (separate .js files) instead of one big monolithic javascript file.
Motivation:
This is focused towards Browser Javascript (not node.js) where its generally desirable to keep file sizes small. In a "big" client side App its usually not required to serve every bit of javascript upfront since the user may never visit certain areas of the site thus not needing the scripts. Since Advanced Compilation requires everything at compile time its very unlikely you could split the output manually without the support of the Closure Compiler.
Feature Description:
Support a :modules vector in the compiler options, for example:
{:optimizations :advanced
:output-dir "target/cljs"
:module-dir "public/assets/js"
:modules [{:id :sub1 :include ["myapp.sub1"]}
{:id :admin :include ["myapp.admin"]}
{:id :sub2 :include ["myapp.sub2"
"myapp.something-else"]}
{:id :sub3 :include ["myapp.sub3"]}
]}
At cljs.closure/optimize time all sources are "moved" into modules by walking the :modules vector in reverse order and prefix matching the :provides statements. If a match is found the file is moved into that module, if no match can be found it will be moved into the "default" module. Also the dependency graph is analyzed and files that are only used by one module can also be moved there. (For example clojure.set is only used in myapp.admin.something so it can be moved into the :admin module). Modules can also depend on other Modules, which should also be analyzed and handled accordingly. (For example :sub3 may depend on :sub2 and :admin may depend on :sub1). All modules will always depend on the :default module which will probably always include cljs.core.
If no :modules are defined everything ends up in the :default module which basically then mirrors the current "one file" build. So its fully backwards compatible, although it would probably be advisable to change some of the compiler options since :output-to is targeted to one file only and has no meaning once a :modules is defined (since at that point at least 2 files will be generated). I'd also include a :module-dir option since its not the same as :output-dir. Output files should be named to <module-dir>/<module-name>.js
So in my example we'd end up with:
public/assets/js/default.js
public/assets/js/sub1.js
public/assets/js/admin.js
...
Overriding the :default name should be possible I guess.
Problems:
I have a "proof-of-concept"
commit which provides the splitting of modules but does not yet properly analyze the dependencies and requires that all modules name which modules they depend on, also only specified includes are moved.
Source Maps are most likely also broken. I have not yet looked into how they work, but the Closure Compiler only provides ONE source maps, regardless of how many JSModules are defined. So I don't know how that works exactly.
:output-wrapper is also targeted at one file only, I don't think this would work with modules.
Generally node.js targeted builds probably wouldn't understand closure modules. But since there is no need to split files in a node.js environment its probably safe to either disable them when {:target :nodejs} or warn accordingly.
Issues:
Herwig Hochleitner suggested using symbols instead of strings for :include, I opted for strings since the underlying data refers to goog.provide namespaces not clojure namespaces. But symbols would work too I guess.
Work to be done:
I'd be happy clean up my proof of concept commit and fix the issues (use the dependency graph). I could use some help on the source maps since I have not used them yet and don't know the internals.
If there are no objections to this I'd be happy to start work and open a proper Jira Issue.
Regards,
/thomas