Fork me on GitHub
#shadow-cljs
<
2019-10-29
>
gdubs05:10:15

Hello! Newb here! I’m having a problem requiring javascript modules from cljs. I’ve created a super-simple reproduction (~4 lines). Googling my error comes up with nothing. Can anybody help me? I’m compiling with npx shadow-cljs compile npm.

David Pham06:10:43

@gdwarner you should have builds configuration in your shadow-CLJS.edn

David Pham06:10:42

If you target node, you can check this repo

David Pham06:10:09

In perticular this file

witek07:10:44

Hi. I have a problem with a macro, after I switched from figwheel to shadow-cljs. My macro (defmacro assert ...) lives in kunagi_base/utils.cljc. I have no :require-macros or anything like that in my code. With figwheel this worked fine. Shadow-cljs gives me this error: Use of undeclared Var kunagi-base.utils/assert. Any suggestions? Thank you!

thheller08:10:06

@gdwarner and what is your error? that snippet says nothing without more context

gdubs14:10:01

There’s a stacktrace in the bottom of the snippet: ReferenceError: shadow$provide is not defined

thheller14:10:16

oh oops. I missed that it was expandable

thheller14:10:36

ok that error is because :npm-module defaults to :runtime :browser but you are using it in node

thheller14:10:07

you need an actual build config like :builds {:npm {:target :npm-module :runtime :node :output-dir "node_modules/shadow-cljs"}}

thheller14:10:17

that would match what you have now but run in node

thheller08:10:13

#?(:cljs (:require-macros [kunagi-base.utils])) is likely missing in that ns

👍 4
🙏 4
witek08:10:20

When using release, I get Uncaught TypeError: Cannot read property 'g' of undefined. Wasn't there a way to create release output with hints to the real (not shortened) names?

thheller09:10:36

@witek shadow-cljs release app --pseudo-names will make the names slightly more recoginizable but you probably want to turn on https://shadow-cljs.github.io/docs/UsersGuide.html#infer-externs if you haven't already

👍 4
🙏 4
flightcubs09:10:12

Trying to use shadow-cljs in Cursive with the pom.xml method. Should Cursive be able to resolve references to node modules (or javascript references such as (.log js/console "...")), or is it expected that it doesn’t?

flightcubs09:10:46

Found previous questions regarding this, sorry about that 🙂 https://clojurians.slack.com/archives/C6N245JGG/p1572023469012800

flightcubs09:10:51

Does that apply for js in general (including the npm packages)?

thheller11:10:52

I'm not sure about the state of JS support in Cursive. it has never worked for me so I'm not sure if its suposed to work.

flightcubs11:10:33

Got it - thanks 🙂

orestis12:10:56

I’m still seeing problems when restarting the shadow-cljs build. The process is, I kill shadow-cljs to add a new dependency in deps.edn, then start it again. Then a bunch of errors appear, at their root:

TypeError: module$node_modules$react$index.createContext is not a function
    at eval (routes.cljs:10)
    at eval (routes.cljs:10)
    at eval (<anonymous>)
    at Object.goog.globalEval (main.js:827)
    at Object.env.evalLoad (main.js:2174)
    at main.js:2412

orestis12:10:40

If I delete all compiled output, the shadow-cljs cache, and node_modules, then starting the build again, everything compiles fine.

orestis12:10:01

I get a lot of similar errors too: TypeError: _react.default.createContext is not a function, TypeError: React.forwardRef is not a function, TypeError: module$node_modules$react$index.memo is not a function and similar.

thheller12:10:10

not much I can say about that. maybe you have some bad browser cache?

thheller12:10:35

what setup do you use? the shadow-cljs http servers or something custom?

orestis12:10:32

Custom http server, but with browser cache disabled.

thheller12:10:52

next time this happens. DO NOT DELETE ANYTHING!

orestis12:10:56

Is there any way I could trouble shoot this?

thheller12:10:01

impossible to tell what is happening if you wipe everything

thheller12:10:05

at least copy it somewhere

orestis12:10:38

Roger that. I’ll keep a copy of the .shadow-cljs, node_modules and the compiled output.

thheller12:10:36

also keep more errors ... excerpts of errors are pretty much pointless. it is likely one error cascading out and causing all the rest

thheller12:10:43

service workers can also be a big problem, any cache really.

orestis12:10:44

Is there something I should be looking for?

thheller12:10:53

I don't know

orestis12:10:21

It looks like that React itself has some issue. It could be some dependency but I can’t be sure at this point.

thheller12:10:39

