shadow-cljs

Shantanu Kumar 2025-05-11T08:07:44.478789Z

Hi. Has anyone else run into this issue with shadow-cljs 3.0.x versions?

[:app] Build failure:
The required JS dependency "process" is not available, it was required by "node_modules/react/cjs/react.production.js".

Dependency Trace:
	app/core.cljs
	uix/core.cljs
	node_modules/react/index.js
	node_modules/react/cjs/react.production.js
I am using Java 21 and Node 22.15.0 - I have tried moving react, react-dom from dev to regular dependencies as per https://shadow-cljs.github.io/docs/UsersGuide.html#_missing_js_dependency but still get this error.

thheller 2025-05-11T08:10:07.255579Z

I removed the automatic polyfill for node built-in packages. still need to figure out a better automatic handling, until then npm install process

👍🏽 1
p-himik 2025-05-11T13:30:08.440419Z

Just encountered a similar error, I think. Given the most recent commit that brings back some polyfills, maybe it makes sense to just include all of them? My error in particular is about the fs package when building antlr4@4.8.0.

thheller 2025-05-11T13:31:22.459839Z

no I removed the automatic polyfilling on purpose

p-himik 2025-05-11T13:31:48.404769Z

Hmm, and I still can't run the build even after npm i fs:

package in /home/p-himik/[...]/fs specified entries but they were all missing

thheller 2025-05-11T13:31:54.609059Z

webpack removed it 5 years ago. it is time. packages are not supposed to rely on this anymore. you can still all do it manually. just doing it for process because its so common

p-himik 2025-05-11T13:32:41.514809Z

But if someone uses React with Webpack, they still don't have to add polyfills manually, or do they?

thheller 2025-05-11T13:34:03.618879Z

I don't really care. I wanted to get it of it and I did. I only added process back because I need to make the detection stuff I have smarter

thheller 2025-05-11T13:35:16.717069Z

} else if (
                "object" === typeof process &&
                "function" === typeof process.emit
              ) {
                process.emit("uncaughtException", error);
                return;
              }

thheller 2025-05-11T13:35:28.524149Z

this is the bit that makes my detection think process is required. it isn't really ...

p-himik 2025-05-11T13:35:45.666569Z

I'm just perplexed because it makes shadow-cljs 3.x break for apparently no reason other than "webpack did it", without enabling the same convenience that webpack offers (I assume). So shadow-cljs 3 users have to do more work than webpack users and than shadow-cljs 2 users.

thheller 2025-05-11T13:36:25.691509Z

that is nonsense

thheller 2025-05-11T13:36:41.500069Z

just build your antlr4 with webpack5+ and get back to me

thheller 2025-05-11T13:36:54.453349Z

if it works without manual extra config I'll think about adjusting what shadow-cljs does

thheller 2025-05-11T13:37:14.150179Z

everything that was previously done is still available. just requires manual config

thheller 2025-05-11T13:37:51.254399Z

https://github.com/webpack/node-libs-browser

thheller 2025-05-11T13:38:13.629389Z

please note the "archived" status and deprecated note.

p-himik 2025-05-11T13:39:11.803939Z

Ah, OK, so apparently it doesn't work in Webpack: https://stackoverflow.com/a/41622685/564509 Is there then an analog to fs: "empty", so I don't have to install a random polyfill?

thheller 2025-05-11T13:39:26.014969Z

node-libs-browser installs a bunch of nonsense packages that 99% of shadow-cljs users likely will never need. process is the exception because of react, otherwise that wouldn't be back either.

thheller 2025-05-11T13:39:39.558079Z

:js-options {:resolve {"fs" false}}

thheller 2025-05-11T13:40:07.507699Z

and again one reason that shows why still having the polyfills is bad 😛 there are better options that don't add nonsense polyfills

p-himik 2025-05-11T13:41:35.783729Z

That worked, thanks!

p-himik 2025-05-11T12:34:49.883989Z

Is there a way to prevent JS files from an NPM dependency from being changed by GCC more than absolutely necessary? I need to debug something and source maps are a PITA and the underlying JS files are also not great given all the rearrangement and variable reuse.

p-himik 2025-05-11T12:39:22.771439Z

As an example, this is a function from node_modules/abcjs/src/write/interactive/selection.js:

