Fork me on GitHub
#nbb
<
2022-09-13
>
Daniel Gerson12:09:54

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

borkdude12:09:58

You should probably write `

js/WebSocketServer

borkdude12:09:03

since it's a global

borkdude12:09:43

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

borkdude12:09:52

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

import WebSocket, { WebSocketServer } from 'ws';

Daniel Gerson12:09:38

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.

borkdude12:09:08

well, not exactly

borkdude12:09:35

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

borkdude12:09:23

Try this:

$ node --experimental-top-level-await

borkdude12:09:30

x = await(import('ws'))

borkdude12:09:09

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

borkdude12:09:24

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

Daniel Gerson12:09:38

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

borkdude13:09:12

no, module.exports isn't ES6

borkdude13:09:25

ES6:

export default ...

Daniel Gerson13:09:27

Same for export default ... in ES6

Daniel Gerson13:09:51

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

borkdude13:09:30

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

borkdude13:09:30

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 Gerson13:09:58

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

Daniel Gerson13:09:29

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 Gerson13:09:52

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

borkdude13:09:05

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

borkdude13:09:27

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

borkdude13:09:35

this needs some debugging

Daniel Gerson13:09:00

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 Gerson13:09:12

Or are you asking me to debug it?

borkdude13:09:17

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

Daniel Gerson13:09:37

no problemo, will raise! 🙂

Daniel Gerson13:09:08

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

borkdude13:09:30

there's no switch

1
borkdude13:09:37

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);

borkdude13:09:55

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

borkdude13:09:45

I think it is because of this:

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

borkdude13:09:48

in the package

borkdude13:09:59

nbb should find wrapper.mjs

🙌 1
borkdude13:09:41

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

borkdude13:09:53

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

Daniel Gerson13:09:24

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

Daniel Gerson13:09:42

@U04V15CAJ Well done for deducing this!

borkdude13:09:02

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

Daniel Gerson13:09:22

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 Gerson13:09:55

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

borkdude13:09:15

ok, pushing a solution now

borkdude13:09:30

(to github, not to npm yet)

borkdude13:09:51

publishing 0.7.135 with the fix

Daniel Gerson13:09:53

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

borkdude13:09:17

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

😂 1
borkdude13:09:35

should be available on npm now

🙌 1
borkdude13:09:23

Seems to work now

Daniel Gerson14:09:06

Confirmed it works! Incredible to watch you work.

Daniel Gerson14:09:22

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.

borkdude14:09:51

Glad to be of help

Daniel Gerson15:09:31

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.

borkdude15:09:14

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

Daniel Gerson15:09:24

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.

borkdude15:09:43

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

borkdude15:09:49

you can load nbb files from js like this:

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

Daniel Gerson15:09:31

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

borkdude15:09:42

you can put things in the global context like this:

(set! js/globalThis.foobar x)

borkdude15:09:08

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

Daniel Gerson15:09:52

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

Daniel Gerson15:09:03

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

borkdude16:09:03

@danielmgerson Ah in nbb you have to write:

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

👍 1