Fork me on GitHub
#shadow-cljs
<
2019-12-20
>
pcj01:12:29

Has anyone been able to use anime.js with shadow-cljs?

pcj17:12:20

ahh thank you! was trying to use ["animejs/lib/anime.es" :default anime]

dpsutton05:12:26

i just did a POC. what issues are you having?

salam16:12:09

greetings, all! i’ve put together some instructions for setting up shadow-cljs and fireplace.vim integration as i could not find any up-to-date content on this. i would like to ensure this is THE up-to-date (and correct?) way to do it as of right now before i show this to others so that i don’t end up misleading them. would anybody care to spend 2 minutes of their time and help me review it? thanks a lot! the uri to the instructions is here: https://gist.github.com/finalfantasia/292b8432dc0072b2397ea4d694aab930

Ian M17:12:05

I have done :Piggieback :module-name instead of the :CljEval , i am wondering what the difference is

salam17:12:51

i’m quite new to the cljs side of the things so i’m not sure. but based on the conversations here (https://github.com/thheller/shadow-cljs/issues/62 and https://github.com/thheller/shadow-cljs/issues/561) and fireplace.vim’s on-line documentation about :Piggieback:

*fireplace-piggieback* *fireplace-clojurescript*

To use a ClojureScript, invoke |:CljEval| with the command you would normally use to start a ClojureScript REPL.  For example:
>
        :CljEval (cider.piggieback/cljs-repl (cljs.repl.nashorn/repl-env))
<

...

The :Piggieback command is softly deprecated wrapper for invoking Piggieback.
i figured :CljEval might be the more generic way to go about adding cljs support to an nrepl session. i might be wrong though. :man-shrugging::skin-tone-2:

Ian M17:12:03

thank you for sharing!!

salam17:12:07

you are welcome! it makes me happy to give back to the awesome community, even a tiny bit. 🙂

Drew Verlee17:12:16

Concerning code splitting. What does it mean to depend-on another module? The docs say > The names of other modules that must be loaded in order for this one to have everything it needs. and the blog post says : > As explained above we will start with the `:main` module (becoming the `main.js` output) calling `(http://demo.app/init)` on load, it is the only module that can be loaded directly without loading any other. Then we define one module for each component and they all depend on the `:main` module since that will provide the common code such as `reagent`, `react` and of course `cljs.core`. `:module-loader true` just tells `shadow-cljs` that it needs to do a couple extra steps to allow loading the code dynamically. This seems straight foward, but to test my understanding. Say you have a react compoment A that contains another compoment B and both are made into modules. code looking like (not precise semantics of course) (lazy-load A) (defn A [] (.... (lazy-load B)) In this case it would seem wrong to say A depends on B because it doesn't necessarily contain what it needs to load. Or maybe another test question would be. If in the blog post, you dont say "depends-on" then what happens? Would each module re-load all the code shared between main and the individual modules?

lilactown17:12:39

yes, I would say that it’s wrong to say A depends on B (or vice-versa) if both modules can potentially be loaded without the other being present

lilactown17:12:02

it might be beneficial to have B depend-on A, if A is also lazy-loaded and has some code you don’t want to bundle with the main app

lilactown17:12:53

in other words, if a module depends-on another, it means that the depended on module MUST be loaded BEFORE the module which depends on it

lilactown17:12:25

all shared code between the two will be moved to the module which was depended on

Drew Verlee17:12:50

> yes, I would say that it’s wrong to say A depends on B (or vice-versa) if both modules can potentially be loaded without the other being present So in this case, sense A contains B it doesn't need B to be loaded before. It will load B when it gets to that code.

Drew Verlee17:12:12

hitting this home hard because i think i straight up got it in my head the other way.

lilactown17:12:03

right, ostensibly you have three modules: :main :A and :B

lilactown17:12:11

in this case, you can load :A no problem without :B. so you wouldn’t say it depends-on :B. similarly, you can probably load :B without :A , since they’re both just React components and you might have other components which would lazy-load B somewhere else

lilactown17:12:55

so you want to have a third module, in this case :main , which the compiler can move the code shared between modules :A and :B so it doesn’t get downloaded and loaded twice

lilactown17:12:56

to say it another way again, depends-on is a way of specifying where shared code will be moved between a number of modules. So if you have:

{:main {:entries [app.core]}
 :A {:entries [app.A]
     :depends-on #{:main}}
 :B {:entries [app.B]
     :depends-on #{:main}}}
this will make it so that any shared code (such as React, or other library or application code shared between the two) will be moved into the module created from :main. Then when you load :A, it will only have code specific to that module. :A will kick off a dynamic, lazy load of :B, which will also only contain code that is needed to render the B component.

lilactown17:12:46

to say it even another way: depends-on is a way of specifying static dependencies on modules. in this case, you are dynamically loading another module so there’s no necessary static dependencies between the two

Drew Verlee17:12:06

That makes sense. This helps a ton. Thanks @lilactown

Drew Verlee18:12:50

@lilactown In the case where compoment A contains compoment B and say another component (C) also contains B. If you said A depends on B and C depends on B it would mean that the code in B (that was unique to B and not in main) would be lazy loaded when A or C loaded as opposed to if you didnt declear those depends on then I imagine B's code would be loaded both times (when A loaded and when B loaded).

lilactown18:12:41

if you said A depends on B and C depends on B then B would contain all of the shared code between A and C and you would not be able to load A or C without first loading B

Drew Verlee18:12:16

what seems to happen is that the B module is then loaded as part of loading :main. For context we do have B depends on Main.

lilactown18:12:14

the goal of code splitting and modules is to: 1. only ever load a line of code once 2. optimize which code gets loaded when so you will never have a case where two modules contain the same code. if you do, that’s a serious bug

Drew Verlee18:12:26

So if you have a really big module (say its A). then code splitting can only make it larger because the more things it depends on the bigger package it will load due to bundled dependencies?

lilactown18:12:56

I don’t understand what you mean in this context what “code splitting can only make it larger” means

lilactown18:12:28

let’s say you start with one big module. we’ll call it :app

lilactown18:12:00

in this case, all of your code gets bundled into one app.js file and the client downloads it all upfront and it all gets run in the browser before your app starts

lilactown18:12:22

let’s say it’s 2mb big

lilactown18:12:02

then you say: “Ah! Many of our users don’t use feature, so we’ll split that out from the main bundle and lazy-load it if the user decides to use it!”

lilactown18:12:29

so you create a new module, :feature , and you say:

{:app {:entries [app.core]}
 :feature {:entries [app.feature]
           :depends-on #{:app}}}
now you can lazy-load app.feature.

lilactown18:12:13

what the compiler does is analyze the app.feature namespace, and all of the namespaces it requires. if code or a namespace only appears in app.feature, then it will move that code into into the feature.js bundle. if code or a namespace appears in app.feature, and something that is required by app.core, then it will be moved into the app.js bundle. that way, when you load app.js it will have all of the code necessary for the app.core namespace with it. When you load feature.js, it will not contain any duplicate code that app.core and app.feature both need

lilactown18:12:35

so let’s say app.feature was pretty hefty, maybe it included a couple NPM libs that were only used there, so the app.js bundle is now 1.1mb and the feature.js bundle is 0.9mb

lilactown18:12:53

you haven’t removed any code, but you will never load more than the 2mb that you originally started with

Drew Verlee18:12:01

I did some quick diagrams and i think i understand. In addition to what you said, which i understand, here is another example. Before: A depends on main B depends on main If we add a C module and have B and A depend on it... A depends on main, C B depends on main, C C depends on main Then in the second version we have moved code that was in main to C and C will be loaded when we first load A or B and contain shared code for A and B. For a users stand point we speed up main loading and slowed down the first load of A or B (relative to what it was before).

lilactown18:12:53

right, C must be loaded before A or B

lilactown18:12:02

(I don’t think it will happen automatically for you. maybe shadow’s lazy loader is smarter than I think)

Drew Verlee18:12:50

i'm not sure what you mean by this: > I don’t think it will happen automatically for you. maybe shadow’s lazy loader is smarter than I think I was under the impression that to "load C" in this context where its a ns declared a module. You just use call the show lazy loader e.g https://github.com/thheller/code-splitting-clojurescript/blob/c9e1e9340c45969f3fc5b515fb9d095f28595c24/src/main/demo/util.clj#L3 Put another way, declearing modules in the shadow-cljs only does the code splitting. It doesnt tell your running app how to load (http request) the module. That macro helps the compiler know that it doesn't need to pull in that namespace and its deps. It also wraps your compoment so that its a js module with the component as the default.

lilactown18:12:41

I mean that if you call (lazy-load 'app.B) I don’t think it will infer that it needs to lazy-load C before it. you need to load C first, then B

lilactown18:12:17

if it’s only ever C that lazy-loads B, then you’re fine

Drew Verlee19:12:14

Sorry. Thats slightly confusing to me. In the example: A depends on main, C B depends on main, C C depends on main Given component A contains C and B contains C. I would assume that the code would look something like: main ns

(def lazy-A (lazy-component app.A/A))
(def lazy-B (lazy-component app.B/B))

(case route 
      "A" (lazy-A)
      "B" (lazy-B))
A ns
(:require [app.C])

(defn A [...] (C/lazy-C))
B ns (same as A) C ns
(defn C [...] ...)
(def lazy-C (lazy-component C))

Drew Verlee19:12:44

Would be correct. But i dont feel im explicitly telling it to load C before A or B. So do i need to call (C/lazy-C) before the route in the main ns?

lilactown19:12:03

your depends-on is backwards

lilactown19:12:31

why would A and B depend-on C, if C is only ever loaded after A and B?

lilactown19:12:31

wait I mis-read your example

lilactown19:12:07

your example doesn’t really make sense

lilactown19:12:07

my question is still relevant. Why would A and B depend on C, if you want C to only be loaded after A and B?

lilactown19:12:22

the way you have it written, though, C will not be lazy-loaded by A or B

lilactown19:12:42

C must be already available in the browser, and lazy-C is redundant

lilactown19:12:26

your code will not work because main loads A and B, A and B expect C to be there and it will break

Drew Verlee19:12:26

Two possible independent things: 1) Given what you have told me. I'm not sure its necessary. However if i change: A depends on main, C B depends on main, C C depends on main To A depends on main B depends on main C depends on main Then i get the shadow warning about C moved into main. Which would be the opposite of what we want sense we want to decrease the size of main. 2) The code example is wrong. The A and B ns shouldn't have requires on C. Its not needed as the lazy-compoment macro declears that will look it up later at the realization point of the lazy load. I'm not sure point 2 changes your observation.

lilactown19:12:36

here’s what our bundle looks like if we do nothing

lilactown19:12:56

here’s what our bundle looks like once we’ve split out modules A and B

Drew Verlee19:12:44

why does the call to C contain Lazy A and Lazy B? Is (defn C ...) supposed to be (defn main ...)?

Drew Verlee19:12:38

oh, i was looking at the indentation level. Sorry.

lilactown19:12:12

yeah that’s confusing #diagram-noob

lilactown19:12:52

here’s what your bundles look like according to your example

lilactown19:12:41

this is AFAICT, I’m ready to be proved wrong

lilactown19:12:27

this is I believe what you want

lilactown19:12:56

if you get a message saying that C is moved into main, then that means you are explicitly requiring C in A and B somewhere

Drew Verlee19:12:00

To be clear the A and B ns doesn't require (e.g (ns (:require [...C]))) C directly, that was a mistake in my example that i tried to explain above. They would both use the lazy-component to load C.

lilactown19:12:30

then I’m not sure why you are seeing messages about C being moved into main

Drew Verlee19:12:42

I think its because if a module is shared, then by default it goes into the default module (e.g main) unless you explicitly say other modules depend-on it.

lilactown19:12:22

the only way a module can be shared is if you put it in a require in a namespace

lilactown19:12:27

in both A and B

lilactown19:12:50

if you are truly lazy loading it, it will not appear in any namespace requires in A or B modules. that includes any namespaces that app.A and app.B requires

Drew Verlee19:12:46

The two statements seem to imply you can't share and lazy load the same module. To share: ns require C in A and B To lazy: dont ns require C in A and B But i might be "unable to see the forset through the trees". That is, im misreading details and not letting the full picture lend context.

lilactown19:12:05

yes that’s correct

lilactown19:12:23

if you require a namespace, it must be statically available when your namespace is evaluated

lilactown19:12:56

if you lazy-load it, then you don’t care that it’s available when your namespace is evaluated

lilactown19:12:46

I guess I would ask the question: why would want to share and lazy load the same module?

Drew Verlee20:12:30

> share and lazy load the same module? That isn't a goal in itself. I believe i understand, your saying that its typical to lazy load the components (A and B) (e.g lazy-load A) and have those depend on a shared module (C). But its not necessary to lazy load the shared module. If fact, the sharing is done by the Google Closure compiler which needs that static require to do its job.

lilactown20:12:38

right, I mean it might be lazy-loaded but it doesn’t need to be. C will be moved into a module that A and B depend on

lilactown20:12:50

I created a barebones example of this: https://github.com/Lokeh/module-example

lilactown20:12:44

the above is how you would do it if you wanted to split the C namespace from the main module

lilactown20:12:01

you have A, B, C all split and depending on main. A and B lazy-load C

lilactown20:12:28

if you wanted C to be separate from the main bundle, but A and B require it statically, then it’s a little more complicated

lilactown20:12:09

you’d have to do something like this:

lilactown20:12:39

this is what I meant by “loading C will not happen automatically for you” if you lazy-load A and B, but they have a shared module C, then you need to load the shared module before A and B

thheller20:12:00

@drewverlee @lilactown I'm too tired too go through your entire conversation but one thing that stands out to me is the confusion what :depends-on means. You need to differentiate between "depends on" and "uses".

thheller20:12:02

> In this case it would seem wrong to say A depends on B because it doesn't necessarily contain what it needs to load.

thheller20:12:25

A uses B. B may not be aware of A at all.

☝️ 4
thheller20:12:55

code-splitting in CLJS/Closure really doesn't make too much sense at the component level

Drew Verlee21:12:30

Thanks Heller. I understand. I dont want to eat up too much of anyones times. I plan on writting a blog post that outlines what i learned so hopefully it can be of help to others.