nbb 2025-04-25

Another deno question: How are modules resolved with nbb? For instance, is something like this possible? Or are npm packages mandatory?

(ns test (:require  ["jsr:@luca/cases@1.0.0" :as cc]))

(print (cc/camelCase "test string" ))

@gregorybleiker This script now works:

(ns script
  (:require
   ["npm:react@18.3.1"]
   ["npm:ink@5.2.1" :refer [render]]
   ["npm:ink-select-input$default" :as SelectInput]
   [reagent.core :as r]))

(def items (clj->js [{:label "First"
                      :value "first"}
                     {:label "Second"
                      :value "second"}]))

(declare ink-state)

(defn handle-select [i]
  ((.-clear ink-state))
  ((.-unmount ink-state))
  (js/console.log i)
  (js/process.exit 0))

(defn select []
  [:> SelectInput {:items items :onSelect handle-select}])

(js/console.log render)

(def ink-state (render (r/as-element [select])))
with
deno run -A jsr:@babashka/nbb@1.3.200-test-3 /tmp/script.cljs

can you verify?

the bundle command doesn't work yet, this just assumes npm/node, not deno, but I can take a look at this later

❤️ 1

@borkdude will do asap, won't be before this evening though, I'm afraid

This sample script form your repo also works:

(ns main
  (:require ["jsr:@hono/hono" :as h]
            ["npm:react"]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(def app  (new h/Hono))

(.get
 app
 "/"
 (fn [ctx]
   (.text ctx "Hello Hono!")))

(js/Deno.serve app.fetch)

you need to manually load react though, this doesn't work without it

since reagent must find react somewhere

what du you mean with "manually"? put it in deno.json, for example? or just the require part?

include ["npm:react"] in the require part

just once somewhere in your app

if that's all that's needed that is ace!

before reagent is loaded

@borkdude can confirm above is working (couldn't wait)

(ns main                                                                
  (:require ["jsr:@hono/hono" :as h]                                    
            ["npm:react"]                                               
            ["npm:react-dom"]                                           
            [reagent.core :as r]                                        
            [reagent.dom.server :refer [render-to-string]]))            
                                                                        
(defonce state (r/atom 0))                                              
                                                                        
(def app  (new h/Hono))                                                 
                                                                        
(.get                                                                   
 app                                                                    
 "/"                                                                    
 (fn [ctx]                                                              
   (.text ctx (render-to-string [:div [:p "Hello"]])                    
)))                                                                     
                                                                        
(js/Deno.serve app.fetch)                                               
                                                                                                                                     

is this the way to include reagent.dom?

yes, but obviously something is wrong here, I'll take a look

sorry for being such a pain

not at all, it's a nice challenge to make this work ;)

💪 1

it's unclear to me if react-dom server should be loaded with: "npm:react-dom/server" or "npm:react-dom-server"

ok, apparently "npm:react-dom/server"

hm, that gives me an error though

also, it would be very un-npm-y, or not?

it does work for this script:

import React from "npm:react";
import ReactDOMServer from "npm:react-dom/server";

// A simple React component
function HelloWorld() {
  return <h1>Hello from React and Deno!</h1>;
}

// Render the component to a string
const html = ReactDOMServer.renderToString(<HelloWorld />);

// Output the rendered HTML
console.log(html);

I'm getting locally:

Type:     TypeError
Message:  Could not resolve 'npm:react-dom@17.0.2/server'

the script above is tsx, correct?

This works now with test-5:

(ns main
  (:require ["npm:react@19.1.0"]
            ["npm:react-dom@19.1.0/server"]
            ["jsr:@hono/hono" :as h]
            [reagent.core :as r]
            [reagent.dom.server :refer [render-to-string]]))

(defonce state (r/atom 0))

(def app  (new h/Hono))

(.get
 app
 "/"
 (fn [ctx]
   (.text ctx (render-to-string [:div [:p "Hello"]])
)))

(js/Deno.serve app.fetch)

change to (.html ...) if you want to render the HTML to HTML

A small response for a webserver... but a giant leap for an app :)

🎉 1

excellent

I'll unpublish my stuff on jsr

somehow deno prefers the react version that is a dev dependency of nbb

hmm, weird, now this just works:

["npm:react"]
            ["npm:react-dom/server"]

I think as long as I don't use the local cli.js things are fine, it just prefers the local dependencies

yes, very strange. The whole packaging in deno is still very new to me. I listened to a podcast with Ryan Dahl and he says that the npm interaction is very complicated to develop

I'm still pretty stuck trying to get nbb to work fully with deno. I can get basic scripts to run (ones that don't import anything). As soon as I try to require things, an exception is thrown:

~/projects/clojure/nbb/my-app> deno --unstable-detect-cjs -A ~/projects/nbb/lib/nbb_main.js main.cljs
----- Error --------------------------------------
Type:     TypeError
Message:  (intermediate value)(intermediate value)(intermediate value).catch is not a function
error: Uncaught (in promise) Error: (intermediate value)(intermediate value)(intermediate value).catch is not a function
    at file:///home/greg/projects/nbb/lib/nbb_main.js:13:196
    at eventLoopTick (ext:core/01_core.js:178:7)
My current approach is to use bundle to see what is produced on the js side, but that doesn't seem to be what's happening when a file is invoked directly. @borkdude do you have any guidance on how to debug nbb? Insert print statements in the nbb code?

That’s how I’d do it. Sorry I didn’t have time to look at this yet

no worries. Some things are working. I'd really like reagent integration, no luck so far

I've been having mixed results so far. Just using deno libs with jsr seems to work ok, but everything else is breaking, for instance I can't get reagent working (because react is not found) and when I export stuff from the bundler then nbb stops working all together with an exception.

reagent + react is a special combination in nbb so that might be expected (could be solved, but I haven't looked into it) what exception do you get from the bundler?

deno --unstable-detect-cjs --unstable-sloppy-imports -A jsr:@gregb/jsr-nbb-tryout@0.0.5 sample1.cljs error: Uncaught (in promise) Error: EISDIR: illegal operation on a directory, open at node_internal_captureLargerStackTrace (ext:deno_node/internal/errors.ts:93:9) at node_internal_uvException (ext:deno_node/internal/errors.ts:186:10) at denoErrorToNodeError (ext:deno_node/internal/errors.ts:1898:14) at Module.readFileSync (ext:deno_node/fs/fs_readFile.ts:67:13) at https://jsr.io/@gregb/jsr-nbb-tryout/0.0.5/lib/nbb_bundler.js:14:51 at https://jsr.io/@gregb/jsr-nbb-tryout/0.0.5/lib/nbb_bundler.js:17:212

should be easy to repro I think as a smaller example, by using node readFileSync?

the whole packaging in nbb is black magic to me... for instance, how is it possible that you can pull clojure dependencies in with nbb.deps???

it calls out to #babashka to do so

it's kind of a hack

I have to do more debugging why it's not working even in the trivial case. From all the files produces in bb release in the lib folder, do I really need to export all of them?

if they are used in an import, and you're using deno, I guess so

not exactly in which scenario

if they are loaded only internally, then you don't have to expose them

are things from nbb_bundler.js ever used outside (do you know)?

should not be, it's just a tool that produces a single file

hopefully I have some time to look into this tomorrow

It’s done using dynamic import

so similar to:

$ deno repl
Deno 1.44.4
exit using ctrl+d, ctrl+c, or close()
> import("jsr:@luca/cases@1.0.0")
✅ Granted net access to "".
Promise { <pending> }io/@luca/cases/meta.json

but I had to give it explicit access

hope that makes sense?

I do notice that it doesn't work in nbb currently:

user=> (require '["jsr:@luca/cases@1.0.0" :as cases])
"Error: Cannot find module 'jsr:@luca/cases@1.0.0'\nRequire stack:\n- /Users/borkdude/dev/babashka/script.cljs"
Feel free to file an issue so we can see what's going on

Sorry, was afk. Will do.

Been doing some more trials.. https://github.com/gregorybleiker/nbb/blob/main/sample.cljs I've tried exporting nbb_reagent through deno.json (which shouldn't be in .gitignore) but it's not picked up correctly. Need to have bit of a frustration break.

👍 1

using pnpm install jsr:@luca/cases and (:require ["@luca/cases" :as cc]) does work btw. Only (small) downside: you have to use pnpm or yarn

and perhaps the npm compatibility that jsr described?

I'll get back to this later btw, I have to do other stuff today and tomorrow

Thanks again for picking this up so quickly!

I have the day off, so I'm trying to piece together the sample above

if you have time and want to move this forward, perhaps you can research how to make a jsr package from nbb :)

👍 1

I put together some stuff, it's rather chaotic, I'm afraid. I'll have to postpone for a few days as well. But deno -A jsr:@gregb/jsr-nbb-tryout now starts nbb (yay) and the code is in https://github.com/gregorybleiker/nbb

I will revoke (hope I can!) the jsr package as soon as it's not needed any more

also, `deno -A jsr:@gregb/jsr-nbb-tryout deno_import.cljs aTestString` with direct import is working

thanks :) I'll have a look in a few days. so the jsr import is working?

👍 1

I can also create a PR if you want so it's easier for you to check what I changed. But it's no where near to be a mergeable pr, I'm afraid

I put in this: https://github.com/babashka/nbb/issues/379 Could it be something to do with the import being async (await import...)?

I'll have a quick look

❤️ 1

never really tested with deno

I haven't looked at deno too much either, but I really like the no node_modules

I must make quite a lot of changes to get nbb working with deno in development, but all of those changes should be compatible with node, such as giving library imports a node: prefix, not using js/process, but (:require ["node:process" :as process]) etc.

Hard to say if it's worth the effort. I'd find it wicked if it worked, though.

Is it possible to directly call the deno import through nbb,like 'js/await js/import' or similar?

I pushed the deno related changed to a branch called deno-dev and I can start it like this: Run:

bb dev
in one console. In another:
deno run -A ./cli.js -e '(ns foo (:require ["jsr:@luca/cases@1.0.0" :as cases]))'
When I make some changed in load-js-module I do see that the module can be correctly loaded:
(-> (resolve libname)
            (.catch
             (fn [_]
               (prn :dude _)
               (.then (esm/dynamic-import libname) js/console.log)
               #_((.-resolve (:require @ctx)) libname))))
$ deno run -A ./cli.js -e '(ns foo (:require ["jsr:@luca/cases@1.0.0" :as cases]))'
:dude #object[Error Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file and data are supported by the default ESM loader. Received protocol 'jsr:']
[Module: null prototype] {
  camelCase: [Function: camelCase],
  constantCase: [Function: constantCase],
  kebabCase: [Function: kebabCase],
  pascalCase: [Function: pascalCase],
  snakeCase: [Function: snakeCase],
  splitPieces: [Function: splitPieces],
  titleCase: [Function: titleCase]
}
about js/import or js/require: yeah, but those are currently hacked by nbb to make use of current directory etc. not with deno in mind.

so I think it can be fixed. need some more time, I have to make dinner for my wife now, it's my turn :)

you can make a working js-import like this:

(def my-import (js/eval "(s) => import(s)")
and then use that

Same here, really appreciate the effort! But don't keep your wife waiting!

Got this working now:

$ deno run -A ./cli.js -e '(ns foo (:require ["jsr:@luca/cases@1.0.0" :as cases])) (cases/camelCase "dude-foo")'
"dudeFoo"

Got the REPL working in deno as well:

$ deno run -A ./cli.js
Welcome to nbb v1.3.196!
user=> (+ 1 2 3)
6

user=> (require '["jsr:@luca/cases@1.0.0" :as cases])
nil
user=> (js/console.log cases)
[Module: null prototype] {
  camelCase: [Function: camelCase],
  constantCase: [Function: constantCase],
  kebabCase: [Function: kebabCase],
  pascalCase: [Function: pascalCase],
  snakeCase: [Function: snakeCase],
  splitPieces: [Function: splitPieces],
  titleCase: [Function: titleCase]
}
nil
user=> (cases/camelCase "foo-bar")
"fooBar"

This is in the dev branch?

yes, now pushed it to the dev branch

👍 1

the nbb tests are failing though with what looks like an innocent change. https://github.com/babashka/nbb/compare/deno-dev?expand=1 the REPL tests get stuck into a console REPL... :) I think because arg parsing went wrong somehow, since when invoking with nbb with no args, it starts a REPL 😂

wouldn't this change break all the people's code that are using only node? because now the imports need to be prefixed by node: ?

it's a node convention since node 14

almost all of the tests are passing in deno itself now too

if the tests keep working like this, I'll just go ahead and merge this

🎉 1

btw I have no idea what jsr: is but I'm happy it works :)

Seems to be the new kid on the block because npm seems to be under-loved by MS... Ryan Dahl has given it his blessing, that is good enough for me

do you expect to import with the jsr prefix or without it?

I haven't tested what happens when you do jsr add and then without the prefix

I would think it should be the same as with deno...https://docs.deno.com/runtime/fundamentals/modules/

So with prefix

oh that seems to work after jsr add as well:

$ deno run -A ./cli.js
Welcome to nbb v1.3.196!
user=> (require '["@luca/cases"])
nil

Then you have a deno.json now which is used?

yeah a deno.lock apparently

I published a new nbb but the one published to npm doesn't seem to work:

$ deno run -A npm:nbb@1.3.198 /tmp/script.cljs
----- Error --------------------------------------
Type:     TypeError
Message:  [ERR_UNSUPPORTED_ESM_URL_SCHEME] Only file and data URLS are supported by the default ESM loader. Received protocol 'jsr'
error: Uncaught (in promise) Error: [ERR_UNSUPPORTED_ESM_URL_SCHEME] Only file and data URLS are supported by the default ESM loader. Received protocol 'jsr'
    at file:///Users/borkdude/dev/nbb/node_modules/.deno/nbb@1.3.198/node_modules/nbb/lib/nbb_main.js:13:196
The one compiled locally even with advanced does work:
$ deno run -A ./cli.js /tmp/script.cljs
Not sure what's going on here... But I'll have to postpone this to tomorrow to find out

so I'll probably have to publish nbb to jsr first to get this to work

what a bummer. Strange that it does work locally but not as npm package. I always thought that npm didn't do anything to the code, just package it. Shall I open an issue on deno with the use-case above?

> Strange that it does work locally but not as npm package. This is because it's not an npm package but a local project. When in npm, jsr isn't usable. See the above deno issue

I could just try to publish to jsr, but have to figure out how to do that

I read the issue you're referencing, but is that really what is happening here? Because the jsr reference isn't in an npm package. nbb is launched via npm and interprets the cljs, or not? So the actual import happens in the interpreted part, which itself is not an npm package

the import happens in a function that is part of the nbb npm package, this is why it complains

publishing to jsr would be beneficial in any case, though

according to the https://jsr.io/docs/publishing-packages, it should be as simple as deno publish, but it never is, is it? 😉

no, you need to make a deno.json or jsr.json file with "exports"

and I didn't see how to expose a "bin" or "main" binary script using that

I hope it's not index.mjs since I've designated index.mjs to import nbb's programmatic API

although I guess I could control that via the exports

👍 1

also, I didn't see this before: https://jsr.io/docs/npm-compatibility

it's a bit less cool than directly importing in the cljs, but it would probably work for what I'm trying to do (run an oak server that serves datastar, using hiccup to generate the page)

oak is only on jsr, is that your problem?

yes, it's a deno thing

or maybe not?

ok, give that a try I'd say, but making jsr: work in nbb is a good goal nonetheless. please do use the latest nbb version to see if everything still works

👍 1

I actually didn't even check...

for some reason my stack trace is different to @borkdude:

~/projects/clojure/nbb/hello-test> deno run -A npm:nbb@1.3.196 deno_import.cljs
----- Error --------------------------------------
Message:  Cannot find module 'jsr:@luca/cases'
Require stack:
- /home/greg/projects/clojure/nbb/hello-test/deno_import.cljs
Cannot find module 'jsr:@luca/cases'
Require stack:
- /home/greg/projects/clojure/nbb/hello-test/deno_import.cljs

it actually still looks the same as before...

You are still using version 196

ah, with 200 it's the same