it looks to me like react isn't loaded at all

thheller12:10:53

everything is undefined

orestis12:10:13

Yep, that seems to be the case. One thing that I see in the compiled output is that while I have a “main.js” file which is 11MB, there’s also a whole bunch of individual files under cljs-runtime.

thheller12:10:05

sounds normal

thheller12:10:35

are you sure the "old" shadow-cljs instance was actually dead?

thheller12:10:50

things will get weird if 2 shadow-cljs instances write to the same output-dir

gdubs14:10:13

@neo2551 I’m targeting node, but with the npm-module variant. The docs say that I can use the default shadow-cljs.edn via the ‘covenience mode’. https://shadow-cljs.github.io/docs/UsersGuide.html#_convenience_mode .

thheller14:10:47

yeah that should mention that is a convenience mode for webpack

☝️ 4
thheller14:10:00

it works if you stay in CLJS only

thheller14:10:07

but the JS support needs to know which runtime you are running in

gdubs14:10:43

trying… hopefully that is the missing piece

gdubs14:10:00

Same problem.

thheller15:10:49

@gdwarner try adding :entries [hi] to the build config?

thheller15:10:07

definitely a bug since it is supposed to work without, but maybe a quick fix

gdubs15:10:32

Still no worky with the config below. Regardless, I appreciate your help!

gdubs15:10:40

does it matter: hi vs “hi”. Or do I need to include “hello” since that’s what I’m trying to require?

thheller15:10:50

thats not in the build config

thheller15:10:59

should be in :builds :app

thheller15:10:33

symbol vs string matters yes. always a symbol for CLJS namespaces

thheller15:10:21

but you only need to list the "entries". meaning namespaces accessed from elsewhere (eg. require in node). it'll include its dependencies automatically

thheller15:10:30

but yeah its just a global missing

thheller15:10:20

any particular reason you are using :npm-module? :node-library and :node-script are much better generally

thheller15:10:09

quick fix might be global.shadow$provide = {} before running the require in node

gdubs15:10:43

I want to call my clojure lib/module from javascript. I don’t think I want node-script, but node-library may be appropriate. I have no intention of actually creating an npm module.

gdubs15:10:12

As I’m still getting the same error, even with entries, I’ll try node-library.

thheller15:10:35

try the quick fix I just mentioned

thheller15:10:47

so I know if it works without having to test myself 😛

thheller15:10:14

but yeah node-library has a better structure and better "output"

thheller15:10:38

has nothing to do with actually publishing to npm at all

gdubs15:10:16

ok, your suggested fix eliminated the stack trace I was seeing.

thheller15:10:59

if you don't mind please open an issue with the initial snippet you pasted

thheller15:10:10

will look into it later

gdubs15:10:20

sure thing

gdubs15:10:31

thank you very much!

cjsauer15:10:35

If one of my deps is described as “cljs support coming soon”, but contains (temporarily) incorrect .cljc files, is there a way to tell shadow to ignore that dep during cljs compilation? I don’t intend to use it from cljs, but do want it on my classpath at the clj REPL.

thheller15:10:39

@cjsauer not sure what you mean. what is the actual problem you see? just files on the classpath shouldn't hurt unless you use them?

cjsauer15:10:46

When I add this specific dependency to my project, it breaks cljs compilation by throwing a bunch of warnings about undefined vars in my test files. Removing the offending dep fixes things. I’m guessing that the bad .cljc files in that dep are causing cljs compilation to fail in some way.

thheller15:10:54

breaks how ...

thheller15:10:16

it doesn't matter whats on the classpath UNLESS you actually :require a file

thheller15:10:45

or do you add the dependency via deps.edn :local/root or gitlibs and use a test target?

cjsauer15:10:10

Yes, forgot to mention I’m using {:deps {:aliases [...]}}

thheller15:10:19

that isn't what I asked 😛

thheller15:10:41

:local/root or gitlibs was the important bit 😛

cjsauer15:10:56

Sorry picard-facepalm, no it’s a normal :mvn/version style dependency

cjsauer15:10:35

I don’t require this specific dep in the *-test.cljs files that the warnings point to… :thinking_face:

thheller15:10:52

what does it say exactly?

thheller15:10:12

I mean it should be telling you who required something if thats not found?

cjsauer15:10:50

It points at about 6 of my deftest macros with an undeclared var warning, for example:

