Fork me on GitHub
#shadow-cljs
<
2021-08-05
>
thheller06:08:14

@borkdude if you want react to be optional you'd need to use the same createRequire path you used for the intepreter

borkdude06:08:46

I tried this, but the builtin reagent doesn’t seem to be satisfied if I require react myself this way before loading reagent itself

borkdude06:08:24

I mean, it seems it still wants to load react relative to nbb itself instead of relative to the script

thheller06:08:12

yeah thats what I mean. if you require it from the ns it'll be relative to the source itself

thheller06:08:39

so the global install. if you instead require it dynamically via createRequire it should be fine

thheller06:08:14

IIRC there is also an enviroment variable NODE_PATH or NODE_INLUDE_PATH or something like that

thheller06:08:36

maybe you can find a way with that. needs to be set before starting node though so might be tricky

borkdude06:08:15

If you require it from the ns… doesn’t Reagent itself require react from the ns? How can I make that use createRequire?

thheller06:08:15

oh right. forgot that you are not in control of that require

thheller06:08:38

did you experiment with a simple node script that creates a secondary process actually running nbb? that way you can control the NODE_PATH env or whatever. assuming thats actually still a thing

borkdude06:08:52

I didn’t do that yet, but this may be the most flexible solution, if it works. I guess it could also just be a bash script

borkdude08:08:32

So I guess I'll have to rename my bin to nbb-runner and expose an additional nbb script which is bash on linux/macOS and .cmd or so on Windows with:

#!/usr/bin/env bash

NODE_PATH=$NODE_PATH:$PWD/node_modules nbb-runner $@

borkdude08:08:04

or actually, not PWD, but the node_modules relative to the script rather

thheller08:08:19

pretty sure you must not specify the node_modules

borkdude08:08:34

I did have to do that

thheller08:08:39

default node resolve rules will append that anyways?

borkdude09:08:44

It seems you can "hack" using module.constructor._initPaths which, if this isn't _too_ hacky, is my preferred solution as this makes deploying stuff much easier

thheller09:08:26

seems reasonable

borkdude09:08:04

do you mean the module.constructor hack?

borkdude11:08:15

yeah, might work. I tried to incorporate this into nbb.main but it complained about module not being available in ESM module scope

thheller11:08:10

pretty sure thats the "module" require?

borkdude11:08:35

I just wrote (js/module.constructor._initPaths) etc

borkdude11:08:52

I didn't require "module" explicitly (but I think I have done this in the past and that failed as well)

thheller11:08:39

require('module').Module._initPaths()

borkdude11:08:17

you mean via the createRequire?

borkdude11:08:31

there I think I need import.meta.url to pass it to the createRequire

borkdude11:08:40

and google closure won't allow this in advanced compilation :/

thheller11:08:47

yo do not need that

thheller11:08:19

you can require that normally in your code

borkdude11:08:46

but require doesn't work in this ESM module

borkdude11:08:25

I can't write (js/require ...), that is not allowed

thheller11:08:33

(:require ["module" :as m]) (m/_initPaths)

borkdude11:08:42

let me try that

thheller11:08:00

the .Module seems to be a circular reference back to itself so you can skip that

thheller11:08:17

of course you need to add the path first

thheller11:08:24

just calling init alone won't do anything

borkdude11:08:31

I understand

borkdude11:08:39

$ node out/nbb_main.js script.cljs
file:///Users/borkdude/git/nbb/out/cljs-runtime/nbb.core.js:3
import$module._initPaths();
^

ReferenceError: import$module is not defined

borkdude11:08:47

this might be because it's not a CommonJS module?

borkdude11:08:10

I've found that function yes

borkdude11:08:25

it exists in the Node REPL etc

thheller11:08:53

wait WHERE did you require that

borkdude11:08:00

in nbb.main

thheller11:08:00

in the script.cljs or in nbb code?

thheller11:08:23

and this was a regular compile not some REPL hot-reload or so?

thheller11:08:38

should be fine? don't see a reason why it wouldn't be

borkdude11:08:47

I've tried regular compile, advanced, both same error

thheller11:08:28

probably running into that one 😛

borkdude11:08:16

shim on you

thheller11:08:29

hmm no wait that should be fine

thheller11:08:38

import$module is the correct name. dunno why its not defined

borkdude11:08:05

I've seen errors like "module is not defined in import / ESM scope" before somewhere, can't recall exactly when

thheller11:08:29

this is not that. you are importing it. so it is defined.

borkdude11:08:45

yeah ok. I could try JS interop directly after import

thheller11:08:08

this is pointless

(when require
      (set! (.-require goog/global) require))

thheller11:08:16

there is no global require and there shouldn't be

borkdude11:08:36

it is not pointless since this makes (js/require "foo") work in the interpreted code

borkdude11:08:16

ah gotcha. when using direct JS interop:

$ node out/nbb_main.js script.cljs
file:///Users/borkdude/git/nbb/out/cljs-runtime/nbb.core.js:3
module["constructor"]._initPaths();
^

ReferenceError: module is not defined in ES module scope

borkdude11:08:01

@U05224H0W that :require require is passed to the namespace evaluation logic, but users can also do interop on the global object directly

thheller11:08:12

get rid of the constructor thats pointless

thheller11:08:43

and yes module without a require or import doesn't exist. that is expected.

thheller11:08:50

thats why you import it

borkdude11:08:53

$ node out/nbb_main.js script.cljs
file:///Users/borkdude/git/nbb/out/cljs-runtime/nbb.core.js:3
module._initPaths();
^

ReferenceError: module is not defined in ES module scope

thheller11:08:03

you need to differentiate about what module you are talking about

thheller11:08:24

the implicit module you get in commonjs code is different from the one you get via the "module" module

thheller11:08:36