function getMousePosition(self, ev) {
	// if the user clicked exactly on an element that we're interested in, then we already have the answer.
	// This is more reliable than the calculations because firefox returns different coords for offsetX, offsetY
	var x;
	var y;
	var box;
	var clickedOn = findElementInHistory(self.selectables, getTarget(ev.target));
	if (clickedOn >= 0) {
		// There was a direct hit on an element.
		box = getBestMatchCoordinates(self.selectables[clickedOn].svgEl.getBBox(), ev, self.scale);
		x = box[0];
		y = box[1];
		//console.log("clicked on", clickedOn, x, y, self.selectables[clickedOn].svgEl.getBBox(), ev.target.getBBox());
	} else {
		// See if they clicked close to an element.
		box = getCoord(ev);
		x = box[0];
		y = box[1];
		clickedOn = findElementByCoord(self, x, y);
		//console.log("clicked near", clickedOn, x, y, printEl(ev.target));
	}
	return { x: x, y: y, clickedOn: clickedOn };
}
And this is the same function from :
function getMousePosition(self, ev) {
    var clickedOn;
    a: {
      var y = self.selectables;
      if (clickedOn = getTarget(ev.target)) {
        for (var i = 0; i < y.length; i++) {
          if (clickedOn.dataset.index === y[i].svgEl.dataset.index) {
            clickedOn = i;
            break a;
          }
        }
      }
      clickedOn = -1;
    }
    if (0 <= clickedOn) {
      y = self.selectables[clickedOn].svgEl.getBBox(), y = y.x <= ev.offsetX && y.x + y.width >= ev.offsetX && y.y <= ev.offsetY && y.y + y.height >= ev.offsetY ? [ev.offsetX, ev.offsetY] : 3 > Math.abs(ev.layerY / self.scale - ev.offsetY) ? [ev.offsetX, ev.offsetY] : [ev.layerX, ev.layerY], ev = y[0], y = y[1];
    } else {
      clickedOn = y = 1;
      var svg = ev.target.closest("svg");
      i = 0;
      svg && svg.viewBox && svg.viewBox.baseVal && (0 !== svg.viewBox.baseVal.width && (y = svg.viewBox.baseVal.width / svg.clientWidth), 0 !== svg.viewBox.baseVal.height && (clickedOn = svg.viewBox.baseVal.height / svg.clientHeight), i = svg.viewBox.baseVal.y);
      ev.target && "svg" === ev.target.tagName ? (svg = ev.offsetX, ev = ev.offsetY) : (svg = ev.layerX, ev = ev.layerY);
      y = [svg * y, ev * clickedOn + i];
      ev = y[0];
      y = y[1];
      clickedOn = ev;
      i = y;
      svg = 9999999;
      for (var closestIndex = -1, i$jscomp$0 = 0; i$jscomp$0 < self.selectables.length && 0 < svg; i$jscomp$0++) {
        var el = self.selectables[i$jscomp$0];
        self.getDim(el);
        if (el.dim.left < clickedOn && el.dim.right > clickedOn &&  < i && el.dim.bottom > i) {
          closestIndex = i$jscomp$0, svg = 0;
        } else if ( < i && el.dim.bottom > i) {
          var horiz = Math.min(Math.abs(el.dim.left - clickedOn), Math.abs(el.dim.right - clickedOn));
          horiz < svg && (svg = horiz, closestIndex = i$jscomp$0);
        } else {
          el.dim.left < clickedOn && el.dim.right > clickedOn ? (horiz = Math.min(Math.abs( - i), Math.abs(el.dim.bottom - i)), horiz < svg && (svg = horiz, closestIndex = i$jscomp$0)) : (horiz = Math.abs(clickedOn - el.dim.left) > Math.abs(clickedOn - el.dim.right) ? Math.abs(clickedOn - el.dim.right) : Math.abs(clickedOn - el.dim.left), el = Math.abs(i - ) > Math.abs(i - el.dim.bottom) ? Math.abs(i - el.dim.bottom) : Math.abs(i - ), horiz = Math.sqrt(horiz * horiz + 
          el * el), horiz < svg && (svg = horiz, closestIndex = i$jscomp$0));
        }
      }
      clickedOn = 0 <= closestIndex && 12 >= svg ? closestIndex : -1;
    }
    return {x:ev, y, clickedOn};
  }

thheller 2025-05-11T13:15:26.096899Z

not really no. its just :simple optimizations. so guess its doing a lot inlining

p-himik 2025-05-11T13:20:32.085749Z

Huh. But it's a dev build - why was :simple used instead of :none?

thheller 2025-05-11T13:22:14.317679Z

