Fork me on GitHub
#squint
<
2023-11-21
>
Chris McCormick01:11:59

Field report on compiling a small artifact via shadow-cljs versus squint. shadow (8.6k):

-rw-rw-r-- 1 chrism chrism 8.6K Nov 14 20:25 twge.js
squint (via vite) (11k):
-rw-rw-r-- 1 chrism chrism 11K Nov 18 18:07 _source/public/dist/assets/index-ye3oH3AF.js
This is with cljs that bends over backwards to be small doing all kinds of silly things to avoid native cljs datastructures. If I refactor it to look like more normal cljs the squint asset will be far smaller as the shadow asset will be ~100kb. https://github.com/chr15m/tiny-web-game-engine/

👍 1
❤️ 1
seancorfield02:11:16

I'm very impressed that you managed to produce such a small artifact from full-blown ClojureScript/Shadow-cljs 🙂 Is the refactored version available to look at too?

Chris McCormick02:11:06

I haven't had a chance to re-write it back to more traditional cljs but I know the size because every time I took a small step wrong and used e.g. = the size would balloon out. 😁

Chris McCormick02:11:37

So for example if you change coercive~= to = you will get a much larger artifact size.

Chris McCormick02:11:59

If you're interested in the hoops someone has to jump through to get it that small I wrote a post detailing it here: https://mccormick.cx/news/entries/clojurescript-uis-in-500-bytes

seancorfield02:11:53

Oh, nice! Thank you.

borkdude03:11:19

Did you produce the final artifact using vite build + minify? There’s some stuff that can be done to make the size somewhat smaller. 11kb is still pretty small for an app

Chris McCormick03:11:04

I did vite build and tried to run it through closure (which failed). Let me try some different minify options.

Chris McCormick03:11:19

Yes 11kb is still very small! 🤏

Chris McCormick03:11:21

I'll have to figure out which minify is the one that actually works these days. think_beret

borkdude03:11:50

Vite build already minifies + bundles all deps into single .Js file I think

borkdude03:11:29

Esbuild is good, rollup is what vite uses

borkdude03:11:24

You can just look at the dist output to see what it looks like, if it looks like a garbled mess, it’s minified

Chris McCormick03:11:16

Ah ok yep it's minified already. I don't think Vite does the kind of tree shaking that closure does by default but maybe with squint type of architecture it doesn't matter anyway.

borkdude03:11:04

I’ll have a look tomorrow at the output myself, there’s probably some improvements to be made. Closure is pretty clever though

borkdude03:11:51

I have run Closure over the already minifies output once successfully

👍 1
borkdude03:11:45

Oh wait, the difference is only like 10-15%? I misread. Doesn’t seem that significant to me, in squint you don’t have to hold back that much though :) aset is still something that can be made shorter. If you have tiny examples of stuff that stands out, feel free to post issues

borkdude03:11:06

You can configure clj-kondo to warn on certain function usages like = and suggest an alternative, it’s called discouraged var.

1
Chris McCormick04:11:43

Yeah very small difference. I probably need to check my life choices spending so much time on these kb. :face_holding_back_tears:

😂 1
borkdude04:11:48

Pretty useful experiment I think

borkdude09:11:57

With squint on master, managed to make it somewhat smaller ;)

✓ 19 modules transformed.
dist/index.html                  1.29 kB │ gzip: 0.76 kB
dist/assets/index-NPFnc9NS.css   0.84 kB │ gzip: 0.41 kB
dist/assets/index-140jzfIg.js   10.42 kB │ gzip: 4.34 kB

borkdude09:11:39

After processing it with closure, I can squeeze another 1kb off it ;)

$ npx google-closure-compiler --js=public/dist/assets/index-140jzfIg.js --js_output_file=dist/out.js

$ ls -lat dist/out.js
-rw-r--r--  1 borkdude  staff  9459 Nov 28 10:32 dist/out.js

borkdude09:11:07

