nbb

Daniel Gerson 2022-09-13T12:47:54.434959Z

Hello. I've been trying to get the same code-base working with both NBB and Shadow-cljs, but this seems impossible (I expect I've done something stupid 😅). Shadow is configured as :target :esm. This is the same use case as referred to some time earlier in chat, but I've written it up here: https://github.com/dmg46664/problems/tree/main/03_nbb_and_shadow_import

borkdude 2022-09-13T12:49:58.265869Z

You should probably write `

js/WebSocketServer

borkdude 2022-09-13T12:50:03.298709Z

since it's a global

borkdude 2022-09-13T12:51:43.512939Z

or not? is it coming from the ws package? I thought it was maybe some webstandard

borkdude 2022-09-13T12:52:52.658299Z

anyway, according to that package on npm you should import it like this:

import WebSocket, { WebSocketServer } from 'ws';

Daniel Gerson 2022-09-13T12:56:38.750759Z

Thanks for the quick response. Yes, the server class is from ws as you've concluded. Assuming your last response means I should :require ["ws" :as WebSocket :refer [WebSocketServer]] then this works in Shadow but not in NBB.

Daniel Gerson 2022-09-13T12:56:54.007269Z

damn.

borkdude 2022-09-13T12:57:08.021349Z

well, not exactly

borkdude 2022-09-13T12:57:35.587819Z

import Foo from 'ws' is (:require ["ws$default" :as Foo]) in CLJS, since Foo is the default export

borkdude 2022-09-13T12:58:23.756799Z

Try this:

$ node --experimental-top-level-await

borkdude 2022-09-13T12:58:30.799809Z

x = await(import('ws'))

borkdude 2022-09-13T12:59:09.633479Z

> x.WebSocket
> x.WebSocketServer
[class WebSocketServer extends EventEmitter]

borkdude 2022-09-13T12:59:24.203769Z

hmm, both seem to work and this is normally what happens in nbb as well

Daniel Gerson 2022-09-13T12:59:38.801879Z

I thought there needs to be an module.exports = { default: ... in order for there to be a default export?

borkdude 2022-09-13T13:00:12.676269Z

no, module.exports isn't ES6

borkdude 2022-09-13T13:00:25.084169Z

ES6:

export default ...

Daniel Gerson 2022-09-13T13:00:27.543549Z

Same for export default ... in ES6

Daniel Gerson 2022-09-13T13:00:51.565479Z

yeah... but I don't see a default export in the ws code.... ?

borkdude 2022-09-13T13:01:30.791389Z

To me it's weird that x = await(import('ws')) isn't the same as (:require ["ws" :as x]) in nbb, so this is something that needs to be investigated

borkdude 2022-09-13T13:02:30.495409Z

it is the mixture of CJS and ES6 which complicates this a lot. if ws was exposed as an ES6 module, it would be the same I think

Daniel Gerson 2022-09-13T13:02:58.077329Z

I swear this JS module stuff has done my head in 😅

Daniel Gerson 2022-09-13T13:04:29.828239Z

So if I read your comments above correctly, I either need to do x = await(import('ws')) or node --experimental-top-level-await ? Much obliged!

Daniel Gerson 2022-09-13T13:04:52.365329Z

I'm also glad that I'm not completely crazy!

borkdude 2022-09-13T13:05:05.867159Z

what I meant to say was: you can check how modules are imported by doing that manually

borkdude 2022-09-13T13:05:27.797249Z

but it's not exactly the same in nbb as when I'm doing that, which is what I would expect... it's complicated

borkdude 2022-09-13T13:05:35.177579Z

this needs some debugging

Daniel Gerson 2022-09-13T13:07:00.458159Z

Coolio, thanks for confirming. Yes I was reading your JS as assumed clojure, hehe. Should I leave this with you? Do you need anything else from me?

Daniel Gerson 2022-09-13T13:07:12.428189Z

Or are you asking me to debug it?

borkdude 2022-09-13T13:08:17.862429Z

if nbb behaves differently than shadow-cljs + target esm then I think it could be a bug in nbb: issue welcome

Daniel Gerson 2022-09-13T13:08:37.611219Z

no problemo, will raise! 🙂

Daniel Gerson 2022-09-13T13:10:08.753829Z

I've assumed that NBB defaults to a context of ESM. There's no switch for it to behave differently... ?

borkdude 2022-09-13T13:10:30.191499Z

there's no switch

✅ 1
borkdude 2022-09-13T13:17:37.771409Z

So this is kind of a repro of what happens in nbb vs vanilla import:

import { createRequire } from 'module';
import { pathToFileURL } from 'url';

const req = createRequire(pathToFileURL('./script.cljs'))
const ws = req.resolve('ws');
const mod = await import(ws);
console.log(mod);
const mod2 = await import('ws');
console.log(mod2);

borkdude 2022-09-13T13:17:55.286789Z

the logging of mod seems different than the logging of mod2, although they are very similar

borkdude 2022-09-13T13:18:45.745519Z

I think it is because of this:

"exports": {
    "import": "./wrapper.mjs",
    "require": "./index.js"
  },

borkdude 2022-09-13T13:18:48.987649Z

in the package

borkdude 2022-09-13T13:18:59.207149Z

nbb should find wrapper.mjs

🙌 1
borkdude 2022-09-13T13:20:41.710139Z

nbb is going through createRequire since that allows you to resolve a module relative to a script

borkdude 2022-09-13T13:20:53.972119Z

and this allows you to execute scripts from outside the current working dir

Daniel Gerson 2022-09-13T13:24:24.172149Z

Written up as issue 256, just going through your comments above now.

Daniel Gerson 2022-09-13T13:30:42.332829Z

@borkdude Well done for deducing this!

borkdude 2022-09-13T13:34:02.247999Z

I think I have a solution in branch issue-256 but there are two failing tests

Daniel Gerson 2022-09-13T13:36:22.073699Z

Aha, so the "exports": {in package.json enables ws to deliver to both CommonJS and ES6 targets. I shouldn't have looked at the code then to deduce this.

Daniel Gerson 2022-09-13T13:36:55.090419Z

(amazing how much JS I've learned moving to CLJS...)

borkdude 2022-09-13T13:40:15.533099Z

ok, pushing a solution now

borkdude 2022-09-13T13:40:30.103559Z

(to github, not to npm yet)

Daniel Gerson 2022-09-13T13:40:34.642139Z

Amazeballs! partyparrot

borkdude 2022-09-13T13:41:09.248129Z

https://github.com/babashka/nbb/pull/257

borkdude 2022-09-13T13:54:51.820659Z

publishing 0.7.135 with the fix

Daniel Gerson 2022-09-13T13:55:53.470209Z

I guess some people may find strange corrected behaviour on upgrade... not disputing it's the right course of action.

borkdude 2022-09-13T13:58:17.952669Z

yes, this is a trade-off, but I prefer correctness over breaking changes ;)

😂 1
borkdude 2022-09-13T13:58:35.853029Z

should be available on npm now

🙌 1
borkdude 2022-09-13T13:59:23.183269Z

Seems to work now

Daniel Gerson 2022-09-13T14:01:06.718009Z

Confirmed it works! Incredible to watch you work.

Daniel Gerson 2022-09-13T14:04:22.293349Z

Thanks a ton. My previous post about abandoning NBB is no longer correct. I'm attempting to use polylithto shim in different libraries for shadow and nbb and get the best of both worlds. Don't see why the majority of code shouldn't be shared. NBB has better stacktraces on load failures, and I can tweak on AWS. Shadow can use (meta ... to show file lines on stacktraces/ ala timbre for local testing.

borkdude 2022-09-13T14:05:51.704679Z

Glad to be of help

Daniel Gerson 2022-09-13T15:45:31.373929Z

I guess another difference between Shadow and NBB is the treatment of ^:export https://clojurescript.org/reference/advanced-compilation#access-from-javascript. Shadow seems to still be able to put variables in a global scope even for`:target :esm` , at least for the watch command. For NBB this is not available. Not sure if that's what is referred to by > In development builds however it still exports everything into the global scope via from https://clojureverse.org/t/generating-es-modules-browser-deno/6116 I was thinking of using this for message passing between mock NPM modules in my test server, but I guess it's not proper as variables will be eradicated from global scope with ES6.

borkdude 2022-09-13T15:46:14.760829Z

@danielmgerson nbb doesn't emit any .mjs files so export doesn't really make sense

Daniel Gerson 2022-09-13T15:49:24.185739Z

I have a fake @aws-sdk/xxxmodule with JS code that refers back to my server code. Alas won't work with NBB. No biggy, I'll create a polylith component that wraps this differently for aws and my test server. I wish there was a way to test web-socket apiGv2 code locally within local stack. I refuse to require a live stage to test it. hehe.

borkdude 2022-09-13T15:49:43.037959Z

I think shadow puts things in a global scope because of reloading (and ES6 reloading doesn't work, so this is shadow's workaround)

borkdude 2022-09-13T15:50:49.506639Z

you can load nbb files from js like this:

import { loadFile } from nbb
await loadFile('src/my_stuff.cljs')

Daniel Gerson 2022-09-13T15:51:31.011359Z

Yeah, that's not applicable to my use case. It assumes the calls only go one way.

borkdude 2022-09-13T15:52:42.354409Z

you can put things in the global context like this:

(set! js/globalThis.foobar x)

borkdude 2022-09-13T15:53:08.896809Z

the global var thing in shadow is only an artifact of development, I wouldn't rely on it

Daniel Gerson 2022-09-13T15:53:52.110539Z

ooh, didn't know about that. Thanks. It's only for test harness code, not for prod code.

borkdude 2022-09-13T15:55:17.302689Z

ok

Daniel Gerson 2022-09-13T15:57:03.802739Z

And by using js/globalThis I wouldn't be relying on that shadow quirk 😄 Cheers.

borkdude 2022-09-13T16:03:03.373409Z

@danielmgerson Ah in nbb you have to write:

(set! (.-foobar js/globalThis) 2)
js/globalThis.foobar ;;=> 2

👍 1