Fork me on GitHub
#clojurescript
<
2022-02-22
>
eploko08:02:29

Hey all, does the :target :bundle is only meant to be used for bundling code to be running in a browser? I'm trying to get a node script working with this, but after all is bundled node complains with:

out/index.bundle.js:848
window.CLOSURE_UNCOMPILED_DEFINES = { "cljs.core._STAR_global_STAR_": "global", "cljs.core._STAR_target_STAR_": "bundle" };
^

ReferenceError: window is not defined
    at Object.<anonymous> (out/index.bundle.js:848:1)

eploko08:02:53

My build.edn is this:

{:main hello.core
 :output-to "out/index.js"
 :output-dir "out"
 :target :bundle
 :closure-defines {cljs.core/*global* "global"}
 :browser-repl false}

eploko08:02:21

And I bundle and run it all with:

clj -M:cljs -co build.edn -v -c && npx esbuild ./out/index.js --bundle --platform=node --outfile=./out/index.bundle.js && node out/index.bundle.js

eploko08:02:34

Is it possible to tell the compiler to use, say, global instead of window in the output?

borkdude16:02:14

@micheal.ocathain Hey! I think the #babashka channel is more suited for this maybe :)

Mícheál Ó Catháin16:02:15

Of course it is - apologies all! Amazing tool @U04V15CAJ !

dnolen17:02:34

@eploko it's only for the browser - why do you need it for Node?

eploko23:02:53

I want to use an esm-only npm package in Electron and was going to bundle the resulting clojurescript with webpack/esbuild so that the esm imports are transpiled to cjs requires.

eploko23:02:08

If I use the :target :nodejs then the resulting JS uses a require to load an ESM dependency and that fails with Error [ERR_REQUIRE_ESM]: require() of ES Module blah-blah from main.js not supported. Instead change the require of blah-blah in main.js to a dynamic import() which is available in all CommonJS modules.

eploko23:02:51

This is all happening in the Electron main process, not the renderer, thus there's no window and the :bundle target doesn't quite fit the purpose as it stands now.

eploko23:02:16

I've got a sample repo with a minimal example here: https://github.com/eploko/cljsbuild-for-esm-node-lab

👀 1
kingcode18:02:28

Hello! I have a simple question regarding a specific case of extern’ing. I am able to use clojurescript to generate a single file which works great. Here is the simplistic cljs:

(ns part1-add.core)

(defn ^:export add [a b] (+ a b))
I then compile the project using advanced optimizations (with --target node) in order to generate a single file (main.js). After parsing/squinting, I can see that my exported function is now called ‘z’. I then paste the entire main.js at the bottom of a JS file which invokes my ‘z’ function and outputs it the result to my console - all great. My question is the following: is it possible to use some form of ‘extern’ declaration to protect the function add from being optimized so that it can be called as is? My two key needs are 1) a single file (working) and 2) protect one name..Sorry for my verbosity, and thanks for any comment.

p-himik18:02:02

That export should already be enough to prevent it from being renamed. I don't use Node myself so no clue here, but perhaps --target node somehow messes the export up.

dnolen18:02:26

--target node should have no effect as far as I'm aware

dnolen18:02:56

note that ^:export doesn't really prevent renaming - it just ensures that the desired exported name is also available

kingcode18:02:16

@U050B88UR and @U2FRKM4TW Thanks for your replies…I suppose I could try creating an externs file but don’t know what to point it at.

kingcode18:02:52

I am still new at cljs…alternatively, is there a way to do simple (no renaming) optimization into a single file?

dnolen18:02:28

you don't need to do anything

p-himik18:02:34

So do you really not have part1_add.core.add available from JS, at run time? Don't inspect JS code for that - try it in run time.

dnolen18:02:50

part1_add.core.add should exist

dnolen18:02:13

note that the lowering of - to _ since - is not valid in JS idents

kingcode18:02:32

@U050B88UR, oh hang on. I may simply have missed it…Will try again.

kingcode19:02:02

awrigh’! This worked:

clj -M -m cljs.main --target node --optimizations simple --output-to out/main.js -c part1-add.core

Thanks for your help! 

p-himik19:02:55

It should also work with --optimizations advanced though.

kingcode19:02:56

Ideally I would like to optimize away most of the stuff, but still be able to call part1_add.core.add as is - with simple optimization my file is large but still workable. Maybe I will find a way to extern the protected name away… Thanks again..

kingcode19:02:28

Unfortunately, part1_add.core.add is not made available in ’advanced

p-himik19:02:01

Did you confirm that by looking at the resulting main.js or did you actually check in run time?

kingcode19:02:27

I looked at main.js in both ’simple and ’advanced: it is easy to find in ’simple, but doesn’t exist in ’advanced. Instead, ‘advanced emits:

...function Ke(a,b){return a+b}var Le=["part1_add","core","add"],Me=aa;Le[0]in Me||"undefined"==typeof Me.execScript ...

p-himik19:02:18

Yeah, but now check it in run time. ;)

p-himik19:02:26

You will find that the function is there, I promise.

p-himik19:02:44

That line you quoted constructs the part1_add.core.add object path.

kingcode19:02:17

OK…let me try 🙂

kingcode19:02:34

How do I start a node prompt with main.js? Sorry, newbie here

p-himik19:02:38

$ node
Welcome to Node.js v16.13.1.
Type ".help" for more information.
> require('./out/main.js')
{}
> app.a_b.add(1, 2)
3

kingcode19:02:39

I send my js file to an external grader which runs it - that’s how I know which works/doesn’t

kingcode19:02:38

Yes, it works indeed! Cool. I will now try invoking the require inside the stub…hang on 🙂

kingcode19:02:21

The problem is, I need to invoke part1_add.core.add from the same file which generates the module. I will try adding to it

require('part1_add.core');

p-himik19:02:18

> same file which generates the module Not sure I follow. Is your JS file actually calling clj -M -m cljs.main yada-yada? Or what do you mean?

kingcode19:02:35

I copy-paste the contents of ‘optimized’ main.js into a js file. The js file has a pre-existing function which invokes part1_add.core.add function which should be made available by requiring’ ‘part1_add.core’?

kingcode19:02:01

Error: Cannot find module 'part1_add.core'

kingcode19:02:32

I am going trying to move the client code after the code that generates the module…

kingcode19:02:14

Is the require(…) only based on file path?

kingcode19:02:24

Then it won’t work for me.

p-himik19:02:46

> I copy-paste the contents of ‘optimized’ main.js into a js file. ... but why?

p-himik19:02:06

Your usage scenario seems to be drastically different from the usual ones for some reason.

kingcode19:02:30

I am submitting my file to an external grader on a remote site. The grader chews it up….Yes, indeed drastically different 🙂

kingcode19:02:45

The grader liked the ’simple optimization but not ’advanced

kingcode19:02:12

So was wondering if it is possible to require something generated in memory..

kingcode19:02:36

Thanks for your advice, it much appreciated…sorry for the trouble.

p-himik20:02:39

So you need to have a single JS file. Why not generate that file with CLJS in whole? You can require Node modules from CLJS code just fine.

kingcode20:02:44

OK, but I thought that’s what I was doing basically? main.js is it. I only paste its contents into the stub code that uses it.

kingcode20:02:20

Sorry if I am not following.

kingcode20:02:31

You mean from the repl prompt?

kingcode20:02:05

I must generate JS-only code which can be digested by other JS code (the stub)

kingcode20:02:10

The only node module I need is the one I generate (part1_add.core)

p-himik20:02:19

I see. And what's the stub?

kingcode20:02:28

var readline = require('readline');

process.stdin.setEncoding('utf8');
var rl = readline.createInterface({
  input: process.stdin,
  terminal: false
});

rl.on('line', readLine);

require( 'part1_add.core');

function readLine (line) {
  if (line !== "\n") {
    var a = parseInt(line.toString().split(' ')[0], 10);
    var b = parseInt(line.toString().split(' ')[1], 10);
   // console.log(z(a, b));
    console.log( part1_add.core.add(a,b));
    process.exit();
  }
}
//....main.js pasted here
The above works fine in ’simple

kingcode20:02:06

whoops, the ’require statement was added for advanced…my mistake, sorry

kingcode20:02:39

The above as is rejected by the grader

kingcode20:02:48

From searching online, it looks like ‘require’ is entirely path based.

kingcode20:02:43

It would be nice if an in-memory only module was possible - it doesn’t sound unreasonable

p-himik20:02:41

It should work as is because a JS file is first read and then evaluated - the order of definitions shouldn't matter. What error do you get when you use part1_add.core.add?

kingcode20:02:40

Error: Cannot find module 'part1_add.core'
Require stack:
- /tmp/tmp153tl8fn/part1_advanced-optimized.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:982:15)
    at Function.Module._load (internal/modules/cjs/loader.js:864:27)
    at Module.require (internal/modules/cjs/loader.js:1044:19)
    at require (internal/modules/cjs/helpers.j
...

kingcode20:02:34

Isn’t the module generated by writing to files from within main.js?

p-himik20:02:44

That's the error from require. What if you try without require?

p-himik20:02:05

main.js does not write any files. It's the file that represents a module.

kingcode20:02:37

I see…OK, I think I tried w/wo require in advanced….I’ll try again….hang on.

kingcode20:02:09

Unbelievable but true! I worked - I don’t know what changed with my earlier tries without the require ??

kingcode20:02:13

Thank you very much!

👍 1
kingcode20:02:07

Will have to learn the intricacies of CLJS compilation, externs and node.js - cool stuff 🙂