because :none is not a valid closure compiler option. :whitespace caused issues in the past, so :simple was really the only option

thheller 2025-05-11T13:23:02.867459Z

and simple is smart enough to filter out nonsense like this react pattern

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

thheller 2025-05-11T13:23:40.244139Z

not that it matters much during dev in this case but in some packages it did

p-himik 2025-05-11T13:24:11.718809Z

And there's no way to fine-tune :simple to tell GCC to avoid inlining and variable reuse, is there?

thheller 2025-05-11T13:25:43.482919Z

:js-options {:variable-renaming :off :property-renaming :off} should work

thheller 2025-05-11T13:25:48.282809Z

inlining I don't think so. if so its not currently exposed as a mapping from the config, but it would be easy to add if you find one https://github.com/thheller/shadow-cljs/blob/eb7613c0368d41dbc173852f69d14ac73876caff/src/main/shadow/build/closure.clj#L205

p-himik 2025-05-11T13:27:25.812529Z

Thanks!

p-himik 2025-05-11T13:54:59.323759Z

Hmm, it seems that it's easier to just put a bunch of print statements in this case. I tried doing this:

:resolve        {"fs"                      false
                 "./interactive/selection" {:target :file
                                            :file   "src/selection.js"}}
But it results in require-from is missing package info. Any way I could achieve that?

p-himik 2025-05-11T13:56:03.852219Z

Ah, never mind - the original file had relative imports that I have to update.

thheller 2025-05-11T13:57:24.417719Z

"fs/interactive/selection" would work. relative only works in context of what it is relative to

p-himik 2025-05-11T13:59:28.131049Z

That brings up another issue - changing the copied JS file does trigger a build, but the build uses the old version of the file. The current content starts with

var spacing = require('abcjs/src/write/helpers/spacing');
But shadow-cljs complains with
The required JS dependency "abcjs/helpers/spacing" is not available, it was required by "src/selection.js".
And that was the previous version of the string. > "fs/interactive/selection" would work. relative only works in context of what it is relative to (edited) Nah, it's not about fs anymore - "./interactive/selection" comes from abcjs verbatim, and the provided alternative seems to be picked up just fine.

thheller 2025-05-11T14:00:12.522149Z

I'm confused. are you asking about something a library is doing internally?

thheller 2025-05-11T14:00:27.972649Z

> changing the copied JS file does trigger a build

thheller 2025-05-11T14:00:36.585219Z

what does that mean? who copied what where?

p-himik 2025-05-11T14:03:44.573329Z

Yes, the fs bit came from a different thread - completely irrelevant here. I'm trying to debug abcjs, and currently it seems that the easiest way to do it is to fill the relevant functions with console.log statements. All those functions are in node_modules/abcjs/src/write/interactive/selection.js. That file is required in abcjs only once, as var setupSelection = require('./interactive/selection');. So I guess that adding a :resolve entry for "./interactive/selection" should work. And indeed it does - I copied the original file to src in my project and all I had to do was to adjust the require statements in there. The "another issue" that I mentioned is that while changing that copied src/selection.js does trigger the build, the built itself uses the old content of that file - the file is not re-read.

thheller 2025-05-11T14:04:16.273429Z

ah, so the :resolve above actually does something? that surprises me 😛

thheller 2025-05-11T14:04:25.095909Z

and no, this isn't a supported use case

p-himik 2025-05-11T14:04:40.300409Z

Huh? It's documented. :) https://shadow-cljs.github.io/docs/UsersGuide.html#js-resolve-npm

thheller 2025-05-11T14:05:18.325769Z

consider :file something that has literally never been used or tested 😛 let alone for replacing some internal npm package file

thheller 2025-05-11T14:06:27.142269Z

if you want to debug a npm package do it with their build setup and tooling. I don't consider that something shadow-cljs is suitable for

p-himik 2025-05-11T14:08:57.073489Z

The thing is, I cannot reproduce the issue on their setup and tooling. I can only reproduce it in my CLJS project. > consider :file something that has literally never been used or tested 😛 Well, it works. Apart from hot reload. > let alone for replacing some internal npm package file Assuming it works exactly as it is documented to work - for NPM packages and not internal files - and it also doesn't get hot reloaded on changes to the specified .js file, would you say that fixing that part makes sense? If so, I would assume that it would also automatically fix my minor issue, even if it's unsupported.

thheller 2025-05-11T14:10:27.801079Z

there is no hot-reload for npm packages, partial or otherwise. this is still a commonjs file, so it will not work with hot-reload. so that is working as intended I guess 😛

