What has been done besides modules?
- RAM bundle, allows to avoid loading of all js dependencies at once (from
node_modules
dir) - advanced cljs compilation. Reduced size of index.platfom.js file (from ~11MB to 3.4MB), removed some unused code, saved 25-30% time of js startup.
- Unnecessary resources were removed from js code (translations, svg images, different string resources added by
slurp
) com.taoensso/timbre
heavy but unnecessary deps were removed
All changes above do not guarantee that index bundle file will not grow. When we add new cljs code the index becomes bigger thus startup becomes slower. Only modules can help with new cljs code.
Modules
Rationale
- Modules allow us to load different parts of the application separately from each other, that’s a way to speed up app’s startup. Apparently code instructions are the heaviest part of js bundle for JavaScriptCore, thus moving part of js from index file is more beneficial than moving out some even “heavier” string/binary parts from it.
- Modules help to put some discipline into the codebase by drawing some “physical” boundaries between different parts of the application and making them communicate through API
How it works
- A namespace or a group of namespaces is compiled into a separate js file and is loaded to the application on demand
- In order to define a module we have to pass its configuration to clojurescript compiler, for instance
:modules {:cljs-base {:output-to "index.android.js"}
:i18n {:entries #{"status_im.goog.i18n"}
:output-to "status-modules/cljs/i18n-raw.js"}}
- in the code it looks like this
Using(ns status-im.goog.i18n-module (:require-macros [status-im.modules :as modules])) (modules/defmodule i18n {:mk-fmt 'status-im.goog.i18n/mk-fmt :format-currency 'status-im.goog.i18n/format-currency}) (defn mk-fmt [locale format-fn] ((get-symbol :mk-fmt) locale format-fn)) (defn format-currency ([value currency-code] ((get-symbol :format-currency) value currency-code true)) ([value currency-code currency-symbol?] ((get-symbol :format-currency) value currency-code currency-symbol?)))
defmodule
macro we specify the module’s name (in this case “status-modules/cljs/i18n.js” will be loaded when needed) and specify modules symbols which should be accessed from outside.get-symbol
is defined by macro and allows to access modules symbols when necessary, on the first call it loads the whole module into the application. - the module can be force loaded by calling
module-ns/load-module
,load-module
is defined bydefmodule
macro. :cljs-base
from:modules
will contain all namespaces which are not specified in any of module’s:entries
. We use it as an index file for RN application.- as far as this is rather google closures config than clojurecript compiler’s config, all namespaces should be specified in a form which can be recognized by google closure, and this means that hyphens should be replaced via underscores in the set of namespaces, e.g.
"status-im.*"
=>"status_im.*"
. - even if some namespace is specified as a part of a module but is called directly from some namespace which is a part of
:cljs-base
(i.e. just required there) it will be compiled as a part of:cljs-base
- thus all calls to the specific module should be done through its interface. If the module’s sub-namespaces are accessed directly the whole idea falls apart.
- module’s
:output-to
should be specified in"status-modules/cljs/%module-name%-raw.js"
format.status-modules/cljs
contains all compiled module,-raw.js
points that module’s file should be prepared before becoming loadable. - Before being used in RN app
"module-raw.js"
file is transformed intomodule.js
. The whole content of"module-raw.js"
is put into a string so thatmodule.js
content looks as
By doing this we make it possible to evaluate the module’s code inside the scope of themodule.exports = `module-raw.js content`;
:cljs-base
and thus make module’s vars accessible for the rest of the app.
How it affects existing guidelines
-
Only the core and db namespace of a module can be required outside of the module directory. Other namespaces must never be required outside of the module directory
That definitely will not play well with modules, the module should have a single entry point which i propose to call
module
. All calls tocore
,db
or anything else from inside the module should be done throughmodule
. -
all subscriptions must be defined in the single status-im.subs namespace
This means that we define subs which might not be used by any part of application till some specific module is loaded and doesn’t make too much sense imo. Subs might be defined inside a module if only module’s views are using it, in this case, we do not load unnecessary code on startup. Also, having a single file separated by comments contradicts a bit with the whole modularity thing.
-
all events must be defined in the single status-im.events namespace which can be considered as an index of everything going on in the app
Same thing as with subs.
What is done already
[goog.i18n
is moved to the module ](Move goog.i18n deps to a module by rasom · Pull Request #8360 · status-im/status-mobile · GitHub)- network and extrensions modules
What should be done
Other parts of application like hardwallet
, wallet
, profile
, chat
can be moved into modules.