(deftest entity-ids->lookup-set
Use of undeclared Var my-app.client-test/entity-ids->lookup-set

thheller15:10:38

I'm confused. how is that related to adding a dependency?

thheller15:10:17

seems to be warning about your namespaces?

cjsauer15:10:34

Yeah…not sure…removing the dependency also removes all of those warnings strangely enough.

thheller15:10:42

what dependency is that?

cjsauer15:10:49

This is datahike

thheller15:10:59

please full info

thheller15:10:05

with version

cjsauer15:10:45

Attempting to compile the .cljc files in that repo fails with all kinds of exceptions, because the cljs support isn’t there yet. My thought was those files were somehow tripping up my own compilation. But: >it doesn’t matter whats on the classpath UNLESS you actually :require a file

thheller15:10:13

no. unless a file is included somehow it is not compiled

thheller15:10:37

it doesn't matter what is in the file

thheller15:10:56

> Attempting to compile the .cljc

thheller15:10:13

so we want to figure out WHY it is attempting to compile

thheller15:10:25

BUT your issues seem related to something else

cjsauer15:10:39

Ah sorry, by that I meant cloning that repo and running their cljs tests doesn’t work (which is expected). It fails compilation.

thheller15:10:09

I'm lost ... what are you doing?

thheller15:10:18

you are cloning actual datahike and run the tests?

thheller15:10:23

please don't throw in unrelated info .. this is confusing enough as it is 😛

😅 4
cjsauer15:10:23

This was when I thought maybe datahike’s cljc files was to blame >BUT your issues seem related to something else This is likely the case now that I know shadow is likely not touching any datahike code

thheller15:10:57

please restate your problem. the actual warnings you see ... not your diagnosis.

thheller15:10:20

I'm completely lost and don't know what your problem is anymore

thheller15:10:42

a) you have working tests

thheller15:10:54

b) adding the datahike dependency breaks those working tests?

thheller15:10:15

c) NONE of your code actually has a datahike require? anywhere?

cjsauer15:10:37

Sure, let me restate

cjsauer16:10:10

a) Yeah, I have working tests using the :browser-test build target. I can open the resulting build up in the browser and see All tests passed. b) I add the datahike dependency to my deps.edn file, and then rerun my :browser-test build, which results in a bunch of warnings, but I just found that there is one interesting one:

Resource: datascript/core.cljc:586:36
 Use of undeclared Var cljs.reader/register-tag-parser!
c) Correct. None of my code has made any attempt to require or use datahike yet. So, there seems to be some conflict between datascript and datahike that I think is likely unrelated to shadow (apologies for the noise here). The key thing missing from my understanding of shadow was this: >it doesn’t matter whats on the classpath UNLESS you actually :require a file

thheller16:10:03

please run shadow-cljs clj-repl and ( "cljs/reader.cljs")

cjsauer16:10:09

#object[java.net.URL 0x72d5daf9 "jar:file:/Users/calvin/.m2/repository/org/clojure/clojurescript/1.10.520/clojurescript-1.10.520.jar!/cljs/reader.cljs"]
Seems to exist and load just fine. Likely a dep conflict like you mentioned. I’ll keep investigating. Thanks for your help 🙏

thheller16:10:47

did you potentially get a bunch of warnings on startup about "provide conflicts"?

cjsauer17:10:05

Oh! Yes I did. I hadn’t even noticed those.

thheller17:10:45

is that /private something you did?

thheller17:10:06

otherwise there are some compiled files on the classpath that shouldn't be

cjsauer17:10:34

Hm…not intentionally. I can’t seem to find that directory anywhere on my filesystem. Commenting out the datahike dependency causes all of these conflicts to disappear…this /private folder doesn’t exist in the datahike jar file either :thinking_face:

thheller17:10:44

you can check via ( "private/js/out/cljs/reader.js") which dependency include that

thheller17:10:49

I'll remove this particular behavior so that extra "bad" output doesn't cause trouble

👍 4
cjsauer17:10:44

Aha, it looks like replikativ’s hasch lib is the culprit for that specific conflict:

( "private/js/out/cljs/reader.js")

#object[java.net.URL 0x698e4e6c "jar:file:/Users/calvin/.m2/repository/io/replikativ/hasch/0.3.5/hasch-0.3.5.jar!/private/js/out/cljs/reader.js"]

thheller17:10:59

indeed. the jar is 2.1mb ... with a bunch of dev output

cjsauer17:10:08

I’ll keep an eye on that issue. Thanks again for your time, and for shadow.

thheller16:10:43

could also just be a dependency conflict

markx16:10:50

Any suggestion on how to move everything into a new directory, e.g. dist/ when run npm run build? I understand this might not be shadowcljs’ job, but wonder how people do it.

