shadow-cljs

Daniel Leong 2025-02-08T01:20:44.370149Z

Bit of a follow up to https://clojurians.slack.com/archives/C6N245JGG/p1736127162988209; in a different project that has ESM dependencies, I'm able to get the project running with node nicely with :target :esm and :js-options {:js-provider :import}with "type": "module" in the package.json. However, I'm running into issues getting the tests to work. It seems like :target :node-test only wants to generate a common JS file, and because of the "type":"module" I can't run itβ€”even if I changed the package type, I wouldn't be able to access the ESM dependencies since I'm not using import . I was able to get pretty far with :target :esm-filesand creating a test runner by hand, eg:

(ns ci
 (:require [cljs.test :as test]
           [my.module-test]))

(test/run-all-tests #".*-test$")
but I'd really like to generate this file instead of having to remember to manually add all the test namespaces. I've got a build-hook that sorta works, but I'm not sure how to update build-state so that shadow-cljs will compile it in the same step. (If I just rerun the compile then it'll pick up the source file and compile it, but I'd like to have it all a single step) Is there a good way to add generated sources to the build-state? Or, is there a better way to do what I'm trying to do? Thanks!! πŸ™

thheller 2025-02-09T08:33:30.664159Z

I don't have any immediate thought on this, but let me break down how :node-test works

thheller 2025-02-09T08:34:49.445159Z

basically the build has a custom "resolve" stage. resolve in general being responsible for discovering which files need to be compiled in what order. so usually builds like :browser take :entries (or :init-fn) to know which namespace to start with and just follow the :require structure

thheller 2025-02-09T08:35:46.910639Z

instead :node-test starts out with to hardcoded :entries at all, and instead looks at the filesystem to find all files matching the regex -test

thheller 2025-02-09T08:36:27.616569Z

it then injects all those namespaces as extra dependencies for the test-runner namespace

thheller 2025-02-09T08:36:50.386279Z

beyond that is all just normal compilation

thheller 2025-02-09T08:38:21.767389Z

you cannot reliably do this via build hooks and you absolutely shouldn't modify :build-sources in :compile-prepare. that is too late and essentially asking for a lot of trouble πŸ˜›

thheller 2025-02-09T08:39:43.504449Z

as you can see there isn't much to the :node-test target at all. it would be entirely feasible to create a :esm-test target or something

thheller 2025-02-09T08:40:37.621729Z

I just have never written any kind of test setup using esm and I'm not entirely sure how I would go about doing that

thheller 2025-02-09T08:41:00.371129Z

:node-test falls back on :node-script to get the actual output of the same structure

thheller 2025-02-09T08:41:19.248489Z

:esm on node needs some work overall, so not a 1:1 conversion

thheller 2025-02-09T08:42:48.133249Z

generating the test file might be the best option, but you shouldn't do it from the hook. just generate it before even starting the build

thheller 2025-02-09T08:44:13.428339Z

https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/build/test_util.clj it isn't much code and when not constrained with how the build-state works could be much simpler

πŸ™ 1
Daniel Leong 2025-02-08T21:58:57.674569Z

https://github.com/dhleong/saya/pull/4 is now functional but I imagine there's a better way to do this. Instead of outputting saya.ci.js it's just outputting .js πŸ€”

Daniel Leong 2025-02-09T00:32:52.212009Z

Ah okay, adding :ns to my resource fixed that filename issue. Now getting a warning about failing to write cache due to "no immediate deps" but at least everything is basically working as expected

Daniel Leong 2025-12-15T02:18:23.795549Z

@thheller I finally had some time to revisit my cursed hack here and https://github.com/dhleong/saya/blob/1ada10950e9d1863522a0f4e6229552d0e6c03e4/src/hooks/shadow/build/targets/node_esm_test.clj a :node-esm-test build target that seems to work fine for my purposes. Not sure whether this would be something you'd be interested in cleaning up and pulling into the main repo, and also curious whether you have some insight into what I ran into https://github.com/dhleong/saya/blob/1ada10950e9d1863522a0f4e6229552d0e6c03e4/src/hooks/shadow/build/targets/node_esm_test.clj#L17 where I needed to set :js-provider both at the top level "state" and under ::build/config as expected πŸ™

thheller 2025-12-15T08:00:35.932999Z

:esm has a :runtime default of :browser, since you are running node it would be correct to set :runtime :node in the build config. that addresses the WebSocket global problem. for tests I need to think about it for a bit.