This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-31
Channels
- # aleph (12)
- # announcements (4)
- # asami (7)
- # babashka (20)
- # beginners (92)
- # calva (74)
- # clj-kondo (8)
- # cljdoc (70)
- # clojure (47)
- # clojure-dev (29)
- # clojure-europe (27)
- # clojure-nl (7)
- # clojure-norway (3)
- # clojurescript (7)
- # cursive (2)
- # datomic (1)
- # emacs (8)
- # events (5)
- # fulcro (36)
- # gratitude (4)
- # humbleui (25)
- # introduce-yourself (1)
- # lsp (26)
- # malli (6)
- # missionary (8)
- # nbb (50)
- # off-topic (9)
- # pathom (2)
- # pedestal (3)
- # portal (32)
- # practicalli (5)
- # reitit (5)
- # releases (1)
- # ring (6)
- # shadow-cljs (87)
- # sql (31)
- # tools-deps (26)
- # vim (3)
- # xtdb (15)
I tried creating a simpler project to get this working instead of the reagent template:
{:deps {:aliases [:cljs]}
:builds {:app {:target :esm :output-dir "resources/public/assets/js"
:modules {:main {:init-fn app.core/init}}}}
:dev-http {3000 "resources/public"}}
and then tried to complete the build by running it through vite with the babel plugin wrapped in vite-plugin-relay, but am still seeing an error which I'm reading to mean that the plugin had not been applied, although I've been able to get it to work for projects outside of clojurescriptI'm seeing that when running standard jsx, I'm able to track the transformation. I wasn't sure if shadow-cljs can be configured such that it outputs code that reads similarly. vite code:
import _AppQuery from "./__generated__/AppQuery.graphql";
import React from "react";
import { graphql, useLazyLoadQuery } from "react-relay";
export default function App() {
const data = useLazyLoadQuery((_AppQuery.hash && _AppQuery.hash !== "fe6589af5b8bfbe9722d3b296cc93444" && console.error("The definition of 'AppQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _AppQuery), {});
return /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement("h1", null, /* @__PURE__ */React.createElement("a", {
href: "",
target: "_blank"
}, "SpaceX"), " ", "Data Viewer"), /* @__PURE__ */React.createElement("h2", {
id: "ships-heading"
}, "Ships"), /* @__PURE__ */React.createElement("ul", {
"aria-labelledby": "ships-heading"
}, data.ships?.map(ship => /* @__PURE__ */React.createElement("li", {
key: ship?.id
}, ship?.name))));
}
shadoww-cljs compiled js:
import "./cljs_env.js";
goog.provide('app.core');
var module$node_modules$react_dom$client=shadow.js.require("module$node_modules$react_dom$client", {});
var module$node_modules$react_relay$index=shadow.js.require("module$node_modules$react_relay$index", {});
app.core.query = module$node_modules$react_relay$index.graphql`
query app_pages_homeQuery {
rates(currency: "USD") {
currency
rate
}
}
`;
/**
* testing
*/
app.core.gql = (function (){var G__32142 = (function app$core$gql_render(props__31358__auto__,maybe_ref__31359__auto__){
var vec__32143 = new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [helix.core.extract_cljs_props(props__31358__auto__),maybe_ref__31359__auto__], null);
var map__32146 = (app.core.query.cljs$core$IFn$_invoke$arity$1 ? app.core.query.cljs$core$IFn$_invoke$arity$1(module$node_modules$react_relay$index.useLazyLoadQuery) : app.core.query.call(null,module$node_modules$react_relay$index.useLazyLoadQuery));
var map__32146__$1 = cljs.core.__destructure_map(map__32146);
var rates = cljs.core.get.cljs$core$IFn$_invoke$arity$2(map__32146__$1,new cljs.core.Keyword(null,"rates","rates",-990130920));
var map__32147 = rates;
var map__32147__$1 = cljs.core.__destructure_map(map__32147);
var currency = cljs.core.get.cljs$core$IFn$_invoke$arity$2(map__32147__$1,new cljs.core.Keyword(null,"currency","currency",-898327568));
var rate = cljs.core.get.cljs$core$IFn$_invoke$arity$2(map__32147__$1,new cljs.core.Keyword(null,"rate","rate",-1428659698));
return helix.core.get_react().createElement("div",(function (){var obj32149 = ({"key":currency});
return obj32149;
})(),helix.core.get_react().createElement("p",null,currency,": ",rate));
});
if(goog.DEBUG === true){
var G__32150 = G__32142;
(G__32150.displayName = "app.core/gql");
return G__32150;
} else {
return G__32142;
}
})();
/**
* A component which greets a user.
*/
app.core.greeting = (function (){var G__32152 = (function app$core$greeting_render(props__31358__auto__,maybe_ref__31359__auto__){
var vec__32153 = new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [helix.core.extract_cljs_props(props__31358__auto__),maybe_ref__31359__auto__], null);
var map__32156 = cljs.core.nth.cljs$core$IFn$_invoke$arity$3(vec__32153,(0),null);
var map__32156__$1 = cljs.core.__destructure_map(map__32156);
var name = cljs.core.get.cljs$core$IFn$_invoke$arity$2(map__32156__$1,new cljs.core.Keyword(null,"name","name",1843675177));
return helix.core.get_react().createElement("div",null,"Hello, ",helix.core.get_react().createElement("strong",null,name),"!");
});
if(goog.DEBUG === true){
var G__32157 = G__32152;
(G__32157.displayName = "app.core/greeting");
return G__32157;
} else {
return G__32152;
}
})();
= (function (){var G__32160 = (function app$core$app_render(props__31358__auto__,maybe_ref__31359__auto__){
var vec__32161 = new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [helix.core.extract_cljs_props(props__31358__auto__),maybe_ref__31359__auto__], null);
var vec__32164 = helix.hooks.use_state(new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"name","name",1843675177),"Helix User"], null));
var state = cljs.core.nth.cljs$core$IFn$_invoke$arity$3(vec__32164,(0),null);
var set_state = cljs.core.nth.cljs$core$IFn$_invoke$arity$3(vec__32164,(1),null);
return helix.core.get_react().createElement(module$node_modules$react_relay$index.RelayEnvironmentProvider,(function (){var obj32168 = ({"environment":app.core.environment});
return obj32168;
})(),helix.core.get_react().createElement("div",null,helix.core.get_react().createElement("h1",null,"Welcome!"),helix.core.get_react().createElement(helix.core.Suspense,(function (){var obj32170 = ({"fallback":"loading..."});
return obj32170;
})(),helix.core.get_react().createElement(app.core.gql,null))),helix.core.get_react().createElement(app.core.greeting,(function (){var obj32172 = ({"name":new cljs.core.Keyword(null,"name","name",1843675177).cljs$core$IFn$_invoke$arity$1(state)});
return obj32172;
})()),helix.core.get_react().createElement("input",(function (){var obj32174 = ({"value":helix.impl.props.or_undefined(new cljs.core.Keyword(null,"name","name",1843675177).cljs$core$IFn$_invoke$arity$1(state)),"onChange":(function (p1__32158_SHARP_){
var G__32175 = cljs.core.assoc;
var G__32176 = new cljs.core.Keyword(null,"name","name",1843675177);
var G__32177 = p1__32158_SHARP_.target.value;
return (set_state.cljs$core$IFn$_invoke$arity$3 ? set_state.cljs$core$IFn$_invoke$arity$3(G__32175,G__32176,G__32177) : set_state.call(null,G__32175,G__32176,G__32177));
})});
return obj32174;
})()));
});
if(goog.DEBUG === true){
var G__32178 = G__32160;
(G__32178.displayName = "app.core/app");
return G__32178;
} else {
return G__32160;
}
})();
app.core.init = (function app$core$init(){
if((typeof app !== 'undefined') && (typeof app.core !== 'undefined') && (typeof app.core.root !== 'undefined')){
} else {
app.core.root = module$node_modules$react_dom$client.createRoot(document.getElementById("root"));
}
return app.core.root.render(helix.core.get_react().createElement(,null));
});
goog.exportSymbol('app.core.init', app.core.init);
//# sourceMappingURL=app.core.js.map
@concentric12_clojuria I do not know what kind of code the babel plugin expects. it might just not recognize the CLJS code, which seems likely.
Hello! I'm using the functionality of importing literal js files, which makes my life easier on some fronts. But it seems that they get fed through Google Closure Advanced compilations which, given they don't have externs, screws things up by renaming stuff. What's the best way forward for me? So far I've gone to string-based properties but it's tedious.
probably should just write the externs, makes the code a lot cleaner.
You can write your own externs, I don't know if you knew that. It's not that hard, although figuring out the tooling takes a few hours
here's an example of an externs file I wrote four PouchDB
var pouchdb = function() {};
pouchdb.plugin = function() {};
pouchdb.plugin().auth = {};
var auth = {};
auth.default = {};
var perms = {};
var pfind = {};
pfind.default = {};
var pmemory = {};
pmemory.default = {};
pouchdb.logIn = function() {};
pouchdb.changes = function() {};
pouchdb.changes().on = function() {};
pouchdb.createIndex = function() {};
pouchdb.get = function() {};
pouchdb.put = function() {};
= function() {};
pouchdb.remove = function() {};
pouchdb.allDocs = function() {};
pouchdb.find = function() {};
pouchdb.sync = function() {};
pouchdb.sync().on = function() {};
pouchdb.destroy = function() {};
pouchdb.bulkDocs = function() {};
pouchdb.getAttachment = function() {};
although strangely enough, I haven't had to write externs like that since I started using shadow-cljs according to the guide. But that's what I used when I did figwheel
@U7PBP4UVA is this how you are importing your javascript? https://shadow-cljs.github.io/docs/UsersGuide.html#_requiring_js
Yes, requiring js files. For anything else I have :infern-externs :auto
and just annotate with ^js
as prompted. But for literal js files I get no warning, only runtime errors.
are those 3rd party libs or your libs?
I'm just curious b/c I haven't had that issue for some reason and I use external js extensively
e.g.
if (parent && parent.type.allowsMarkType(type)) {
here, the allowsMarkType
gets renamedhmmmmm
maybe try turning off :inter-externs
b/c that shouldn't be renamed, or if it is renamed, it should be globally renamed
do you have a precompilation step like webpack?
otherwise it seems to just paste all the code into one file then aggressively rename stuff, so I wouldn't see why it wouldn't be globally renamed
well one way you can check is to turn off renaming
:pretty-print true
somewhere
lol I love telling people things they already know, it's so much easier than doing my own work 😅
there is no externs inference for JS files. so it is expected that allowsMarkType
would be renamed
you can sort of figure out what the original name is by using shadow-cljs release app --pseudo-names
an alternate is to not feed the JS files through :advanced
. you do that by changing them to commonjs
only ESM JS files go through advanced, regular commonjs is processed like all other npm files (ie. only simple)
Ah, using common.js is easier than trying to write externs, thanks @U05224H0W - why should ESM files be advanced though? Is there the expectation that an npm library that produces ESM will also get renamed?
well you lose some stuff by going to commonjs. the files can no longer import CLJS code directly
writing externs also takes like 5min https://shadow-cljs.github.io/docs/UsersGuide.html#_simplified_externs
ProseMirror actually has a well-defined Typescript API, not sure if that's consumable somehow
But the actual use case I have for JS files is mainly copied code that is too cumbersome to rewrite to CLJS 😄
I mean there could be a tool that collects all property names in JS files and just adds them to externs
turning of all renaming unfortunately makes the build rather large, since CLJS generates rather long property names for stuff
http://www.dotnetwise.com/Code/Externs/ https://www.npmjs.com/package/tsd2cce FYI
generating too many unneeded externs can have negative effects if you care about build size at all
Thanks @U3BALC2HH @U05224H0W - for now I'm just converting my handful of JS files to common.js, ProseMirror has a large enough API surface to make writing externs for it a nuisance. Most of our code is using it from CLJS anyway, the JS files are mostly utilities.
hi everybody. Let's say there is a instance of shadow watching some :app build id, and I connect to the shadow nrepl server, is there a way of compiling and evaluating a form under some namespace for my :app build id ? I know I can convert the repl to a cljs one, and then eval there, but I want to know if it is currently possible to do it from the clojure repl.
something like retrieving the repl environment by build id
nice! thanks!
@U05224H0W is there an easy way of getting that build-id env? so I can call things like cljs.analyzer.api/resolve
, which requires the env
(shadow.cljs.devtools.api/compiler-env :app)
but its not the env required for resolve, its the compiler env usually bound to cljs.env/*compiler*
atom
I think there is a cljs.analyzer.api/with-compiler-env
macro or so where this would go
thanks a lot! will try to experiment with that
Hi, we're using shadow-cljs + esm for our front end and just upgraded to 2.20.7. Wanted to share that it shaved off ~25% from our bundle size w/o any changes needed. Awesome work and thanks!
That's great! Would love to hear a bit more about what changes you made in your config as well as any shadow-cljs
changes you're aware of that contributed to the dramatic decrease in bundle size
probably only this change https://github.com/thheller/shadow-cljs/commit/723fbf0075c8a06ba452104b61fb97ca6609f573
with using :js-provider :import
(ie. shadow-cljs not in charge of bundle npm dependencies)
Yep! We're using :js-provider :import
along w/ webpack and the commit above made our webpack bundle much smaller 🎉
Awesome!
As an aside, we are currently lazy-loading modules in our app. Do you think a similar change would benefit us? It's been a while since I revisited this, but if I recall correctly, :js-provider :import
bundles all npm deps into a single file to be fed into the external JS bundler. Since modules are loaded all or nothing, would this mean all our deps would be loaded as soon as our first "app" module required a dep? in a lazy-loaded/code-split environment, the benefit is not as clear-cut right? maybe it's still worth the reduced bundle size -- i might have to experiment 🙂
From my understanding, :js-provider :import
doesn't do any bundling of the JavaScript dependencies and instead leaves that to some other tool (in our case webpack).
Ah yes, sorry I used "bundle" incorrectly. I meant to communicate that shadow-cljs puts all of the JS require calls (dependencies) into a separate file, then lets the external tool do the bundling/tree-shaking
Ahhh, we're not doing code splitting yet so I'm not sure how it would work w/ that. If you do end up experimenting some, would be interested in your findings!
@UGGU8TSMC you are thinking of :js-provider :external
. thats different. :js-provider :import
fully supports code splitting as well, :external
puts all JS deps in one file and is not splittable
whether or not you'd benefit by :import
depends on the npm deps you use. not all are tree shakeable
Sorry for the obtuse question but how would I get started using :js-provider :import
over :js-provider :external
would I just need to hand roll a js file for the requires? I’m not sure how they’d be exposed to shadow-cljs. I’ve got a 1mb production artifact I’m convinced could be split down some.
The way I did it was I created a webpack config with the entrypoint being a javascript file looking like this. main
corresponds to the module that I have defined in my shadow-cljs.edn
file.
if (process.env.NODE_ENV !== "production") {
require("./.shadow-cljs/cljs-runtime/shadow.cljs.devtools.client.browser.js");
}
import init from "./.shadow-cljs/main.js";
init();
I don’t suppose you have anything on github I can look at? 😄 Does this mean that the requires within the shadow-cljs app are picked up by whatever js build tool? That’s nice. That’s very nice.
I actually wrote a note to put together a blog post with what I did since it's not part of an open-source project so will tag you when that gets done 👍 what I ended up doing was disabling webpack hot-reloading and I let shadow-cljs do all the reloading.
Awesome, if I wrap my head around it fully I’d write something myself. I do find some of the latter esm stuff a bit hard to understand at times 😅
@U2U78HT5G if all you need is to run init you can just use :init-fn
in the build config. no need for exports then. also the extra conditional require
shouldn't be required at all. you can just point webpack directly at the shadow-cljs output
Hi again, I just wrapped up the blog post this morning. I think I included all the changes I made as part of the migration but if anything doesn't make sense feel free to reach out! https://deh.li/2022/11/04/shadow-cljs-webpack.html
@U2U78HT5G please note that it is not necessary to have two builds like this. splitting dev/advanced build is already handled by the watch/release commands. the config can be the same for both builds
so in your case just
;; shadow-cljs.edn
{:builds
{:web
{:target :esm
:output-dir ".shadow-cljs"
:devtools {:watch-dir "resources/public"}
:js-options {:js-provider :import}
:modules {:main {:init-fn com.vistaly.web.core/init!}}}
}}
is all you needon a second note though please consider using another :output-dir
. the .shadow-cljs
dir is for shadow-cljs internal runtime files and caches. at some point this may create conflicts and either create problems in your builds or shadow-cljs internally
release
also sets goog.DEBUG false
, so no need to specify it. it also defaults to :optimizations :advanced