thheller16:10:22

what is npm run build?

markx16:10:02

It’s the npm-script. I usually config something like

"scripts": {
    "start": "shadow-cljs watch app",
    "build": "shadow-cljs release app"
  },

thheller16:10:52

so why do you want to copy it? why not just have shadow-cljs write the output dir you actually want?

thheller16:10:21

if you really need to copy you can just use "build": "shadow-cljs release app && cp dist somewhere" or so?

markx16:10:08

It’s just my preference to have everything built to a separate folder. If you dev and release in the same folder, say public/js/, then do you commit this folder in VCS, say git? I do want to commit the release version, but then I will always see it changing in git status when I dev.

markx16:10:37

something like shadow-cljs release app && cp dist somewhere would work, but then before I copy, I assume I need to clean up the dev files? But shadow-cljs doesn’t have a clean command, so…

markx16:10:12

Just wondering how others do it, if one ever does.

Filipe Silva16:10:41

"start": "shadow-cljs watch app",
    "build": "yarn clean && shadow-cljs release app",
    "clean": "rimraf public/js"

Filipe Silva16:10:50

for the same reasons really

Filipe Silva16:10:07

when deploying dist/, I don't want to include the non-prod js files that get produced there

markx16:10:25

Ah nice. So I can clean, build, and copy.

markx16:10:32

@filipematossilva do you have any plans to add something like react-scripts for create-cljs-app?😁

Filipe Silva17:10:51

so abstracting away all the tools in a separate package, right?

Filipe Silva17:10:16

I thought about it but I think that runs a bit contrary to the clj ethos... although this starter is directed to js folks

Filipe Silva17:10:42

I also don't feel that confident in my knowledge of the "best" way of doing things

thheller17:10:30

@filipematossilva would you mind changing the default generated config a bit. I'd rather it used the newer :dev-http config

thheller17:10:01

so instead of

{:builds
   {:app
      {:asset-path "/js",
       :devtools {:http-port 3000, :http-root "public"},
       :modules {:main {:init-fn app.core/main}},
       :output-dir "public/js",
       :target :browser},
    :e2e
      {:ns-regexp "e2e.*",
       :output-to "out/e2e.js",
       :target :node-test},
    :test
      {:ns-regexp "app.*-spec$",
       :output-to "out/test.js",
       :target :node-test}},
 :dependencies [[reagent "0.8.1"]],
 :nrepl {:port 3333},
 :source-paths ["src" "e2e"]}
you do
{:builds
 {:app
  {:asset-path "/js",
   :modules {:main {:init-fn app.core/main}},
   :output-dir "public/js",
   :target :browser},
  :e2e
  {:ns-regexp "e2e.*",
   :output-to "out/e2e.js",
   :target :node-test},
  :test
  {:ns-regexp "app.*-spec$",
   :output-to "out/test.js",
   :target :node-test}},
 :dependencies [[reagent "0.8.1"]],
 :nrepl {:port 3333},
 :dev-http {3000 "public"}
 :source-paths ["src" "e2e"]}

thheller17:10:51

also kinda not very clojure-ish with all the , and incorrect indentation 😛

thheller17:10:04

top-level :dev-http instead of nested :devtools {:http...} is the newer format

aisamu21:10:56

Good to know! And where would :http-resource-root and :preloads fit into this new pattern?

thheller21:10:50

:dev-http {3000 "classpath:public"}

thheller21:10:00

:preloads stay were they are since they are actually build related

thheller21:10:14

but you can also move them into individual :modules

aisamu21:10:39

Great, thanks!

Filipe Silva17:10:54

regarding the config changes, yes let me add it

Filipe Silva17:10:41

regarding the indentation, that is the default for zprint-clj https://github.com/kkinnear/zprint

Filipe Silva17:10:16

I couldn't find another formatter that had a good npm distro

Filipe Silva17:10:41

is there a canonical config for these formatters that matches what people use?

thheller17:10:22

so you can run any clojure code you want (eg. zprint)

thheller17:10:34

if a shadow-cljs server instance is running you won't even pay for startup time

Filipe Silva17:10:15

I had no idea, that sounds pretty handy!

thheller17:10:35

there are some clj -m zprint.main <deps.edn examples in the zprint readme

thheller17:10:00

shadow-cljs run zprint.main would be the same

thheller17:10:50

although I guess the <deps.edn would be a problem

thheller17:10:21

but that could be fixed

Filipe Silva18:10:26

so right now shadow-cljs run lets me run a given function in a clj namespace I have