so how you modify that is different depending on how you work with it

thheller11:08:11

so the implicit module you'd go via .constructor or .Module the others do not

thheller11:08:59

don't have time now but maybe I can figure this out over the weekend

thheller11:08:31

there have been a bunch of changes in the closure compiler regarding this as well so maybe the whole shadow.esm/dynamic-import stuff isn't necessary anymore

borkdude12:08:01

This is the entire output btw:

$ node out/nbb_main.js script.cljs
file:///Users/borkdude/git/nbb/out/cljs-runtime/nbb.core.js:3
cljs.core.prn.cljs$core$IFn$_invoke$arity$variadic(cljs.core.prim_seq.cljs$core$IFn$_invoke$arity$2([module], 0));
                                                                                                     ^

ReferenceError: module is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/Users/borkdude/git/nbb/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///Users/borkdude/git/nbb/out/cljs-runtime/nbb.core.js:3:102
    at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
    at async Loader.import (node:internal/modules/esm/loader:178:24)
    at async Object.loadESM (node:internal/process/esm_loader:68:5)
    at async handleMainPromise (node:internal/modules/run_main:63:12)
(I just realized I left out the interesting bit perhaps)

borkdude12:08:15

(this is from (prn js/module))

borkdude12:08:05

which is not right as I should be using m obviously

thheller12:08:35

yes, js/module is not supposed to exist

borkdude12:08:36

This gives:

(prn m)
(prn (._initPaths m))
#object[Object]
nil

thheller12:08:53

so it works fine?

borkdude12:08:17

no, the nil shows that _initPaths is not there

thheller12:08:43

hmm yeah its calling the function and that returns nil? seems fine?

borkdude12:08:36

indeed, the function seems to exist...

borkdude12:08:47

ok, I'm going to try some stuff now that this seems ok

borkdude12:08:30

seems to work :)

borkdude12:08:14

crap, I think I was too early. it probably picked up a "react" from an earlier install in node_modules. it seems npm remove -g nbb doesn't clear that

borkdude12:08:20

it doesn't seem to work, alas

borkdude12:08:31

perhaps the dynamic import doesn't pick up on the NODE_PATH stuff

borkdude12:08:55

or the way how reagent requires react

thheller12:08:13

I thought for the interpreted code you are setting up the require via createRequire?

thheller12:08:29

I mean just don't use import in any interpreted code?

borkdude12:08:28

ok, to tease things apart a bit: my nbb.core namespace is compiled as an ESM module my nbb.reagent namespace is compiled as an ESM module why? because code splitting only works with ESM modules currently nbb.core loads nbb.reagent on demand. the only way this works (I think) is via dynamic import (ESM modules) Then nbb.reagent loads a pre-compiled version of reagent. This doesn't run through the interpreter, it is pre-compiled. So that's just normal CLJS requires happening in reagent.

thheller12:08:26

ah right. hmm yeah no idea about the ESM part

borkdude12:08:02

The way it is solved currently is to just have an optional dependency on react

borkdude12:08:33

so when people load reagent, reagent loads the react dependency from nbb

thheller06:08:05

but then you can't use the ns require for the parts shadow-cljs builds

borkdude20:08:36

lol:

$ npx shadow-cljs --force-spawn test modules
shadow-cljs - config: /Users/borkdude/git/nbb/shadow-cljs.edn
shadow-cljs - starting via "clojure"
TBD
I was wondering how to test my ;target :esm build.

borkdude20:08:07

I could just compile it and then use another test runner (I've used cljs-test-runner before and that worked fine)

thheller20:08:54

just use :target :node-test?

borkdude20:08:18

will this work together with :target :esm somehow? I don't know really where to start

borkdude20:08:00

$ npx shadow-cljs compile test
shadow-cljs - config: /Users/borkdude/git/nbb/shadow-cljs.edn
shadow-cljs - starting via "clojure"
[:test] Compiling ...
========= Running Tests =======================
file:///Users/borkdude/git/nbb/target/test/test.js:5
var SHADOW_IMPORT_PATH = __dirname + '/../../.shadow-cljs/builds/test/dev/out/cljs-runtime';
                         ^

ReferenceError: __dirname is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/Users/borkdude/git/nbb/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///Users/borkdude/git/nbb/target/test/test.js:5:26
    at file:///Users/borkdude/git/nbb/target/test/test.js:1571:3
    at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
    at async Loader.import (node:internal/modules/esm/loader:178:24)
    at async Object.loadESM (node:internal/process/esm_loader:68:5)
    at async handleMainPromise (node:internal/modules/run_main:63:12)

borkdude20:08:05

That's what I was fearing

thheller20:08:47

I always recommend using a separate output directory per build. don't mix them

thheller20:08:10

so you output the esm stuff to packages/nbb with its own package.json in that dir

thheller20:08:19

and tests whereever but not in that dir

thheller20:08:46

that way you also never have to worry about including files in the published package that shouldn't be there (eg. cljs sources)

thheller20:08:19

or create a test-out with its own package.json. really up to you

thheller20:08:50

or just output-to to a .cjs file like the error suggests. many possible solutions

borkdude20:08:12

but isn't the point of the tests that they can load my app code?

borkdude20:08:43

trying .cjs

thheller20:08:48

the compiled version no, your namespaces sure

borkdude20:08:48

is it possible to add a src dir test only for the test build?

borkdude20:08:07

I am using :deps true

borkdude20:08:37

something like :aliases [:test] on the level of the build for example which would pull in :extra-paths ["test"]

thheller06:08:53

just put it always on. it doesn't affect your builds in any way.

borkdude07:08:40

I'm using :deps true but I'll use :deps {:aliases [:test]} then

thheller20:08:07

tests are separate builds. there is no special test target for :esm