p-himik 2025-05-11T14:13:47.044469Z

Oh! A correction - I'm not talking about hot reload in the sense "the web page should automatically pick up the changes". I remember now that we've discussed it a few years ago, when I had to make some JS files a part of the same CLJS project. But in this case the problem is with using the actual JS file content during the build - hot code reload notwithstanding. So I can't even fix it by refreshing the web page, as I could with other JS files. I have to restart the whole watch process.

thheller 2025-05-11T14:15:05.569729Z

I do consider :resolve {"./something/relative" ...} working a bug. that should not work, so if anything I'd fix that first

p-himik 2025-05-11T14:15:29.419979Z

Please don't! :D

thheller 2025-05-11T14:15:49.605349Z

relying on that is crazy dangerous and problematic

p-himik 2025-05-11T14:16:01.982739Z

I am relying on it solely for debugging purposes though.

thheller 2025-05-11T14:16:13.882339Z

it replaces for everything. so if any other package also happens to have that exact relative require it would also replace that

thheller 2025-05-11T14:16:20.514679Z

unlikely I know, but still

thheller 2025-05-11T14:16:34.368279Z

why not just change the original file? why jump through this hoop?

p-himik 2025-05-11T14:17:30.754649Z

Right in node_modules? I guess it makes sense now that I have to restart the build process anyway. But if I didn't have to restart it, I would definitely prefer to use :resolve, regardless of the potential danger.

thheller 2025-05-11T14:17:35.850589Z

you could use :js-package-dirs ["mine" "node_modules"]. then have mine/abcjs with the modifications and the original in node_modules

p-himik 2025-05-11T14:18:15.239529Z

Oh, alright, that's definitely better than the hassle with changing the original file. Although I assume I would still have to restart the whole build process on any change.

thheller 2025-05-11T14:18:36.952229Z

touching mine/abcjs/package.json should be enough. shadow still watches that to detect installs of new versions

p-himik 2025-05-11T14:18:48.269459Z

Checking...

thheller 2025-05-11T14:19:19.019589Z

node_modules/abcjs/packages.json as ewll of course

p-himik 2025-05-11T14:21:05.369369Z

Why is the latter required? I thought that abcjs would fully come from "mine" with that config.

p-himik 2025-05-11T14:24:40.498479Z

> you could use :js-package-dirs ["mine" "node_modules"]. then have mine/abcjs with the modifications and the original in node_modules Did not work. The modifications did not get picked up, so I assume the original version was used.

p-himik 2025-05-11T14:29:33.405599Z

And neither of these commands triggered a build:

touch mine/abcjs/package.json
touch node_modules/abcjs/package.json

p-himik 2025-05-11T14:37:30.541979Z

My bad - I put :js-package-dirs outside of :js-options. The :js- prefix confused me.

p-himik 2025-05-11T14:39:34.915859Z

The changes are picked up but touch still doesn't trigger a build.

p-himik 2025-05-11T13:48:05.549429Z

Maybe I'm doing something wrong but it seems that shadow-cljs watch is basically incompatible with editors that write to disk without a user having to explicitly save. Whenever I change shadow-cljs.edn and switch to some other window (e.g. to the reference to look up some option), there's always a risk of hitting something like Map literal must contain an even number of forms, after which the shadow-cljs watch process seemingly stops watching for the config changes.

thheller 2025-05-11T13:49:07.397169Z

works fine for me

thheller 2025-05-11T13:50:23.653109Z

ah wait nvm. just tried for normal files

thheller 2025-05-11T13:50:46.192549Z

yeah the shadow-cljs.edn watcher is missing a try/catch I guess

Crispin 2025-05-11T14:31:48.131729Z

Hi, does shadow elide docstrings in production :target :browser builds? What about other meta? Is there anything like clojure's :elide-meta ?

thheller 2025-05-11T14:43:34.562649Z

metadata of vars only exists in the compiler side, so clj. it does not exist in JS, so nothing to elide

thheller 2025-05-11T14:44:10.270609Z

so, its as if :elide-meta is always on, even for dev builds I guess

thheller 2025-05-11T14:44:51.816109Z

only exception is using actual #'foo vars in your code. in that case nothing is elided at all

Crispin 2025-05-11T14:45:48.229009Z

ok great, that's what I wanted! Thanks for confirming.

Crispin 2025-05-11T14:32:54.207749Z

cant find anything in the docs about it, only about :elide-asserts