Filipe Silva18:10:13

but to use it directly as shown in https://github.com/kkinnear/zprint#clojure-cli might not work currently, is that right?

Filipe Silva18:10:26

I trying just doing run zprint.main without any modification to shadow-cljs.edn besides adding the dependency, and it seems to print the formatted output

kamik@RED-X1C6 MINGW64 /d/work/create-cljs-app/template (master)
$ shadow-cljs run zprint.main <shadow-cljs.edn
shadow-cljs - config: D:\work\create-cljs-app\template\shadow-cljs.edn  cli version: 2.8.64  node: v10.16.0
{:builds
   {:app {:asset-path "/js",
          :modules {:main {:init-fn app.core/main}},
          :output-dir "public/js",
          :target :browser},
    :e2e {:ns-regexp "e2e.*", :output-to "out/e2e.js", :target :node-test},
    :test
      {:ns-regexp "app.*-spec$", :output-to "out/test.js", :target :node-test}},
 :dependencies [[reagent "0.8.1"] [zprint "0.5.1"]],
 :dev-http {3000 "public"},
 :nrepl {:port 3333},
 :source-paths ["src" "e2e"]}

Filipe Silva18:10:32

it's very slow though

Filipe Silva18:10:40

(btw the dev:http change is now published)

👍 4
thheller18:10:55

@filipematossilva have you read about server mode and how it functions? (regarding the "slow" comment)

Filipe Silva19:10:33

I'm familiar with it insofar as using shadow-cljs start in the background to speed up further commands.

Filipe Silva19:10:57

I didn't try it with this command though

thheller19:10:00

or shadow-cljs server yes

thheller19:10:24

but yeah the point is to get rid of the startup delay. so in general the server should always be running when you are developing

thheller19:10:30

making startup time a non issue

Filipe Silva19:10:13

Yes you are right, it is much faster now. Especially the second and subsequent runs.

Filipe Silva19:10:30

I need to think a bit about this usage pattern... It feels pretty powerful insofar as it provides direct access to all these clj tools. But I'd need to see if I could get a 1-to-1 correspondence as far as functionality goes. The npm zprint wrapper allows formatting all files and writing them back to the same place, and that sounds like it'd need a custom function to do. But a custom function is also a good example of how a user can customize it.

thheller19:10:11

I just wanted to mention it. just in case there is something available in clj but not on npm

thheller19:10:07

the zprint defaults seem to suck for code but there was a big discussions about formatters a while ago

thheller19:10:44

guess CLJ is too flexible syntax wise to find an "agreeable" standard

Filipe Silva19:10:17

I remember seeing a discussion on clojureverse I think

Filipe Silva19:10:20

it looked stalled

thheller19:10:56

yeah its a bit easier in JS and especially JSON configs since they are least maintain insertion order

Filipe Silva19:10:19

I had an unrelated question for you

Filipe Silva19:10:00

so something like

// node_modules/lib/foo.js
shadow$provide["module$node_modules$lib$foo"] = function(module, global, process, exports, require) {
    var foo = 1;
    exports.hello = function() { return foo; };
}

// node_modules/lib/bar.js
shadow$provide["module$node_modules$lib$bar"] = function(module, global, process, exports, require) {
    var foo = require("module$node_modules$lib$foo");
    foo.hello();
}

thheller19:10:20

yes, mostly the same

Filipe Silva19:10:04

in regards to module usage, I assume these wrappers can never avail from code motion, right?

Filipe Silva19:10:13

GCC has a nice feature called code motion, where it can "split" the content of a given JS file if part of it is used in one module but not the others

Filipe Silva19:10:50

so for instance

// node_modules/lib/foo.js
shadow$provide["module$node_modules$lib$foo"] = function(module, global, process, exports, require) {
    exports.hello = function() { return 1; };
    exports.world = function() { return 2; };
}

thheller19:10:53

no, the npm content is never "split"

thheller19:10:02

but it is moved into the proper modules when it can

thheller19:10:28

ie. if module B uses something the others don't they will be in B

Filipe Silva19:10:36

is there a way to exclude certain modules from the wrapper?

thheller19:10:00

don't understand the question

thheller19:10:31

you can't not wrap the code

Filipe Silva19:10:16

I was trying to imagine how would code motion be used for npm libraries, assuming the library was in a GCC friendly format

thheller19:10:42

if everything was strict ES6 then we would just enable GCC for everything

thheller19:10:46

with all its glory

thheller19:10:20

until then we run in "compatibility" mode

thheller19:10:12

