Fork me on GitHub
#shadow-cljs
<
2023-06-30
>
Ella Hoeppner04:06:17

I'm looking for advice on how to minimize the size of my build. Right now it's at about 350kb. The only dependecy is a pure cljs library, which itself has no dependencies. I thought I'd read that it was possible to get shadow cljs builds down to ~90 kb in the best case, but in the build report I'm seeing that just cljs/core.cljs itself is 166.75kb, so I don't see how the project could get smaller than that. What's a reasonable lower bound on how small shadow cljs builds can get, and are there any tips that might help me get my build to be smaller?

hifumi12304:06:50

If the cljs library uses pprint and a bunch of cljs core functions, then there's not much you can do IMO

Ella Hoeppner04:06:52

It does use pprint but I can remove that for the build, it's only included for debugging. The library is one I've written myself, so I can make changes to it to to help reduce the size as well. Does the size of the cljs/core.cljs file depend on the number of different cljs core functions I'm using, or does the whole thing get included if I use any of them? I might be able to avoid using some if that would help

thheller04:06:28

the size of whats remaining of cljs.core definitely varies depending use

Ella Hoeppner04:06:28

yes, here's the report if it's helpful:

thheller04:06:40

pprint will definitely keep just about everything alive

thheller04:06:07

[GZIP: 82.47 KB]

thheller04:06:12

that is very reasonable I think

Ella Hoeppner04:06:40

ah ok. I knew pprint was already adding a lot because cljs/pprint.cljs itself was 89.98kb, but if getting rid of that will also shrink cljs/core.cljs then that might be all that I need

thheller04:06:03

yeah definitely get rid of pprint

👍 2
Ella Hoeppner04:06:48

82.47kb would be reasonable enough for most use cases, but for this project I'll be uploading all of the code to the ethereum blockchain, so... every kb is $$$

thheller04:06:23

depends on how many core collections and protocols you use. in theory cljs.core can be fully eliminated, but even accessing pr-str or something print related will keep pretty much everything alive

thheller05:06:58

if you want to dive really deep into optimizations you can run with shadow-cljs release browser --pseudo-names and wade through the output looking for "big chunks" of code

thheller05:06:29

they usually stand out in some way, running through prettier js or something might help too

Ella Hoeppner05:06:39

ok, good to know. Another thing I've thought about to reduce the size is going through the compiled js file and removing all calls to throw() and more generally any error handling code. The code will be well-tested before it goes live and if there are any bugs then there's no way to update the code once it's on-chain, so there's not really any point in having code for handling errors. There doesn't happen to be a compiler option or anything that would do something like that for me, does there?

thheller05:06:50

its somewhat straightforward to identify what the code belongs to, then you can maybe figure out if you actually need it

thheller05:06:23

there is not no

thheller05:06:47

is the code gzipped when put on chain? or stored as is?

Ella Hoeppner05:06:56

gzipped, thankfully.

Ella Hoeppner05:06:10

the --pseudo-names thing sounds very helpful, thanks

thheller05:06:28

thats good. then many of the repeated patterns don't hurt so much

thheller05:06:18

as a general tip avoid variadic or multi arity functions. those generate much more code than regular single arity functions

thheller05:06:42

but your code seem very small already so you're probably good

hifumi12307:06:29

@ellahoeppner honestly, Ethereum is the last place id store compiled CLJS code... yes google closure is amazing at DCE and optimization, but there's so many little things in CLJS that simply are not eliminated by Google closure (unused map keys, unused top-level data structures), ..., and some things, by design, are impossible to tree shake (specs, multimethods, ...) while arithmetic etc on Ethereum costs about 20 gas, storage is 20k gas, so i would try to use google closure itself with vanilla JS to get as many optimization as possible, and prefer brotli over gzip if it is possible to use brotli compression or better yet, store the code elsewhere...

thheller08:06:27

I didn't know eth runs on JS? or is this really just for storage and some other contract thingy? (I know next to nothing about eth)

thheller08:06:24

@hifumi123 I disagree. only because the compiler can't optimize it doesn't mean a human can't. so if you are careful you can write very minimal code. the question is if you are still writing CLJS in the end if you give up too much stuff

thheller08:06:09

people have written <5kb JS in CLJS, so it is totally possible

hifumi12308:06:22

I have been able to write very tiny CLJS software, too, but it doesn’t do anything interesting compared to when you can afford bundling all of cljs.core 😛

hifumi12308:06:11

I think we agree on this point: if you’re give up almost everything CLJS offers to get smaller bundle size, then what is the value of CLJS?

thheller08:06:41

yeah, lots of value in cljs.core for sure but sometimes you have other priorities such as absolute size

thheller08:06:38

butI disagree that switching to "vanilla JS" is necessary, you can achieve the same in just CLJS size wise

Jérémie Salvucci12:06:17

Hi, I'm trying to use the npm package @walletconnect/modal-sign-html but it depends on @walletconnect/modal which does some await import. This seems to be an issue (with an error message such as "Uncaught (in promise) Module not provided: @walletconnect/modal-ui"). Is there a way to circumvent this with shadow-cljs right now?

thheller14:06:46

this was asked not too long ago in this channel

thheller14:06:05

there is no support for this currently no

thheller14:06:24

