Currently libraries need to write some logic to load JS libraries from various places. For example Reagent supports:
- (Default) Cljsjs packages which export
js/React
,js/ReactDOM
,js/ReactDOMServer
andjs/createReactClass
- Npm packages, with
require
when using Node target
Because the default is Cljsjs, Reagent namespaces depend on cljsjs namespaces like cljsjs.react
. This means that to use Reagent
with option 2. requires creating empty stub namespaces that provides these.
Using Reagent with :npm-deps
is not possible, because... FIXME
(defn module []
(cond
(some? imported) imported
(exists? js/ReactDOM) (set! imported js/ReactDOM)
(exists? js/require) (or (set! imported (js/require "react-dom"))
(throw (js/Error. "require('react-dom') failed")))
:else
(throw (js/Error. "js/ReactDOM is missing"))))
Next version of Cljs will support string requires, https://dev.clojure.org/jira/browse/CLJS-2061.
These are currently only useful together with :npm-deps
.
String requires exists because some Node packages need to be referred using names
which can't be normally expressed, e.g. react-dom/server
, where /
is illegal character in symbols.
But string requires don't need to be tied to :npm-deps
feature.
String requires are only about making it possible to write these requires.
The dependency loading should work no matter how these modules are provided: :npm-deps
, or Cljsjs style :foreign-lib
(which provides browser-ready JS files)..
It will take for everyone to adapt :npm-deps
and this will be even slower when users
will only use this is libraries use it. But libraries will have hard time using this
if this breaks the existing configurations, which use e.g. Cljsjs.
:npm-deps
modules are converted to Closure modules and loaded as such.
These modules get names like module$absolute$file$path$react
.
Cljs compiler takes care of mapping requires of e.g. react
into this module.
Foreign-lib code can't be loaded as Closure modules. But is possible to create a simple wrapper Closure module:
goog.provide("module$react");
module$react = window.React;
Which creates valid Closure module. This module can be added to Cljs dependency index
and can be required like :npm-deps
(normally, or string requires when needed).
To select the window global object which provides the lib, new configuration param
can be added to foreign-lib map: :js-global "React"
.
Some minimal changes are required to allow strings in requires for foreign-libs.
After/before processing JS-modules using CJS/ES6, the foreign-libs containing :js-global
property are selected, and for each a wrapper Closure module is created.
- Foreign lib with
{:file "react-dom-server.js" :provides ["react-dom/server"] :requires ["react"] :js-global "ReactDOMServer"]}
module$react$dom$server.js
file is created based on foreign-lib mapreact-dom/server
->module$react$dom$server
is added to dependency index- The created Closure lib is loaded later, by
closure/js-dependencies
- At this point,
goog.requires/provide
from the file are read and used to select dependencies etc. module$react$dom$server
has to depend on the original foreign-lib so that is also added to build d dependencies by Cljs and loaded- Probably the original foreign-lib should be renamed, e.g.
module$react$dom$server$original
- At this point,
- This works for both cases where Npm module exports an object, or an function
- Npm modules always export single object or function, so
:js-global
should be enough for this use - What about ES6 modules?
- No need to support browser libraries that are not ever used through
:npm-deps
? These might export several objects.
- Npm modules always export single object or function, so
- This will work correctly with advanced builds also.
- Might be some minimal difference in output size?
- Would it be possible for Cljsjs/user to write these wrapper modules themselves?
- Perhaps, but quite hard. There needs to be mapping from e.g.
react-dom/server
-> what ever is the module name, so there would need to be documented way to name these files. - Closure modules can't currently requires Foreign-libs, which means that the real JS code would not be included
- Perhaps, but quite hard. There needs to be mapping from e.g.
- Working proof of concept: https://github.com/Deraen/clojurescript/tree/string-require-js-global
- Will need to be cleaned a lot
- Current
:npm-deps
/ string requires implementation should be reworked a bit: https://dev.clojure.org/jira/browse/CLJS-2206- This will decouple string requires from
:npm-deps
and will make it easier to use string requires with other module sources.
- This will decouple string requires from
- On Node, similar wrapper Closure module could be created to allow
libraries work both with Node and on browser with
:npm-deps
:module$react$dom$server = requires("react-dom/server");