you can't select one library to go with the full mode while others don't

thheller19:10:21

thats leads to the externs mess we already have to deal with

thheller19:10:55

I experimented with all of this a very long time. basically it never works. there are barely and "strict" ES6+ modules on npm (that also only depends on other strict ES6+ code)

Filipe Silva19:10:10

I don't mean for the general case though

thheller19:10:49

do you have an actual example?

Filipe Silva19:10:01

yeah, now that I think about it I do

Filipe Silva19:10:57

so for instance I could try to import @angular/core. It is published in ES2015 among other formats. It is also annotated for GCC via tsickle (a TS wrapper to annotate TS output for GCC).

Filipe Silva19:10:13

nevermind that there's no way in hell angular will properly work in cljs

Filipe Silva19:10:22

but that library is full ES2015

Filipe Silva19:10:45

then separately I also had momentjs, which is in crazy format

Filipe Silva19:10:56

I import them both from my app

Filipe Silva19:10:03

but neither has an interdependency

Filipe Silva19:10:27

I would like to tell shadow-cljs "try to import angular/core in full GCC mode, but keep momentjs in compat"

thheller19:10:43

that doesn't work

thheller19:10:59

you can "fake" it by copying node_modules/@angular/core/esm5/* to src/main/angular or so

thheller19:10:42

then import it via (:require ["/angular/index.js" :as angular])

thheller19:10:59

classpath JS will follow be processed by the GCC and advanced

thheller19:10:15

but that means you can't use any libraries that want to use angular

Filipe Silva19:10:31

yes, that limitation I did expect

thheller19:10:50

and you'll need a whole bunch of externs for all the code it consumes

thheller19:10:00

since there is no externs inference for JS code

Filipe Silva19:10:08

so back to the old externs way

thheller19:10:19

well you can always turn of the renaming

Filipe Silva19:10:41

yeah but that kinda defeats the purpose of trying to optimize these further

thheller19:10:20

var PlatformRef = /** @class */ (function () {

thheller19:10:27

why does it not use class? 😛

Filipe Silva19:10:37

because that's es5 code in ESM format

Filipe Silva19:10:45

there's also es2015 code in another folder

Filipe Silva19:10:54

the esm2015 one

Filipe Silva19:10:22

when you said this doesn't work, do you mean that it's not a feature that is available or that there's no reasonable way such a feature could be developed?

thheller19:10:59

well, not going to call it impossible. I certainly can't implement it in a reasonable way.

thheller19:10:22

if you had perfect externs then it is possible yes

thheller19:10:41

BUT that only applies when mixing code

Filipe Silva19:10:52

You mentioned there is no extern inference for js code. I imagined that the wrapped npm libs got some sort of extern inference.

Filipe Silva20:10:14

is it that they don't actually get any extern inference?

thheller20:10:03

"they" don't need inference, if by they you mean the npm libs

thheller20:10:39

the :advanced parts need externs for the code not part of the advanced compilation (ie. npm code)

thheller20:10:56

if you process some of the npm libs as part of advanced

thheller20:10:10

then they also don't need externs. but you still need externs for others

thheller20:10:46

AND if you have npm libs accessing the advanced code you'll need externs for those parts

thheller20:10:54

so the closure compiler doesn't rename those either

thheller20:10:28

the current state of npm just doesn't allow anything more than what is currently done

Filipe Silva20:10:34

no I don't think there's ever going to be a solution for the re-entrant case

Filipe Silva20:10:08

where by re-entrant I mean going from "compat" back to "full gcc" code

Filipe Silva20:10:31

which is probably me misusing the word

thheller20:10:34

with enough info about what is used it is possible

thheller20:10:44

but getting that info might not be possible 😛

Filipe Silva20:10:48

yeah but no one is going to bother getting all that

Filipe Silva20:10:09

which is why shadow-cljs filled up this need so well

Filipe Silva20:10:35

full npm compat is sweet

thheller20:10:32

bitter sweet maybe 😛 I kinda regret exposing us to that world sometimes

Filipe Silva20:10:50

tbh that's what drew me to shadow-cljs instead of any other setup

thheller20:10:15

yeah, sometimes you just want access to all that stuff

Filipe Silva20:10:24

I'm from a JS background and it's a hard sell to have a huge overhead to all the stuff you already know

thheller20:10:16

yeah, same for me with Clojure .. already had 10+ years of experience with Java so having access to that was sort of the killer argument

thheller20:10:47

same for the JS parts ... just felt like missing out

thheller20:10:03

but the npm/JS ecosystem is a lot more messy 😛

thheller20:10:36

we'll see how this evolves. I forsee a lot of breakage if they ever adopt more ESM code

Filipe Silva20:10:54

atm some libraries publish it under non-standard package.json entry points, like module, esm, or es0215

thheller20:10:10

the problem is interop

Filipe Silva20:10:13

which means that the resolver needs to know to look these up... and it gets messy

thheller20:10:19

so 100% commonjs is fine

thheller20:10:25

100% esm would be great

thheller20:10:31

mixing ... is just painful 😛

thheller20:10:45

(shadow-cljs knows about those btw)

thheller20:10:03

just doesn't use them by default

Filipe Silva20:10:10

I sorta expected the wrapper to handle the interop well enough, it's similar to the webpack wrapper

thheller20:10:39

the problem is the non-strict interop that babel/webpack had

thheller20:10:52

webpack v5 will remove it so we'll see how that goes

thheller20:10:17

import { aThing } from "some-common-js" can't work in strict mode

thheller20:10:38

only import foo from "some-common-js" + foo.aThing

thheller20:10:58

but lots of code relies on the babel interop mode

thheller20:10:14

which means all code moving to the strict variant will break the packages not yet moved

thheller20:10:17

fun times ...

Filipe Silva20:10:19

for all the good webpack did, it also messed stuff up a bit by letting people write packages that only work on webpack pipelines

thheller20:10:52

indeed. although babel takes some of the blame too

Filipe Silva20:10:56

like all the require('something.css') and defaulting resolving .wasm extensions by default

Filipe Silva20:10:04

huge burden on consumers

thheller20:10:38

I understand the need for all of that. just wish they hadn't built it on top of require

thheller20:10:50

heck even require.asset("something.css") or so would have been better

Filipe Silva20:10:23

yeah something that just wasn't overloading the default stuff that much

Filipe Silva20:10:24

webpack ended up doing something similar to what I was asking about, where they pulled out esm code into one giant scope, but bail on a number of circumstances

Filipe Silva20:10:10

So imagine this case then: - I have cljs code - I have some es2015 libs that sometimes call each other, but never call non-es2015 libs - I have non-es0215 libs that call each other, but not es2015 libs would this case work by having the es2015 libs in advanced mode?

Filipe Silva20:10:45

I know this isn't something that would just generally work, I'm mostly thinking of a case where I am very familiar with my dependencies

thheller20:10:09

there is no mode for it but that would be possible yes

thheller20:10:44

but even some libraries that boast to be es2015 aren't

thheller20:10:49

eg. lodash-es

Filipe Silva20:10:52

and if I switched it a bit to the point where the es2015 libs called non-es2015 libs, but not the other way around?

thheller20:10:19

if you have externs for the non-es2015 parts

Filipe Silva20:10:58

couldn't those get figured out by shadowcljs the same way it does when cljs calls a compat-mode npm lib?

thheller20:10:01

es2015 then effectively becomes the same as CLJS code

thheller20:10:27

kind of but not really

thheller20:10:10

JS is just handed to the GCC to do its thing. it would need to do the same kind of processing that is done for CLJS

Filipe Silva20:10:27

yeah that makes sense

thheller20:10:52

currently when processing the npm JS code

thheller20:10:10

it looks for module.exports (and variants thereof) and collects the exported names as externs

thheller20:10:51

but in practice that is actually not a good idea and doesn't work when the JS code isn't processed by shadow-cljs (eg. react-native, node-script, node-library, ..)

thheller20:10:02

so the code is mostly relying on externs inference in CLJS

Filipe Silva20:10:45

I didn't actually understand that... I thought "extern inference" meant "looking through js code to try and figure out externs" but now I understand it means "looking through CLJS to figure out npm lib externs"

thheller20:10:04

externs inference is done in CLJS code

thheller20:10:19

it looks at the code and tries to figure out if something you accessed will be coming from npm

thheller20:10:34

when it can't figure that out it will warn

thheller20:10:54

in many cases it is easy to figure out (:require ["some-npm-lib" :refer (foo bar)])

thheller20:10:03

easy to add the refers to externs

thheller20:10:27

(defn wrap-baz [x]
  (.baz x))

thheller20:10:44

not easy to know if baz is safe to rename or requires externs

thheller20:10:12

the JS processing and extra externs gathering is only supplemental

thheller20:10:29

and not active when building non-browser targets basically

Filipe Silva20:10:07

if I wanted to take a stab at doing that three tier approach, would you be open to accept contributions?

thheller20:10:15

if you supply an example project that can be tested against 😛

thheller20:10:29

adding that is like 10 lines of code or so

Filipe Silva20:10:33

yes I'd provide involved test cases

Filipe Silva20:10:04

it seems like an interesting entry point in contributing to shadow-cljs

thheller20:10:42

I'd first try to find an actual practical example

thheller20:10:59

like something that people would actually use. not some hypothetical thing that doesn't actually exist in the wild

thheller20:10:44

you can fake it with zero code changes and just a few symlinks today (or file copies)

Filipe Silva20:10:28

yeah a real example would also give a good indicator of the benefit

Filipe Silva20:10:46

I imagine the benefit is that the es2015 libs get a dramatic cut in size

Filipe Silva20:10:53

but maybe that ends up not being true

thheller20:10:23

the potential is there for sure

Filipe Silva20:10:10

oh I think I have an interesting example

thheller20:10:22

but I can think of a billion things that can still to wrong

thheller20:10:42

using ES6+ code still has a few issues in the way it is currently implemented

thheller20:10:51

pretty much only works for :browser builds

Filipe Silva20:10:16

yes I don't have any illusions that code on npm will always find way to break

Filipe Silva20:10:36

npm was never really built with forwards compat in mind

Filipe Silva20:10:41

so imagine I have a bunch of non-es-2015 libs

Filipe Silva20:10:53

and then I bundle them via rollup

Filipe Silva20:10:08

kinda of how the cljs recommended they were bundled via webpack instead

Filipe Silva20:10:16

but this time the output is actually a giant es2015 module

Filipe Silva20:10:33

(that probably is broken because of some weird lib)

Filipe Silva20:10:04

if it's not broken though, GCC takes over and might do some good because it got a lot of es2015 to work with

Filipe Silva20:10:41

and since it's a giant es2015 module, gcc can do codemotion

thheller20:10:58

I thought rollup also only did it for all esm?

thheller20:10:10

or does it support the mixed thing?

Filipe Silva20:10:11

no, you can config for commonjs compat

Filipe Silva20:10:42

actually let me check that this produces es2015...

Filipe Silva20:10:42

compat isn't as good as webpack, but it exists

thheller20:10:59

never used rollup on anything real

Filipe Silva20:10:59

this of course is subject to all the pitfalls in random code we discussed

thheller20:10:23

I added a new :js-provider :external not too long ago

thheller20:10:50

which lets you use webpack to create to create an "external" package

thheller20:10:02

something like the official CLJS webpack guide recommends, just automated

Filipe Silva20:10:17

in angular cli we're actually experimenting with doing rollup inside webpack... because webpack has a lot of trouble making code in lazy chunks tree shakeable

thheller20:10:38

eek lazy chunks

Filipe Silva20:10:41

so we process all the es2015 code with rollup, then pass on that output (including lazy modules) to webpack

thheller20:10:01

I'm scared of libraries actually using import()

thheller20:10:29

and top-level await

thheller20:10:37

the JS world sure has crazy ideas ...

Filipe Silva20:10:57

with webpack the problem is this: - if you have a single chunk, maybe webpack will do module concatenation and everything is in a single scope, then Terser can do tree-shaking - if you have multiple chunks module then concatenation breaks down, and the webpack module loader indirection stops Terser tree-shaking

thheller20:10:42

seems like something they could fix with all the funding they have 😛

Filipe Silva20:10:46

rollup, similar to GCC, performs tree-shaking with bundling information

Filipe Silva20:10:06

so those are the only two JS tools today that can actually handle tree shaking across chunks/modules

Filipe Silva20:10:41

they probably could but are trying to do build caching as their main focus atm I think

Filipe Silva20:10:38

which is why I think shadow-cljs is in a really good place to take advantage of es2015 libs

Filipe Silva20:10:18

contrary to most of the js ecosystem, the clj ecosystem is not averse to non-js toolchains like java

Filipe Silva20:10:38

the pure js GCC implementation is pretty much unusable, so realistically GCC needs java

Filipe Silva20:10:25

anyway I'll try to setup a few realistic test cases, then some mock cases that explore it a bit more, then try a implementation

👍 4
thheller21:10:14

happy to guide you through it. it will mostly be about changing the resource type from :shadow-js to just :js 😛

thheller21:10:29

so the logic there would need to change

Filipe Silva21:10:24

cool, thank you for the pointer

Filipe Silva21:10:35

I'll look there after the test cases

aisamu21:10:56

Good to know! And where would :http-resource-root and :preloads fit into this new pattern?