@dnolen Hi, I would like to discuss (again I know) about the :default
require keyword. I followed the issue at:
https://clojure.atlassian.net/browse/CLJS-2376
And I understand why you are not inclined to support it in cljs compiler. In my situation, I'm working around the cljdoc project, and some of the analysed package contains the :default
keyword in their requirement.
In this case, the cljdoc-analyzer, which relies on the cljs parser facilities, just throws an exception during its execution.
My question is:
Is the support of :default
keyword in the cljs parser definitely rejected? Or is it still in the balance?
A strategy should be adopted in cljdoc from there to deal with :default
keyword. (ping @martinklepsch)
I typically advise people not to use :default
in libraries so maybe the lib authors should just use the alternative :rename {default foo}
instead of :default foo
?
Why not, it's possible. It's a way to deal with it. So the :default
keyword from shadow-cljs is deprecated?
no, it isn't. just makes libs shadow-cljs only which I lib authors should avoid (IMHO)
Ok, if you advice to avoid it, so if cljdoc doesn't support this keyword, it's pretty reasonable, no?
Yes, I think supporting :default would be outside the scope for cljdoc since the library would be only usable from shadow anyways
Ok, I have to fix my lib π
one alternative for the :default
thing just always do the rename bit for Node.js libs?
Could be done for ES6 libs too of course if we detect them.
The thing I wanted to avoid with CLJS-2379 is more new stuff in the ns form - but if we just automatically do this would avoid the need for that
similar to what we do for invokeable nses
what do you mean by automatic? I mean how would you know whether a react-native
package has a default
export or not?
right we would need to add that metadata, I thought our node_modules
processing looked at exports
or could be easily modified to do so
this doesn't seem very hard to do - i.e. Rollup
in anycase this is the only kind of solution I would find acceptable - nothing manual
happy to see someone take this one - I would say medium difficulty
not too many changes to ClojureScript once the parsing problem is solved
https://clojure.atlassian.net/browse/CLJS-2376 ticket updated, @juhoteperi I'm assuming you don't still want to be assigned to this one quite old
I'm planning on cutting a release today - haven't head any bad news and all the latest changes are just to support downstream tooling
1.10.738 being built now
hmmm, I'm seeing cljs.core/+, all arguments must be numbers, got [clj-nil number] instead
for (s/coll-of ::menu-item :kind sequential? :min-count 1)
@roman01la make something minimal and file a ticket, would be nice to know if that's a regression of some kind since we messed w/ this stuff recently
@roman01la yeah that was definitely one of the patches clj-nil
not considered a number type anymore
Am I doing something wrong here?
clj -Sdeps '{org.clojure/clojurescript {:git/url "<https://github.com/clojure/clojurescript.git>" :sha "b79007367818f0d1567646f28f09e2de3450a99e"}}' -m cljs.main -v -re node
Execution error (FileNotFoundException) at clojure.main/main (main.java:40).
Could not locate cljs/main__init.class, cljs/main.clj or cljs/main.cljc on classpath.
I think that was my patch
you're missing the :deps level
clj -Sdeps '{:deps {org.clojure/clojurescript ....
right clj -Sdeps '{:deps {org.clojure/clojurescript {:git/url "<https://github.com/clojure/clojurescript.git>" :sha "b79007367818f0d1567646f28f09e2de3450a99e"}}}' -m cljs.main -v -re node
ah I see, thanks
@roman01la I'm going to revert your patch
if you could reopen the ticket and add that would be helpful
sure
building 1.10.739
website updated with release post
@alexmiller I want to do a post after that is there an easy way to have a post come afterwards with AsciiDoc even though the date is the same?
not to my immediate knowledge
there's a date sort somewhere
2009/05/17 17:58:44
a format like that might work to set an explicit time for the postlet me try it
cool thanks
that seems to sort and appear correctly for me if I use 2020-04-24 23:59:59
but may just be coincidence, not totally sure
do you want me to push current site or wait?
wrapping up the others posts
one moment
just ping me
@alexmiller fire away
@alexmiller just added some missing updates - let me know when it's ready and I will post updates to various channels
bunch of issues around the js-iterable?
predicate, took a few tries to sort that out
1.10.741 should be appearing relatively soon
I updated the website to reflect the new version
stepping away for a little bit I believe everything should be ready to go - please try 1.10.741 when it appears would love to address any straggling issues if they come up today
@dnolen sorry, was heads down on other stuff, will push site
pushed
Thanks!!!
@alexmiller minor typo, another push whenever you can
Will do
@thheller I don't really understand your concerns about handling exports when stuff like this exists https://github.com/rollup/plugins?
given the years of prior art - this doesn't seem problematic to me, your comments so far seem to be ignoring this?
@alexmiller another bump on the site, typo in the webpack guide
button pushed
seems like you could even just do this with Rollup.js directly
another possibility
but as I expected JS already had to work around the chaos
@dnolen the point I have about default is that I want something that DIRECTLY corresponds to a JS feature that is here now and used in libraries and probably more to come in the future as more people move to ESM
it isn't even about how we process that, it is about being able to access it in a user friendly manner which IMHO :default foo
does better than :rename {default foo}
I understand you don't want to add stuff to the ns form and I'm fine with that ... don't do it then
but adding compiler magic or adding third party tools does not address this problem in any way whatsoever
the :rename
thing is not so import
it is just about " how do I refer to this thing that library exports" nothing more
how it is accomplished isn't meaningful
"3rd party" is irrelevant
Closure is 3rd party thing
how it is detected is an implementation detail
so when we discuss it that's what I'm interested in
not talking about irrelevant implementation details - but the end result
if you require a library that exports default it can be used as a namespace
w/o accessing a property
what? no it cannot?
how do you mirror import * as thing from "foo"
then if :as
is already "used for default"?
let's rewind a bit
before talking past each other
import Thing, * as everything from "foo";
I think you actual problem w/ the proposal is that in JS you have a choice
and choosing anything flies in the face of being able to choose in JS
I don't follow
let's stop talking about import
just about how we want to use X from CLJS
some thing exports default
in JS I don't have to talk about default
when I import
I just get a ns-y thing
that would be preferred in CLJS
I don't follow sorry. you mean you don't have to use the word default
?
you don't have to use default
when you import in JS
so why should you have to use it in CLJS?
yes, because it has dedicated syntax for it?
like literally import <defaultExport> from "foo"
?
import Thing, * as everything from "foo";
this is actual valid JS?
and does that fail if you don't export default?
and would be (:require ["foo" :as everything :default Thing])
I'm lost again? that import will fail if the library has no default export yes?
just trying to parse that expression
it's imports the default under one name, then all other exported symbols into a different bag called everything
all I'm looking for is being able to express all the variants of import
that the ESM spec has
yeah not necessarily interested in that
import Thing, {foo, bar} from "foo"
is also valid
because of impedance mismatch
what does that mean?
adding more stuff to the ns form
that isn't Clojure stuff
that's the hard design constraint
so host-interop doesn't matter? I mean we have :refer-macros
and other stuff clojure doesn't have?
sure but old days, we reconciled that with sugar long ago for .cljc
nobody wants to go backwards
Clojure has some prior art here w/ harder things to import, inner classes in packages
Foo$Bar
what about foo$default
and "foo$default"
?
give me a full example please. not really sure what you mean? as a separate require?
yes
I don't like it. Just makes things more complicated to explain IMHO
I made a translation table here https://shadow-cljs.github.io/docs/UsersGuide.html#_using_npm_packages
without more elaboration that's not much of an argument
see ES6 Import to CLJS Require
so that people can look at a JS example of how to use a certain library
and have a direct translation of how you do that in CLJS
sure but that's not compelling argument
anyways the end result is something that would get the same result w/o futzing w/ the ns form
I cannot make a compelling argument if you have already decided that you are not going to add :default
I've consistently rejected adding another thing for a couple years π
not gonna change my mind now
then I don't know why we are even talking
but open to feedback about stuff that doesn't mess w/ the ns form
the other thing about foo$default
is that it's trivial to implement
also trivial to support downstream
so there's a compatible way that libs/apps can use w/o caring
I don't like it. I think it is needlessly confusing when we can express this nicely with actual syntax data and not magic strings
your feedback has been heard - now many times π
anyways let other people chime in
foo$default
and "foo$default"
semantically would let us get at the default export as a namespace-y thing
there's prior art in Clojure inner class import as well as CLS bootstrap foo$macros
I don't think I'd have too much problem adding the $default syntax to that table. I find it weird to use a namespace directly as a variable, but I know that's already there.
Being more of a clj than a cljs dev, I find the finer details of jvm interop something I encounter very seldom. Most things I need are wrapped in a (thin) clj wrapper. Reason Iβm mentioning this is that maybe this is the case with cljs as well, and if so, maybe it doesnβt matter too much if the syntax is a bit idiosyncratic?
@slipset I wouldn't say that's too true. There's a large number of npm packages, and they cover a broad scope. We use a large number of npm packages currently.
my personal feelings right now is that it would behoove CLJS to pave the path to using standard JS
ESM is not a moving target anymore, I think that laying some concrete on how syntactically we can translate between the JS and CLJS ecosystem helps a lot with onboarding and helping people feel productive
I think some of this discussion is brought on by the fact that tools like webpack have been very liberal in parsing ESM imports in the past
import React from "react"
does work sometimes but itβs actually not valid, it should be import * as React from "react"
but tools like webpack just let you get away with the former
I'd be careful with that statement. It is still not "final" on how CommonJS <> ESM is ultimately going to work
so import React from "react"
is actually correct as long as react
only ships CJS code
but since no browser even supports that option and likely never will its always going to stay a bundler thing
there was a twitter thread where they were really encouraging people to use the actual ESM ns form instead of the default form (that relies on CJS interop)
and that they would be moving the docs / create-react-app to reflect this
probably because they really want to ship ESM but canβt because the ecosystem is still so choatic
again .. thats a bundler thing and webpack keeps changing their mind on this. they want to release it with strict-mode and have been talking about breaking changes for a while now ... yet still no webpack v5
react doesn't ship ESM because they want to do stuff you can't do with ESM
like this conditional require of the minified bundle https://unpkg.com/react@16.13.1/index.js
you cannot do this with ESM .. you need import maps for that ... but that is yet another unfinished spec
I see. I misspoke
really I have no hope for this ever to work out
the only clear way out of this is to have everything as ESM ... but that is going to take ages ... so we live in chaos π
my point was that JS devs do have to make a choice, based on the standards, and that bundlers have been inconsistent in their approach to all of this.
but we should pave the path to using standard JS
my personal dream would be that I could copy + paste an ESM into the source path of my CLJS project and it would Just Work:tm:, and things that require bundling (commonjs, other garbage from npm that requires transpiling) I can use shadow-cljs/rollup/whatever
you can do that today. just can't also mix in some random npm commonjs code (eg. react)
right, but the importing thing weβre talking about above comes into play right?
if you have all ESM it does not no. the interop/mix is the issue. all ESM or all CJS is totally fine.
I thought yβall were talking about how to refer to default imports in an ESM
the actual point Iβm circling around is that I agree with adding a :default
import π
you can still use it regardless of :default
. It really is just syntax sugar for :refer (default) :rename {default foo}
. can't remember if the :refer
was actually necessary so may just be :rename {default foo}
Remind me why we need sugar?
because the :rename
trick brings in an object not a namespace thing
foo$default :as foo
would support foo/api
and you could use it in combination with [foo :as f]
I'm a little out of touch with esm, if you do import x from "y"
does x.z make sense for all things?
this about about using JS libs as namespaces
I'm assuming default could be a string.
there's another interesting idea here
Its usually a single object like a class or so. haven't seen a case where that be an actual "namespace"
which I floated around before which is that foo$default
might not need to be specific to ES6
another reason I don't really like :default
that much
only in the cases where CommonJS is actually imported that way .. so import React from "react"
and React.createElement
someGlobal$bar
would be possibility
i.e. some JS object on the page that's global
and you could get externs infer for that too
rather than naked js/...
so that's another to weigh, Foo$Bar
could be generic pattern rather hard coded to default
problem
@dnolen not sure what you are talking about
so you could do (:require [window$AbortContoller :as AbortContoller])
?
what about using :import
?
@lilactown yes all these standard apis could be required as namespaces
it would cleanup usage
@lilactown (:import ...)
is really just a hack for GCL
because the namespace can be an object which is really annoying
@lilactown I don't think Foo$Bar
pattern can help much with that
anyways I'm warming up to Foo$Bar
because it's more general, and default
falls out of it, instead of being designed for it
wanting to treat some property as a namespace is a generally useful thing
how will it work with imports that are strings? "@corp/my-lib$default"
seems gross to my eyes
would be exactly the same, and again not specific to default
you could get at anything in that lib as a namespace
introducing novelty to the delimiter here is not that interesting to me
Clojure already has a pattern we already use it
and I guess, IMO it would be better for external analysis to not have to parse strings / symbols
so we are not modifiying the ns because :default
is "new" but we are adding a whole bunch of magic for accessing nested properties?
"whole bunch of magic"
this isn't a very challenging change we already have everything to return whatever from a resolve
I mean adding new functionality to ns
. not talking about implementation at all, I know that is trivial.
it's really a change to resolve
barely a change to ns
yeah I think the semantics of a keyword vs a special string/symbol are not really different to me as a consumer, which I think is what thheller means
I think regular JS interop is fine for this don't know why the ns/resolve would need gain that functionality
don't even want to think about an npm package that has an actual $
in its name. I'm sure there exists one π
@lilactown I'm already talking about bigger problem now
the discussion was just think about the default problem
but now maybe a more general issue to solve
the default issue is the same as many other things you can't practically require
that requires going to globals or accessing objects
I am tracking your thoughts I think. I disagree with the ergonomics of using a special $
delimiter
@dnolen just so I understand what you are proposing. suppose there is a npm package that exports something nested like react-native :as rn
rn/AppRegistry.registerComponent
. you want (:require [react-native$AppRegistry :as reg])
reg/registerComponent
?
I don't hate the idea but this definitely falls into the magic category for me.
scratch that ... I don't like magic characters.
(ns <http://corp.app|corp.app>
;; add a new `:with` keyword that provides the behavior
;; to use a JS property as an ns, provide externs, etc.:
(:require ["@corp/my-lib" :as my-lib :with [default :as my-lib-default]]
[window :with [AbortController]]))
the difference between :with
and :refer
/ :rename
is that the symbol would be treated as a namespace, not a value
@lilactown noooooooooooooooo π
π
@thheller this is no longer about just node_modules
it does not matter what require you are using, it would work
there's nothing magical about it, it's just a custom resolve
it's almost identical to inner classes in Clojure
there something in a package that you want to refer to, but it's impractical
in this case there some property in JS which is effectively a namespace
i.e. a global, i.e. a property, i.e default, it doesn't not matter
it would be better to use that thing as a namespace from ClojureScript
so less interested in now in feedback about default
problem
more interested in feedback about a problem in literally all ClojureScript codebases right now
tons of libraries / apps right now accessing properties which are just namespaces, global which are just namespaces
I'm not asking about node_modules. I used that as an example of how the actual syntax is supposed to look
it would only work on the ns identifier itself nowhere else, and probably the additional constraint of only one level
I'm a browser guy, so I'm thinking of something like js/window.location.href, is that the kind of thing you'd like an alternative to?
[FooGlobal$AnalyticsThingy ...]
JS globals is also possible
[Math ...]
I won't cry for js/Math
or losing the special casing
an how do you tell that FooGlobal
is an actual global? I mean is every symbol now valid?
[FooGolbal$AnalyticsThingy ...]
? how is the user warned about that typo?
for known HTML stuff, Closure can tell us - foreign libs w/o :file is an open JIRA thing
the latter give you a way to say you want to use something from somewhere not in the build
1. This is sort of moving the goal posts, but I appreciate thinking more generally about this problem
2. I hate the $
as a special delimiter inside of a symbol or string. We have EDN at our disposal; we should be able to craft syntax that represents this
$
has prior art though...
exactly
prior art that is abused for some entirely different purpose
$
is just how the JVM handles inner class names so its more a JVM thing than it is a clojure thing IMHO
But it's how you refer to an inner class, which rhymes with what you want to do here
also $
is ugly
so less likely to clash w/ anything
but if people have way better ideas for the delimiter then tell me
no ideas about extending the ns form please
I don't like the idea simply because of the magic character. it does not matter which character it is.
Given that . Works as the delimiter already, why something new?
"." Is probably a good choice actually... Nobody will use that in js
.
is definitely valid and used in a few npm package names so would lead to ambiguities when trying to figure out which package name the user meant
my assumption was that it would make it difficult to resolve a.b.c
where b
or c
could be a namespace or property
Really!? Gah. I thought it might particularly not be valid, due to conflict with properties.
https://www.npmjs.com/package/object.assign and of course there is a object
package as well
I think itβs better to extend the ns form parsing than to add a delimiter. Β―\(γ)/Β―
How about the $ and a reader macro which allows a more familiar require string for node modules to get munged into an unambiguous form
@olivergeorge what do you mean, though reader macro is almost automatic no
Rough thought was something like this
(:require [#npm "some/mad.convention"])
or if needed going a step higher to allow for richer syntax
(:require #npm ["@corp/my-lib" :as my-lib :with [default :as my-lib-default]])
yeah no
fair enough π
π
The conflicting syntax & conventions does complicate finding a compatible approach without having "modes" of interpretation... that lead me to a munging wrapper.
I'm not that offended by $ becoming more common
> adding more stuff to the ns form that isn't Clojure stuff, that's the hard design constraint
I'm not sure if I understand this. trying to fit all the npm lib require stuff into :require
... why not something more dedicated like :npm-require
, less confusion overall for tooling as well maybe
this isn't just about npm
. IMHO a string is already clear enough in that the require refers to JS code and not a CLJS namespace
shadow-cljs had (:js-require ["foo" ...])
for a while but rolling that into :require
was way nicer
ok
are string require names obligated for npm libs btw? I've had one example in #clj-kondo where a user didn't use a string for an npm lib and this resulted in some warnings, because clj-kondo expects you do use a string for npm libs
in shadow-cljs it is not no. in CLJS you can actually do (:require ["clojure.string" :as str])
I believe which I would consider a bug π
clojure-lsp does not like the string requires at the moment
CLJS allows any symbol if there is a folder of that name in node_modules
. so (:require [react])
is valid if it indexed a node_modules/react
before.
(ns foo
(:require
["markdown-it" :as md]
["markdown-it-texmath" :as md-texmath]
[com.nextjournal.editor.markdown.todo-lists :as todo-lists]
[com.nextjournal.editor.markdown.gh-preamble :as gh-preamble]))
(def mdit (.. (md #js{:linkify true :breaks false :html true})
(use md-texmath)
(use gh-preamble/Plugin)
(use todo-lists/Plugin)))
This was the example, but without the string quotes in the requires. This confused clj-kondo because it doesn't expect the alias to be usable as an object, when it's not a JS libshadow-cljs also allows (:require ["./foo.js" :asfoo])
for directly accessing js files from the classpath, so not npm.
but thats shadow-cljs only and not supported in CLJS (and was rejected there too)
aha
@borkdude strings are just allowed in general, there's no interesting reason to be more restrictive, use to indicate anything is not a good idea
would it be reasonable to ask users to use strings to indicate js libs?
the real problem which only tangentially has to do w/ JS is that Clojure notion of symbols is related to what works for Java packages
so how can tooling "see" if an alias is a js object or just a CLJS alias?
strings widen what characters are allowed in an effective package name
I think so yes. I do recommend that. I hate that node_modules/anything
automatically becomes a valid symbol to require
but that ship has sailed and is not going to change
what shadow-cljs does here is just not up for discussing let's stop talking about this in this channel
nothing is going to change here
I accept that π But is there a way to distinguish between a JS obj or alias from a tooling perspective (without inspecting the thing at runtime)?
if the answer is no, then clj-kondo should treat all aliases as potential js objects
@borkdude I think what's happening there is the invokeable ns stuff since some JS libs do that, what's return is a function you have to invoke
@borkdude so I think you can't reasonably lint that, we don't do any kind of detection either really
thanks
(ns foo
(:require ["@foo/bar$default" :as foo.bar]))
(ns foo
(:require [global$console :as console]))
(ns foo
(:require [global$document :as doc]))
(ns foo
(:require [global$crypto :as crypto :refer [subtle]]))
(ns foo
(:require [global$CustomModule :refer [bar]]))
some typical cases I've observed in pretty much every ClojureScript project that I've worked on
the global$foo
would resolve to goog.global
and be validated against the existing externs (when available)
towards the end it's also quite common in a custom JS context (React Native obviously but anything, Ambly, raw JSCore) that you installed a native module at the global level
so there's a lot of global patterns here that can be lifted into the ns form
in truth that's what the global is, effectively a package, but we don't treat it as such
it should in fact probably just be goog.global
to avoid a little bit of special handling
goog.global$crypto
the default
stuff is just extra
goog.global$Math
etc. would provide a discplined way to handle the standard JS builtins without the current hardcoding in the analyzer
I suppose for looks in this case goog.global.Math
is fine
hrm what about :
?
(require ["@foo/bar:default"])
and (require foo.bar:default)
?
though symbol w/ :
is a perfectly valid ns name in Clojure, where $
is likely ugly enough to be uncommon
I like it slightly more than $
. it feels more aesthetic
problem is it's pretty valid
I would be super surprised to see either a :
or $
in a Clojure ns
$
less likely because it appears when munging
anything else I wouldn't place any bets
A string form with a space?
I wish we could use /
but thatβs even more likely to show up in js module import
yeah no way
ok gonna take break, please ruminate on this over the weekend if you're feeling invested π
read the whole thread and I have to say I like the whole idea personally so I give my π I even like the $
cause it is consistent with Clojure
thanks for the feedback!