Fork me on GitHub
#clojurescript
<
2022-03-26
>
West00:03:52

Trying to do promise interop with js.

backend> (-> (js/Neutralino.filesystem.readDirectory js/NL_PATH)
                       (p/then #(prn %))
                       (p/catch #(prn %)))
#<Promise[~]>
I'm not sure why I can't get anything to print out. Is there a way to inspect a promise?

West00:03:16

If necessary I can upload a git repo with what I'm working on later.

West03:03:32

If I decide not to use promesa, then I get this:

(-> (js/Neutralino.filesystem.readDirectory js/NL_PATH)
      (.then #(prn %))
      (.catch #(prn %)))
It still just returns a promise though, I get nothing showing up in the console.

p-himik09:03:40

Yeah, I definitely wouldn't use Promesa here. :) But that's besides the point. Your promise is never resolved because .readDirectory probably never attempts to read that directory. It's either bugged or blocked by something else. The latter is often the case when you have another code running, just because JS is single-threaded. But if you're doing it in a REPL then that shouldn't be the case.

West18:03:05

I might have to use electron after all, even though I really don't want to ship another chromium with my application.

p-himik09:03:34

> But, if you look at, say, the port of clojure.pprint for use with ClojureScript, it involves a macro namespace

p-himik09:03:28

The CLJS version of pprint needs additional CLJ code. Technically, I guess, it could be moved to a different namespace. But either I'm wrong and that's not possible or the authors decided not to do that for some reason.

pinkfrog11:03:15

Example code is:

(defn- main-stack
  []
  (let [stk (createNativeStackNavigator)]
    [:> (.-Navigator stk) {:headerMode "none"}
     [:> (.-Screen stk) {:name "Welcome"
                         :component (r/reactify-component welcome-stack)}]]))
and metro warns about this:
WARN  Got a component with the name 'app.welcome.stack.welcome_stack' for the screen 'Welcome'. React Components must start with an uppercase letter. If you're passing a regular function and not a component, pass it as children to 'Screen' instead. Otherwise capitalize your component's name. 

p-himik11:03:33

The warning seems rather self-explanatory, no?

p-himik11:03:29

This part >

React Components must start with an uppercase letter.
is of course invented by Metro - React itself doesn't care.

pinkfrog11:03:03

I dunno how to hide this warning. The name http://app.welcome.xxx is the cljs ns name, so it’s something I cannot control though.

pinkfrog11:03:22

e.g., I cannot make the ns name to begin with uppercase

p-himik12:03:29

What about the second part of this warning? About passing the function. Or you can look at Metro's sources, find where the warning is issued, and see if there's a way to disable it.

p-himik12:03:59

As the last resort, you can always override console.warn and just filter it out explicitly, by text.

bringe01:04:14

Ran into this today. I did what’s mentioned above:

(set! js/console.warn
      (fn [message]
        (when-not (str/includes? message "React Components must start with an uppercase letter.")
          (js/console.warn message))))

p-himik07:04:10

Yeah, we discussed it on Reddit as well. If I were one of you guys, I'd create an issue for react-navigation. ;)

bringe17:04:29

I was partly through creating one, and they required an repro project link, so I abandoned it because I didn’t want to spend the time setting up a separate project just to show them a simple issue, unless I knew they were going to accept a change (the one I experienced it in is a work project). I understand the importance of repro steps, but always requiring a project link is going to stop some people from reporting real issues at all, which I think is unfortunate.

bringe17:04:40

FWIW, here’s updated code that won’t go into infinite recursion and also respects all args given to console.warn.

(defonce warn js/console.warn)
(set! js/console.warn
      (fn [& args]
        (when-not (str/includes? (first args) "React Components must start with an uppercase letter.")
          (apply warn args))))

p-himik17:04:45

If you make something like "Add a flag to disable that warning" a feature request, surely that won't require a repo? Because there's nothing to put in the repo.

bringe17:04:39

Good point!

bringe17:04:41

I’ll do that

bringe03:05:38

FYI, someone closed the above request, informing that LogBox.ignoreLogs can be used to silence the warning.

Carlo13:03:43

Another clj vs cljs question. In clj I have the function requiring-resolve , used like (requiring-resolve 'foo.bar/baz). What should I use in clojurescript? I know that there's resolve in cljs, but it doesn't automatically load baz from the foo.bar namespace.

p-himik13:03:57

If you're building a complete JS bundle without self-hosting, then there are no dynamic requires.

p-himik13:03:27

If you can describe what's your primary goals are, maybe a better solution can be devised.

Carlo13:03:34

so, I mainly would need this as a dev help for me. I have in my emacs config:

(after! cider-mode
  (defun cider-tap (&rest r)
    (cons (concat "(let [__value " (caar r) "]"
                  "  (if-let [f (requiring-resolve 'emacs.portal.master/portal-add-metadata)]
                        (tap> (f __value))
                        (tap> __value))
                     __value)")
          (cdar r)))

  (advice-add 'cider-nrepl-request:eval
              :filter-args #'cider-tap))
which basically checks if I have the emacs.portal.master/portal-add-metadata function, and if that's there it acts as an interceptor to choose a correct visualizer for portal

Carlo13:03:06

the problem is that, being portal-add-metadata in an external library, I cannot just check `(resolve 'portal-add-metadata), because this will be evaluated in a different ns

Carlo13:03:45

maybe I could quickly try to switch to the emacs.portal.master ns, if I can detect something. Edit - this doesn't seem to work

p-himik13:03:14

A common way to solve it is in reverse - assume that the function is always there, and either use a classpath that has it or add a dummy noop instead.

Carlo13:03:26

so, I should (require '[emacs.portal.master :as emp]) and use that nonetheless, but if I don't add the dummy noop it will explode, right? And the dummy noop is just another library with the same path

Carlo13:03:48

I'm confused by the fact that if I evaluate this at a repl:

(require '[emacs.portal.master :refer [portal-add-metadata]])
(resolve 'portal-add-metadata)
I get nil

p-himik13:03:40

Indeed. It doesn't have to be a proper library - it can just be a separate dir that you include in your :paths via an extra alias. So e.g. alias :with-portal has either :paths or :deps (or :extra-deps - you get the point) that has a proper implementation of emacs.portal.master/portal-add-metadata. And an alias :without-portal has the same but with a noop implementation.

p-himik13:03:07

> I'm confused by the fact that if I evaluate this at a repl Try putting require anywhere that's not top-level. Wrap it in an if.

p-himik13:03:31

If you always have emacs.portal.master on your classpath but sometimes portal-add-metadata is not there, then using require + resolve and then checking for nil? should work.

Carlo13:03:27

So, something like:

(if (require '[emacs.portal.master :refer [portal-add-metadata]])
  :a
  :b)
gives me :repl/exception and no other info in the repl 😂

Carlo13:03:04

moreover, what's the conceptual problem with resolving-require in js?

Carlo13:03:16

I mean, why can't that be a thing?

Carlo14:03:19

maybe in cljs I can only use (:require ...) in the ns form, but not (require ...) as a standalone function

p-himik14:03:40

No, I meant as a separate combination:

(require 'emacs.portal.master)

(if-some [f (resolve 'emacs.portal.master/portal-add-metadata)]
  ...
  ...)
No clue why requiring-resolve - maybe just not a priority to have it.

p-himik14:03:56

Or maybe so that require is always at the top level, since you can't have it anywhere else. More likely.

Carlo14:03:27

the problem with this snippet:

(require '[emacs.portal.master])
(if-some [f (resolve 'emacs.portal.master/portal-add-metadata)]
  :a
  :b)
is that it returns :b even when I have the library in the classpath, probably because dealing with namespaced symbols is not what resolve does

p-himik14:03:07

Ehm, that's weird. It works just fine for me.

p-himik14:03:49

With the default REPL and compiler:

ClojureScript 1.11.4
=> (require '[app.core])
nil
=> (if (resolve 'app.core/main) 1 2)
1

Carlo14:03:23

you're right, I can do:

(require '[cljs.core.async])
(resolve 'cljs.core.async/chan)
;; => 'cljs.core.async/chan
but not
(require '[emacs.portal.master])
(resolve 'emacs.portal.master/portal-add-metadata)
;; => nil
Here are my shadow.cljs and deps.edn files:
;; shadow-cljs configuration
{:deps true
 :dev-http {8080 "public"}
 :builds
 {:frontend
  {:target :browser
   :modules {:main {:init-fn main.core/init}}}}}
and
{:deps
 {thheller/shadow-cljs {:mvn/version "2.17.8"}
  djblue/portal {:mvn/version "0.22.1"}
  emacs-portal-master/emacs-portal-master {:local/root "../emacs-portal-master"}}}

p-himik14:03:44

After you do (require '[emacs.portal.master]), what happens if you evaluate just emacs.portal.master/portal-add-metadata?

Carlo14:03:58

just :repl/exception. I think the require is not working for some reason

Carlo14:03:24

maybe these are not the right coordinates?

emacs-portal-master/emacs-portal-master {:local/root "../emacs-portal-master"}}

Carlo14:03:57

I also tried:

emacs-portal-master/emacs-portal-master {:local/root "/home/carlo/code/clojure/emacs-portal-master"}

p-himik14:03:22

Well, if your code doesn't work with require then yeah, something's fishy. :) The relative path should be OK, as long as it's correct relative to the deps.edn file where you're using it. But does that ../emacs-portal-master dir have its own deps.edn with correct classpath and sources?

Carlo14:03:27

that has only:

{:deps
 {metosin/malli {:mvn/version "0.8.4"}}},
do I need something more?

Carlo14:03:00

in there I have src/emacs/portal/master.cljc, and I didn't specify a sources path because src is default for deps.edn. Also, I can load emacs-portal-master as a dependency in the clj side.

p-himik15:03:01

I tried repeating your setup, to some extent. I have a project, and I have dir lib next to it, so its relative path also starts with ../. That dir has one file at src/lib/core.cljs, with one function f. I then ran npx shadow-cljs watch main in one terminal and, after opening a browser tab with the app, npx shadow-cljs cljs-repl main in another tab. In that tab:

cljs.user=> (require 'lib.core)
nil
cljs.user=> (resolve 'lib.core/f)
#'lib.core/f
So it all works as I would expect. I can't reproduce what you see. Maybe you can create a minimum reproducible example (with no external dependencies, no extra tools, etc) and share it.

❤️ 1
p-himik15:03:05

Moved the file from .cljs to .cljc, as it's in your case - same exact behavior, it all works just fine.

p-himik15:03:31

Given that you're using a CLJC file, are you sure that function doesn't end up behind a reader conditional that excludes it from CLJS?

Carlo15:03:31

thanks @U2FRKM4TW! I'll see about creating a reproducible example and check for conditionals!

👍 1
p-himik15:03:38

How so? Given that we're both using shadow-cljs, and it's working just fine for me. Or are you running it not from a REPL but from some CLJS file?

Carlo15:03:58

yes indeed, I was trying to call it from a cljs file

p-himik15:03:26

I see. Then just make that require into :require as a part of your ns declaration. The resolve part should still work.

Carlo15:03:40

because I thought that when I was evaluating a form in the cljs file, it was just like sending that expression to a repl

Carlo15:03:59

and instead what's happening is that I'm loading the namespace first, right (I think cider evaluation does that)

Carlo15:03:47

so cljs knows that I'm in a cljs namespace, and require is not available outside a ns declaration? I don't know if this is actually the reason

p-himik15:03:08

Maybe, I'm not familiar with the Emacs ecosystem. But in any case, see if :require works.

Carlo15:03:39

It refuses to require my namespace because it depends on malli.instrument, which seems to be only available in clj wow (I asked in #malli why that's the case, since I know that there are other projects, like guardrails, that do instrumentation in cljs)

👍 1
p-himik15:03:33

But given that you control that CLJC namespace, you can make a dependency on malli.instrument CLJ-specific.

Carlo15:03:10

oh, you are totally right, in fact, I don't even really depend on malli.instrument except for some code blocks that I was using to test the functionality. I'll remove them

Carlo15:03:12

ok now it's loaded and I get a proper response for

(resolve 'emacs.portal.master/portal-add-metadata)
next thing for me is figuring out how I can structure this so that it only runs in a repl, avoiding the ns* restriction

p-himik15:03:53

Does resolve not work if you make it a part of a CLJS file? I thought only require didn't work.

Carlo16:03:03

I still don't think that:

(resolve 'emacs.portal.master/portal-add-metadata)
by its own can work, I mean it's not an error, but will return nil . (If I don't do a require before in the ns form)

p-himik16:03:11

Wait. Why wouldn't you do :require in the NS form?

p-himik16:03:20

Of course you should do that - resolve needs that.

Carlo16:03:18

ok, I get that now; so now the problem is that was meant to be some dev tooling, and this forces me to add a require on a dev dependency in namespaces that are doing other things (and delete it after). Like, if I have an unrelated project that has a main.core, now I have to say:

(ns main.core
  (:require [emacs.portal.master]))
even when main.core doesn't have much to do with this dev library, and remove it manually afterwards

p-himik16:03:27

Right, that's when a noop NS comes in. The one I described at the very start.

Carlo16:03:45

the other option would be changing namespaces in:

(defun cider-tap (&rest r)
    (cons (concat "(let [__value " (caar r) "]"
                  "  (if-let [f (resolve 'emacs.portal.master/portal-add-metadata)]
                        (tap> (f __value))
                        (tap> __value))
                     __value)")
          (cdar r)))
but if I change namespace there, now all the variables referenced in the code are not valid

Carlo16:03:09

ok, let me read the noop solution again

p-himik16:03:06

So you will have that dev :require in all relevant namespaces. During the prod build, that dev namespace will have only noop functions (or no functions at all if you can still use resolve). During the dev build, that dev namespace will come from a completely different file and will have proper functions.

Carlo16:03:15

ok I see, but last question, where does the noop version live? The only possibility I see is that it's another dummy library that is in the default dependencies, that I then overwrite with my real dev library in a extra-deps clause

p-himik16:03:48

Anywhere you want. Logically, it would make sense to put it in the same project where you use that library, because it's the user that's concerned with noop, not the library itself. It doesn't have to be a separate library, it can be just an item in :extra-paths.

p-himik16:03:29

I have a :dev alias that has :extra-paths ["dev"] - I put all dev stuff there.

❤️ 1
Carlo16:03:03

oooh, I see, it's another path. I see, thank you, this clarifies all!

Carlo16:03:53

I learned a lot about the differences in requiring, thank you for the great help 😄

👍 1