(note that I had to wrap the entire output in a self-calling async function because closure doesn't support top level await)

borkdude09:11:03

(also the closure output doesn't even work, so it's probably not worth it)

borkdude09:11:20

I know another way to reduce size, hang on

borkdude09:11:21

hmm, marginal improvement:

dist/assets/index-ow5yst2U.js   10.37 kB │ gzip: 4.34 kB

borkdude09:11:45

The whole file fits in one screen on my 13" laptop so it's probably not hard to notice the differences

borkdude09:11:56

let me try your branch

borkdude09:11:50

these are the closure vs squint js outputs optimized and then pretty-printed again: https://gist.github.com/borkdude/5dce5e97bc8052b95c37644759dfa07d let's see...

borkdude10:11:18

I think a little bit more of squint's stdlib is imported which may explain the difference

Chris McCormick10:11:04

Wow! 10kb. This is great. That means I can maybe get small size without the small size compromises I had to make with full blown cljs. Thanks, I will definitely take a look at this next time I'm on the game engine project.

borkdude10:11:20

last time it was 11kb, so not that big of a difference but now it's only 1.4kb difference with full blown cljs with restrictions ;)

borkdude10:11:58

(I mean in your OP)

borkdude10:11:38

I think I know what's happening: I can probably optimize the js-interop macros a little bit more such that they won't use the squint stdlib at all and then you should end up in the same ballpark (1.5kb less) but the idea of squint is not to avoid the squint stdlib per se

borkdude10:11:40

@chris358 Ah got it. When optimizing those js-interop macros to avoid the stdlib I get this which is similar to "full blown shadow with Closure with avoidance of any CLJS core library function"

dist/assets/index-Cx3tSDMb.js   8.97 kB │ gzip: 3.81 kB

👍 1
guliy05:11:57

hi guys! Is it possible to writing react-native (expo) apps using squint? Maybe someone has experimented with this? Like https://github.com/PEZ/rn-rf-shadow

borkdude05:11:47

I haven’t tried but I’ve been wondering about this too. Take a look at the examples/vite-react directory and perhaps it’s possible to adapt this to a RN example

guliy05:11:07

thanks, i’ll check

borkdude09:11:50

since squint just compiles .cljs to .jsx I expect it to just work

borkdude09:11:05

but I don't have any experience with setting up a RN project

borkdude09:11:18

perhaps @U0ETXRFEW can make a squint example too :)

guliy09:11:39

I can try to do this, but for now I’m struggling with squint-repl )

pez09:11:09

I also expect it to work. Stoked to see what you find out @guliy!

borkdude09:11:51

@guliy How are you using the REPL? For RN I'd just use npx squint watch with {:paths ["src"]} in squint.edn

guliy09:11:48

Well, I wanted to be able to work with repl like I’m used to doing in clojure, but I see that it doesn’t work that way (

borkdude09:11:05

The nREPL works for Node.js currently, but it's kinda limited to 1 file, it's still in development. I don't know how a REPL for React Native works, what environment does it use?

guliy09:11:14

yes, I already tripped over the one file limit )

borkdude09:11:47

you can make it work when you rewrite the imports yourself from symbols to ["./relative_file.js"]

borkdude09:11:11

I intend to fix this within the next two months or so, it just takes time to mature stuff :-D

guliy09:11:25

yes that’s what I do now)

😆 1
guliy15:11:50

It’s works!! thanks, @U04V15CAJ :hugging_face:

pez16:11:03

Whooohoo!

borkdude14:11:42

Does anyone here has experience with vite on a non-react project? It does a full page reload each time I edit the JS but I want to preserve some state (the state of a text editor)

alexdavis14:11:50

what framework are you using? you usually need a third party plugin that can tell vite specifically what should be hot reloaded

borkdude14:11:16

no framework, just index.html + demo.mjs

alexdavis14:11:42

hmmm I think you will need to write a plugin then using this https://vitejs.dev/guide/api-hmr

alexdavis14:11:58

(maybe there is a better way, I am not an expert)

borkdude14:11:08

I'm looking at the api-hmr docs, but don't understand how accept works

borkdude14:11:33

also read that 😢

alexdavis14:11:17

Here's a brief example of how to use HMR's API in a Vanilla JavaScript project:
Let's say you have a module message.js:
export function message() {
  console.log('My Message')
}
And you import it in main.js:
import { message } from './message.js'

function app() {
  message()
}

app()

// Handle HMR
if (import.meta.hot) {
  import.meta.hot.accept('./message.js', () => {
    app()
  })
}
seems like accept registers a callback for when a file (module) is updated

alexdavis14:11:35

another way of doing this btw is just to make lots of small files, vite should only rerun the ones that have changed

alexdavis14:11:14

so if you don't touch the index file which renders the root components but touch some 'subcomponent.mjs' it won't rerender everything - I think it does this out of the box

alexdavis14:11:44

