This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-12-20
Channels
- # adventofcode (38)
- # announcements (8)
- # aws (4)
- # babashka (131)
- # beginners (263)
- # calva (2)
- # clj-kondo (12)
- # cljdoc (12)
- # cljsrn (3)
- # clojure (122)
- # clojure-europe (3)
- # clojure-finland (2)
- # clojure-nl (13)
- # clojure-uk (80)
- # clojured (1)
- # clojuredesign-podcast (3)
- # clojurescript (78)
- # core-async (19)
- # cursive (19)
- # datomic (7)
- # duct (10)
- # events (1)
- # fulcro (7)
- # graalvm (12)
- # graphql (3)
- # juxt (4)
- # malli (10)
- # music (3)
- # nrepl (4)
- # off-topic (25)
- # pathom (4)
- # pedestal (1)
- # re-frame (78)
- # reagent (8)
- # shadow-cljs (91)
- # sql (8)
- # vim (3)
- # xtdb (2)
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
I have done :Piggieback :module-name
instead of the :CljEval
, i am wondering what the difference is
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:you are welcome! it makes me happy to give back to the awesome community, even a tiny bit. š
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?
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
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
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
> 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.
hitting this home hard because i think i straight up got it in my head the other way.
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
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
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.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
That makes sense. This helps a ton. Thanks @lilactown
@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).
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
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.
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
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?
I donāt understand what you mean in this context what ācode splitting can only make it largerā means
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
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!ā
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
.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
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
you havenāt removed any code, but you will never load more than the 2mb that you originally started with
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).
(I donāt think it will happen automatically for you. maybe shadowās lazy loader is smarter than I think)
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.
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
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))
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?
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?
your code will not work because main loads A and B, A and B expect C to be there and it will break
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.
why does the call to C contain Lazy A and Lazy B? Is (defn C ...)
supposed to be (defn main ...)?
oh, i was looking at the indentation level. Sorry.
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
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.
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.
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
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.
if you require a namespace, it must be statically available when your namespace is evaluated
if you lazy-load it, then you donāt care that itās available when your namespace is evaluated
I guess I would ask the question: why would want to share and lazy load the same module?
> 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.
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
I created a barebones example of this: https://github.com/Lokeh/module-example
the above is how you would do it if you wanted to split the C namespace from the main module
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
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
@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".
> In this case it would seem wrong to say A depends on B because it doesn't necessarily contain what it needs to load.
code-splitting in CLJS/Closure really doesn't make too much sense at the component level
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.