don't know anything about the specific code, so can't say if there is a workarround

Jérémie Salvucci15:06:25

The code is the following one

import{ModalCtrl as t,ThemeCtrl as o,ConfigCtrl as i,OptionsCtrl as n} from "@walletconnect/modal-core";

class d {
  constructor(e) {
      this.openModal=t.open,
      this.closeModal=t.close,
      this.subscribeModal=t.subscribe,
      this.setTheme=o.setThemeConfig,o.setThemeConfig(e),i.setConfig(e),
      this.initUi()
  }

    async initUi() {
	if (typeof window<"u") {
	    await import("@walletconnect/modal-ui");
	    const e=document.createElement("wcm-modal");
	    document.body.insertAdjacentElement("beforeend",e),
	    n.setIsUiLoaded(!0)
	}
    }
}

export{d as WalletConnectModal};

Jérémie Salvucci15:06:53

do you have a reference explaining the current limitations regarding this feature?

thheller15:06:49

its not supported at all. period. no limited support or anything, just not supported as of today.

thheller15:06:56

and frankly I don't see why they do this in the first place. is it too much to ask your users to require @walletconnect/modal-ui if they need it?

thheller16:06:43

and this is what I recommend doing too? whats the point of this small nonsensical code?

thheller16:06:55

I believe the recommendation I made the last time is to use webpack instead like this https://shadow-cljs.github.io/docs/UsersGuide.html#js-provider-external

thheller16:06:22

or if I follow this can of worms you can just skip @walletconnect/modal-sign-html and @walletconnect/modal entirely and implement it yourself. its probably less than 50 lines of code. but given the way all these libs seem to be structured webpack seems like a better path, they seem to rely heavily on it.

Jérémie Salvucci20:06:42

All right, I'm going to think about doing this right

zimablue14:06:01

why not just upload the source code? also depending on what you're trying to write, couldn't you bundle nbb with it and would that ever be smaller (but slower)?

borkdude14:06:10

nbb itself is not small

zimablue14:06:52

sorry, stupid idea then. I thought there might be some cross-over where the cost of loading the interpreter was less

borkdude14:06:42

if you're looking for ultra-small files but still want to have some something that looks like CLJS, try #C03U8L2NXNC - you can use esbuild to optimize it to a single file, should usually be under 10kb or so

borkdude14:06:56

note that it isn't compatible with regular CLJS code, only a subset

zimablue14:06:25

sorry because this is totally off topic and none of my business, but how did you reply so fast? do you have some sort of notification-driving query for keywords set up?

borkdude14:06:05

I was notified by someone using the "nbb" word

zimablue15:06:06

is there a programatic way to enumerate multiple instances of the same app in the shadow.cljs.devtools.api? eg. I yarn start the same app twice, now in the shadow-cljs web portal I can see that there are two REPLs under Runtimes, in order to connet to a specific one I need their ID (?). I can't see a way to get to that through the API, skimming the shadow-cljs ui code it looks like it tracks them through mutating the DB when connection messages are received

zimablue15:06:27

nevermind found it

zimablue15:06:03

repl-runtimes

Ella Hoeppner16:06:28

@hifumi123 @zimablue the context here is that I'm creating a generative art piece for an ethereum art platform, http://alba.art. The platform expects me to upload a html file (where all the js that defines the piece is included in inline <script> tags) that will be put on the blockchain, and then people can view the piece through the alba site, which retrieves the code from the blockchain and runs the code in their browser. So just uploading the cljs source code wouldn't work, it needs to be executable js. The code is uploaded to the blockchain so collectors can be assured that the piece will still be accessible many years down the line, as opposed to just being on some private server that could get shut down. Of course I could just write everything in vanilla js instead, but I've developed a graphics library and workflow with cljs that I'm very fond of, so having to give up on cljs entirely would really suck. In the long-run I might try to build up a new library/workflow in js or some language that can target WASM to reduce my file sizes in future projects, but in the short-to-medium run I'll be working with cljs, so the smaller I can get the shadow-cljs build to be, the better.

thheller19:06:01

interesting. thanks for the breakdown.

lilactown21:06:08

I have an nginx server that is serving my built JS locally over HTTPS. I'm trying to configure the websocket to connect over HTTPS as well. How do I do this?

lilactown21:06:55

I've tried configuring :ssl {:keystore ,,, :password ,,,} at the top level of my shadow-cljs.edn, but it continues to try and connect over an insecure websocket connection

lilactown21:06:20

DOMException: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.
    at Function.eval [as cljs$core$IFn$_invoke$arity$2] (js/compiled//cljs-runtime/shadow.cljs.devtools.client.websocket.js:25:15)
    at Function.eval [as cljs$core$IFn$_invoke$arity$1] (js/compiled//cljs-runtime/shadow.cljs.devtools.client.websocket.js:20:52)
    at Object.eval [as attempt_connect_BANG_] (js/compiled//cljs-runtime/shadow.cljs.devtools.client.shared.js:481:77)
    at Object.shadow$cljs$devtools$client$shared$init_runtime_BANG_ [as init_runtime_BANG_] (js/compiled//cljs-runtime/shadow.cljs.devtools.client.shared.js:969:16)

lilactown21:06:37

it worked after restarting the shadow-cljs server completely