personally I get very annoyed by js projects where every single component is its own file but I think this is the reasoning behind it. though these days react etc have plugins which keep track of named functions and can surgically reload just the ones that have changed (though you have to follow some rules like splitting regular exported functions and components into diffferent files, at least you don't end up with 10000 files)

borkdude14:11:32

the thing that bites me is that Vite is doing a full page reload

borkdude14:11:32

even when I have a small file

./editor.mjs
when I add console.log('editor'); there, and only touch that file, the whole page still gets refreshed

borkdude14:11:34

The part I don't understand about this accept business is, what exactly happens here:

if (import.meta.hot) {
  import.meta.hot.accept('./message.js', () => {
    app()
  })
}

borkdude14:11:46

I just don't want to refresh the whole page and don't remount the editor, that's all 😭

borkdude14:11:40

I'll just move on for now, perhaps I'll revisit some day

alexdavis14:11:08

import.meta.hot.accept('./message.js', () => { app() }): If HMR is enabled, this line is saying, "please accept the hot updates of the module './message.js'", and when there is an update, execute the provided function { app() }, which means rerun the app function. I think

borkdude14:11:50

that's what I understood too, but how do you prevent an editor from re-mounting in this scenario for example

alexdavis14:11:13

yeah it shouldnt, assuming the page doesn't reload... apparently doing

if (import.meta.hot) {
    import.meta.hot.accept();
}
should be enough to stop a full page reload, but if that doesn't work perhaps you can use some persisted state management so that the editor contents is maintained even through a page reload?

alexdavis14:11:27

either using the url as a state store or some localstorage/idb solution

alexdavis14:11:42

if the editor can have infinite content I would go idb

borkdude14:11:31

oh nice, this prevented the hot reload!

if (import.meta.hot) {
  import.meta.hot.accept();
}  

borkdude14:11:38

yeah, I can store the editor in a global

borkdude14:11:41

so now I can work it out

borkdude14:11:40

This works!

if (import.meta.hot) {
  import.meta.hot.accept();
}

function initEditor() {
  if (globalThis.editorLoaded) return;
  new EditorView({state: state,
                  parent: editorElt,
                  extensions: extensions });
  globalThis.editorLoaded = true;
}
initEditor();  
Thank you!

borkdude14:11:44

Except that it doesn't really do the hot update :)

borkdude14:11:47

but it does hot-update the tiny file I edit

borkdude14:11:55

I think the problem now is that it doesn't swap in the new module

borkdude14:11:10

just need to have that part going, then it would be perfect

borkdude14:11:43

I do see reloading side effects, just functions don't get redefined

borkdude14:11:03

or perhaps this is just an issue of function references not updated

borkdude14:11:54

this is so much more complex than in a CLJS app, damnit

☝️ 1
borkdude14:11:13

sorry for the rant and thanks for the help

refset15:11:05

alt+enter isn't working for me, but windows+enter ("MOD + Enter" / cell eval) works great - awesome stuff!

borkdude15:11:43

yeah, the keys on the left aren't accurage

borkdude15:11:58

on the right facepalm (had a short night)

😅 1
borkdude15:11:40

I updated the link to include a require from CDN from some lib

refset15:11:41

cool, that's activated the alt+enter keybinding, but the wires seem a bit crossed - like currently I would say the following is an accurate description: > mod+enter is doing "at cursor" > alt+enter is doing "cell" > alt+shift+enter is a noop

borkdude16:11:32

yes, I need to make those things OS-specific

👍 1
refset16:11:14

(I'm running Linux/Chromium fwiw)

refset18:11:36

nice, almost! the instructions now say ctrl, but I have to press windows (mod?) instead -- otherwise it all works as expected 🙂

borkdude16:11:06

@guliy if you want to share the code, we can link from the squint readme

guliy16:11:02

no problem, i can make pr

👍 1
pez16:11:07

Is there something similar to #C03U8L2NXNC, but for Java?

pez16:11:39

Is it unfeasible?

borkdude16:11:57

can you name 1 or 2 things that you would fine squint-ish but for Java?

pez16:11:10

What I’m after is something that let’s me code in Clojure, but can run in a Java environment where the Clojure runtime can’t run. Don’t know how squint-ish that is, but it seems to be part of it that wherever javascript runs, I can use squint to write the program, without the overhead of a runtime.

borkdude16:11:16

I don't think that'll work as nicely as with Js

pez17:11:57

> What’s a royal ball? After all, I suppose it would be frightfully dull, and boring, and completely ... completely wonderful. — Cinderella

Chris McCormick23:11:22

perhaps somebody has compiled java to wasm? think_beret

Chris McCormick23:11:12

oh wait that's completely the wrong thing never mind

Chris McCormick23:11:53

@Export
public static void main() {
    Document document = Window.document();
    HTMLElement div = document.createElement("div");
    Text text = document.createTextNode("Hello World, this text come from WebAssembly."); 
    div.appendChild( text );
    document.body().appendChild( div );
}
what a monstrosity. "java"script comes full circle.

Chris McCormick00:11:09

> CheerpJ is the only solution which can run any large-scale, unmodified Java applications, applets, or libraries in the browser. No downloads or plugins required. https://cheerpj.com/

Chris McCormick00:11:34

> Compiles Java bytecode to JavaScript, WebAssembly and C https://github.com/konsoletyper/teavm

Chris McCormick01:11:23

imagine clojure -> java bytecode -> wasm -> compiling clojurescript -> javascript 😂