Share your thoughts in the 2021 Clojure Community Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by

ES6 has special syntax for using "default" imports and there is currently no equivalent for CLJS when using imported ES6 code.

`
import * as name from "module-name";
(:require ["module-name" :as name])

import { export } from "module-name";
(:require ["module-name" :refer (export)])

import { export as alias } from "module-name";
(:require ["module-name" :refer (export) :rename {export alias}])

import defaultExport from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
;; no easy access to defaultExport
`

I'm proposing to add a {{:default}} to the {{ns :require}}.

(:require ["module-name" :as mod :default foo])

This makes it much more convenient to use rewritten ES6 code from CLJS. If "module-name" has a default export you currently have to write {{mod/default}} everywhere since they is no easy way to alias the default.

`
(:require ["material-ui/RaisedButton" :as RaisedButton])
;; :as is incorrect and the user must now use
RaisedButton/default

(:require ["material-ui/RaisedButton" :default RaisedButton])
;; would allow direct use of
RaisedButton
`

Internally the Closure compiler (and ES6 code rewritten by babel) will rewrite default exports to a {{.default}} property, so {{:default}} really is just a convenient way to access it.

The long version that already works is

(:require ["material-ui/RaisedButton" :refer (default) :rename {default RaisedButton}])

When ES6 becomes more widespread we should have convenient way to correctly refer to "default" exports.

(link: 1) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

10 Answers

0 votes
by

Comment made by: deraen

First take on the implementation. This implementation rewrites {{:default name}} to {{:refer [default] :rename {default name}}} at the lowest level ({{parse-require-spec}}), so I think other functions don't need to be changed.

I didn't update the require spec validation errors yet.

0 votes
by

Comment made by: dnolen

It seems to me the most desirable syntax for ES6 default imports is the following:

(:require ["material-ui/RaisedButton" :refer [RaisedButton]])

I don't see why we couldn't just make this work?

0 votes
by

Comment made by: thheller

I don't understand that suggestion. The default export has no name, the {{.default}} property is only used in interop by transpilers. In actual JS a live reference is used. How would you map {{RaisedButton}} back to {{default}}?

{{["material-ui/RaisedButton" :default foo]}} would be valid. The JS module may also actually have an export { RaisedButton } in addition to export default ..., which would lead to a conflict if you "guess" the wrong name. Default exports are not aliases for the module itself, that is separate.

I made a reference translation table (link: 1) and adding the {{:default}} alias is the only way to reliably map all ES6 import/export features. I don't see a way to make {{:refer}} work that would not be totally non-intuitive and unreliable.

import defaultExport, * as name from "module-name";

(link: 1) https://shadow-cljs.github.io/docs/UsersGuide.html#_using_npm_packages

0 votes
by

Comment made by: dnolen

Let's leave rhetoric out of this discussion please and focus on the technical bits. I just don't want to include another directive in the ns form and I would prefer an approach that avoids that. From my point of view there has not been enough exploration of that alternative.

This ticket is mostly about convenience, and I think we need to stew on this one for a bit longer before deciding on anything.

0 votes
by

Comment made by: jcr

Is there a reason we can't use metadata to deal with it? I.e.

(:require ["material-ui/RaisedButton" :refer [^:default RaisedButton]]) (:require ["module-name" :as mod :refer [^:default foo]) (:require ["module-name" :refer [^:default foo, bar, baz])

This doesn't require additional ns directives and doesn't cause any ambiguities either.

Since this wasn't used for macros (i.e. we have :require-macros instead of plain :require + ^:macros or :refer (link: ^:macro foo)), I assume there may be some nuances I am not aware of that prevent implementing it. Are there any?

0 votes
by
_Comment made by: thheller_

The {{^:default}} hint seems far more complicated to me from an implementation standpoint.

There is no equivalent for default import/export in Java/JVM and Clojure. There is also no equivalent in CommonJS even. It is strictly about ES6. Therefore bolting it onto "other" methods seems incorrect to me.

The reason it should be separate IMHO is that using the {{.default}} property is not strictly correct and the fact that it exists at all is purely for compatibility reasons with CommonJS. In strict ES6 they don't exist as part of the "Object" created by {{:as}}.


;; a.js
export let a = 1;
export let b = 2;
export default 3;

;; b.js
import x, * as y from "./a.js"

// these exist
y.a
y.b
// this is NOT valid in ES6 and does not exist
y.default
// instead x must be used
x


We certainly rely on the {{.default}} property existing for the time being given that CLJS does not emit ES6. {{webpack}} already changed how the default exports are handled however and I do expect the Closure Compiler to do something similar in the future.
0 votes
by

Comment made by: thheller

Turns out I'm actually wrong. Re-read the spec and {{.default}} is actually defined as a getter, so it is accessible. Nevermind my previous post then.

Doesn't change my opinion about the usefulness of {{:default}} as a simpler aliasing method though.

0 votes
by

Comment made by: pesterhazy

I ran into ES6 default exports, which are common these days. Folks stumble over default in the code, e.g.: https://github.com/pesterhazy/cljs-spa-example/issues/13. I wanted to report my findings.

As explained in the description of this issue, you can use a combination of :refer and :rename to get rid of the references to :default in the code, moving it into the NS declaration. It's not the most elegant solution but gets the job done and works with global exports: https://github.com/pesterhazy/cljs-spa-example/pull/14/files

Note that AIUI because default is a strict keyword in ES3, you need to set the following compiler option for this to work:

:language-out :ecmascript5

Otherwise default gets munged to default$

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-2376 (reported by thheller)
0 votes
by

The "sanctioned" fix for this is documented here: https://clojure.atlassian.net/browse/CLJS-3235

...