shadow-cljs

juhoteperi 2026-04-27T08:36:10.964189Z

If I read the docs correctly, there is currently no support for ESM output (`:target :esm`) to "dynamically" output esm files, like :target :npm-module with :ns-regexp works? I was looking at the options to export modules for StoryBook, their recommended default builder is VIte which works with ESM. But I could also just keep using CJS with npm-module target and use Webpack instead.

thheller 2026-04-27T08:37:00.938919Z

:target :esm-files works like :npm-module and outputs separate files per ns

juhoteperi 2026-04-27T08:38:52.443459Z

Ah right, experimental feature mentioned on the changelog. I though I had seen it somewhere.

thheller 2026-04-27T08:39:36.490449Z

last time I checked storybook heavily relied on static analysis of the files and didn't understand cljs output at all

thheller 2026-04-27T08:40:07.280439Z

so not sure that'll work

juhoteperi 2026-04-27T08:41:30.393639Z

We have a version with Webpack that uses a custom storybook "indexer" code to parse Shadow-cljs output, that version at least works

juhoteperi 2026-04-27T09:06:34.376629Z

Seems like I could implement similar indexer for the ESM files, but it isn't any simpler with the ESM files, even more complex than parsing the npm-module output, as there is the additional ESM "wrapper" file.

thheller 2026-04-27T09:33:23.212869Z

what are you parsing in the first place?

thheller 2026-04-27T09:34:26.336499Z

I have never used storybook so I'm not really sure whats required these days. some time ago I did a prototype that just used the clojure metadata from the build state to write some boilerplate code

thheller 2026-04-27T09:34:42.801439Z

dunno where that went but seems like that might be useful

juhoteperi 2026-04-27T10:33:38.852569Z

Recent StoryBooks wants a default export which should be a JS object, and should have a certain properties which it can statically analyze. And by default it is looking for any exported values in the file to use as stories.

juhoteperi 2026-04-27T10:34:31.601649Z

Current impl I have from another project, does some regex magic to find module\.exports or Object\.defineProperty calls etc, and finds out a string value which should be the "title" for the file.

juhoteperi 2026-04-27T10:35:23.698399Z

I could probably improve it a bit by emitting correct JS objects from Cljs. But the :esm-files output doesn't work directly because the default export is a reference to the cljs-runtime file, and not a JS object itself.

juhoteperi 2026-04-27T10:52:46.644239Z

... and some custom Webpack loader thingie to "fix" the default property in :npm-module code...

juhoteperi 2026-04-29T10:20:53.446749Z

I did end up writing at least shorter solution, in my opinion quite a bit cleaner also, no need to parse JS code with regexes. Our defmeta and defstory macros store the required metadata to compiler state under the namespace, and then we have build hook which takes this metadata and writes one or per namespace .stories.json files to the output-dir. Then we have really simple indexer for StoryBook which just reads these json files as is. The files just need the path of the JS file and name of the JS variable in that file, per story. With this version, we can use :esm-files output and probably pretty much any JS bundler. StoryBook doesn't care much as long as the refered JS file and variable exists.

juhoteperi 2026-04-29T10:22:03.064899Z

The only workaround I needed, was to enable https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/build/targets/npm_module_goog_overrides.js for esm-files output. HMR broke on StoryBook without the goog.provide version here.

juhoteperi 2026-04-29T10:23:18.790819Z

Might be nice to have a public example or repo for the solution... I might ask the client if we can post it somewhere.

juhoteperi 2026-04-29T10:24:26.269109Z

I did consider also "full" storybook addon, similar approach as the official plugins due for Vue, Svelte etc, where the StoryBook indexer does "a proper parse" of the Vue etc. source file to find out the story definitions for the metadata.

juhoteperi 2026-04-29T10:25:06.923789Z

But definitely less work to just handle it in the macros, and there is a benefit that if we had a macro generating more defstory macro calls, this still should end up generating correct metadata.

thheller 2026-04-29T14:53:32.929379Z

could be useful to have a :target :storybook impl that just does all this with minimal config. happy to help set that up if want/can

juhoteperi 2026-04-29T14:59:14.125769Z

Could be. This version works for the StoryBook "frontend", but I hear the MCP server / AI integration might require even more setup. Not sure if or how that works together with this approach.

thheller 2026-04-29T15:00:40.012019Z

what if you just generated .js files instead? I mean its just boilerplate right?

thheller 2026-04-29T15:01:17.340689Z

well, I tried a long time ago and gave up getting tired fighting storybook. I doubt that has changed much šŸ˜›

juhoteperi 2026-04-29T15:01:40.388109Z

I think they have rewritten the whole thing like once or twice a year with every major version \o/

juhoteperi 2026-04-29T15:03:39.544999Z

If the support was with-in Shadow-cljs, I think it would be quite possible to emit JS files with correct boilerplate

thheller 2026-04-29T15:04:39.801139Z

I don't mean actual "compilation". I just mean some random JS by bashing some strings together

thheller 2026-04-29T15:05:15.837629Z

(str "export default " (json/write-str the-metadata)) type of thing

juhoteperi 2026-04-29T15:05:18.631589Z

Hmm true. Instead of writing the JSON in build-hook, just do the same but write it as valid JS.

juhoteperi 2026-04-29T15:06:17.653909Z

It should remove the need for custom indexer config in storybook (which was very short for the json files anyway.)

pez 2026-04-27T15:09:07.801679Z

This has started to happen intermittently in Calva CI. I don’t know if it is CircleCI or something with shadow-cljs or my config. • The step building the calva and test targets exits with 0 exit status before the test target is built. • I have added a step to catch the error early. ā€œintermittentlyā€ here is about 50% of the times. So most often I can Re-run failed jobs, and it passes. https://app.circleci.com/pipelines/github/BetterThanTomorrow/calva/9937 ā€œstarted to happenā€ is: happened first tome 25 days ago. I don’t know what I could do to figure out more. I fail to reproduce the problem locally. Anyone have a clue/suggestion?

thheller 2026-04-27T15:19:44.632999Z

thats usually the shadow-cljs getting killed by the OS/VM usually for reaching some kind of memory limit

thheller 2026-04-27T15:20:46.976349Z

try setting a memory limit via deps.edn :jvm-opts

šŸ™ 1
pez 2026-04-29T09:50:14.328259Z

Setting a memory limit via :jvm-opts was indeed the solution. CI hasn’t croaked on this thing once in the 25 or so runs since. Thanks! šŸ™ ā¤ļø

šŸ‘ 1