I am trying to change some shadow-cljs output mainly because we have to handle .css imports and I would really like to not compile with shadow-cljs and then pipe it through another tool (because this breaks nice stuff like a REPL and hot reload).
So I was looking at hooking into the compilation stages would that be a valid use case for this? I am thinking I need the flush stage. But I would actually like to change that file input just before it get's flushed but it doesnt seem there is a stage for that
not entirely sure I understand which problem you are trying to solve?
what shadow-cljs output do you want to change?
I assume you are talking about a .css import a npm JS file is doing? not CLJS?
@thheller fyi, importing CSS is actually a part of JS standard now you can do
the output is an instance of CSSStyleSheet
same for type: "json"seriously?
yeah. here's a demo https://jsbin.com/janacorajo/1/edit?html,output
I have to use @atlaskit components from atlassian and those are NPM packages which contain css-imports
docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with
I just learnt yesterday that import got whole bunch of bells and whistles over the last couple of years
yay .. looking forward to that shit 😛
@mitchelkuijpers do you have an example?
CSS import does not equal CSS import unfortunately. so it isn't entirely clear what is needed without seeing an actual example
I literally see stuff like this:
import "./spinner.compiled.css";yes, what library is that in?
@atlaskit/spinner
They use this: https://compiledcssinjs.com/docs/how-it-works#extracted-styles
ok, that is the "simplest" variant which I have been meaning to add support for for a while now
never quite got around to implementing it yet though
basically all it needs to do is collect all those references and in the end concatenate all those files together and put a css file somewhere
Yeah I quite like how they did this honestly
at least in the simple version
gets more complicated when styles need to be scoped or renamed or whatever else other things do
but just import/require with unused return values should be pretty straightforward
kinda probably also want some kind of CSS processing (de-duplication) but that could come later
gist is that currently shadow-cljs just throws all this info away and doesn't collect it in any meaningful way
fwiw this compiled css stuff is exactly how shadow-css works, so I like that. just that its done as part of a JS build is kinda annoying. but its JS so what choice do they have 😉
Yeah true, I don't hate this solution too much tbh
But shadow-css also extracts a css file and gives you map that links to the generated classnames?
it could, but currently doesn't since css is a macro and already generates a classname https://github.com/gamb/shadow-css?tab=readme-ov-file#using-the-css-macro
this de-duping atomic css has never felt necessary yet. css is already tiny when gzip'd
Ah I see how shadow-css works that is also nice
the css being a macro helps and JS just can't do that
I was also thinking a bit about how loaders work in webpack but not sure if you would ever want to go that route
well thats why I asked. there are many cases where CSS is included as CSS modules, as in your write .foo { ...} ins a css file, require and an then use css.foo to get the actual classname which ends up getting scoped and renamed
that is much more involved and harder to process, and require special webpack loaders even in JS. so not that common anymore these days
but there are some libs that still do this, but then just ship a .css.js file that basically does all this and looks like a regular .js file to shadow. that just works, which is nice, but doesn't produce ideal css
Yeah that makes it way harder, I like how atlassian does it because it doesn't force us to use it in that way
I can add that shadow stores this in a way that a build hook can access
so that a build hook could just collect all those css references and do whatever
Could it for now also work to just use the flush build hook?
not really no, this info is just thrown away
Because I basically only want to update the import calls for those components
update?
Yeah sorry i need to be more clear. I am currently playing around with rollup if I give it the esm_import$$atlaskit.... file it then fixes the imports of this crap
and writes the css file an basically leaves the output from shadow-cljs alone but only adds the inlined npm module
inlined npm module? I'm so confused. so you are processing this stuff with a second tool? thought you didn't want that?
It transforms the import ... @atlaskit/spinner to the actual compiled source
I was playing around with how to fix this for now
ok, lets start from scratch. you are building a CLJS project with shadow-cljs. but it fails because it encounters a .css require in some npm dependency?
Yes you are exactly right
that is the core problem
ok, and you can't just ignore the .css file, you actually want the css to end up somewhere
if ignoring is fine you could just set :js-options {:ignore-asset-requires true} and shadow will just ignore them
Oh damn
but then the css is lost entirely
I think that might be enough for now since I fixed it for the frontend and that uses the same UI code
so you want that css to still be collected and accessible somewhere
Yeah in ideal world I would like that then I can ditch other bundlers
yes, that is what I was proposing. instead of throwing away encountered css requires I could just store them in a way that a build hook can access
so that the build hook could copy the contents somewhere, or really do whatever. without any need for other tools
collecting all that info instead of throwing it away is fairly straightforward and I think would be enough to get you what you want
you just can't do it currently because the info is not stored anywhere
Yeah exactly, if I would not the imports I could even feed it to a bundler to only fix that css crap for me if necessary
sure, something like :build-hooks [(some.thing/write-css-to "foo/bar.css")] or so would be trivial
Btw does :ignore-asset-requires work with :js-provider :import?
Because I don't get how that would work
no, why would it? it will never encounter any assets requires
Yeah that is what I thought
but that would't work with the build hook either .. shadow isn't processing any JS in that case
I have this problem currently also for a nodejs target
what problem? 😛
:target :esm
:runtime :node
:output-dir "target/shadow-cljs-output/functions"
:js-options {:js-provider :import}ok and?
This also imports stuff with those stupid css imports
yes, but its node doing the importing, not shadow ... so nothing shadow can do about it 😛
That why i was "inlining" those modules where some other tool transforms it to something that works with nodejs
But then I break hot reload and the repl
ok, shadow can already do that
:js-options {:keep-as-import #{"foo"}}, that set listing all the dependencies shadow should NOT bundle
but bundle all else. so you then also add :js-options {:ignore-asset-requires true} and you are good
:js-provider :import needs to be removed of course
Yeah ofcourse
I am going to play around with this to see if I get it working. I should then probably use: :node-library ? Because this won't work with target esm right?
should be fine? in fact :keep-as-import will ONLY work with :esm, for :node-library it would be :keep-as-require
Oh cool thank you so much. And writing the css to a separate file would still be amazing then we can remove a bundler
@thheller you should really document all of those flags, I feel like I’ve seen at least 10 of ad hoc flags now for various JS workarounds 🫠
leave it to the JS people to always come up with new and interesting ways to do things ...
"./runtime": {
"import": [
"./dist/esm/runtime.js",
"./src/runtime.ts"
],
"require": [
"./dist/cjs/runtime.js",
"./src/runtime.ts"
]
},the @atlaskit/spinner lib has these exports. first time I see a package with this structure and shadow-cljs gets confused by it. why the fuck is there a .ts file referenced ...
so yeah, even if I remove the asset require the lib still won't build because of that. fun times.
Wtf? I'll check some other packages to see if they do the same
I don't see this in the spinner lib, I see this:
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"module:es2019": "dist/es2019/index.js",I added a workaround but its still kinda annoying to always find these weird edge cases
Is it compiled/react stuff?
yeah sorry, from the @compiled/react package
I was also fighting with this:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/mitkuijp/Development/atlas-crm-connect/node_modules/@compiled/react/dist/esm/runtime/index' imported from /Users/mitkuijp/Development/atlas-crm-connect/node_modules/@compiled/react/dist/esm/runtime.js
at finalizeResolution (node:internal/modules/esm/resolve:283:11)
at moduleResolve (node:internal/modules/esm/resolve:952:10)
at defaultResolve (node:internal/modules/esm/resolve:1188:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:642:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:591:25)
at ModuleLoader.getModuleJobForRequire (node:internal/modules/esm/loader:347:53)
at new ModuleJobSync (node:internal/modules/esm/module_job:333:34)
at ModuleLoader.getModuleJobForRequire (node:internal/modules/esm/loader:407:11)
at new ModuleJobSync (node:internal/modules/esm/module_job:333:34)
at ModuleLoader.getModuleJobForRequire (node:internal/modules/esm/loader:407:11) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///Users/mitkuijp/Development/atlas-crm-connect/node_modules/@compiled/react/dist/esm/runtime/index'
}
I had added it to the ignore stuffBut it also doesn't work out of the box
good to know that it isn't just shadow-cljs getting confused by this 😛
Yeah I have no idea what they are doing
I noticed they also have nasty exports where they check if the dist folder exists and then point to the right folder.
yeah I suspect they use this setup for development and want to use actual .ts files and stuff. then just publish it as is instead of having a dedicated actual "dist"
Yeah I guess so too
I was trying to quickly fix this locally like this:
:resolve {"@compiled/react/runtime" {:target :file :file "node_modules/@compiled/react/dist/esm/runtime.js"}}
But that kinda blows up
Would it be possible to cut a release with this fix?I was going to do the asset stuff to a bit later and then make a release
I can make one now if that helps you?
No worries I'll clone it locally I mainly want to play around with it to see if I can get our build working
NP
no need for a full clone. just put the shadow/build/npm.clj file into your classpath
or just :local/root should be fine too, git directly doesn't work because some java files need compilation (i.e. lein javac)
Oh I'll just put that file in my classpath then, thank you so much 🙏
just don't forget to remove it later 😉
Hehe
alright so I just pushed 3.1.2 which changes how css files are handled. instead of just failing or throwing out the info about them, it just collects and ignores them quietly by default
but you can add :build-hooks [(shadow.build.css/extract-hook)] to the build config and that will create a css file per module
this is basically meant as an example as I don't really know what to best do with these files
https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/build/css.clj
anyone can in theory write a hook and do whatever makes sense
I'm open to ideas for all this, but this was the simplest examples I could come up with
its kinda hacky but seems to work (only tested with the @atlaskit/spinner)
the generated css file isn't loaded or referenced by the generated JS in any way, so something else is responsible for actually including it in the page
might make sense to just inline the thing into the .js file and just create a <style> tag, but I'm not sure thats a good way to go
Awesome I think we will just collect all css into one big file and see how that goes
It seems to work perfectly with :target :browser