Fork me on GitHub
#shadow-cljs
<
2018-06-01
>
wilkerlucio02:06:34

I'm trying to port a big app to use shadow, I'm trying to start by using the lein profile, but when I try to compile I'm getting:

wilkerlucio02:06:36

Exception in thread "main" java.io.FileNotFoundException: Could not locate cljs/externs__init.class or cljs/externs.clj on classpath., compiling:(shadow/build/cljs_bridge.clj:1:1)
	at clojure.lang.Compiler.load(Compiler.java:7526)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:2793)
	at shadow.build.classpath$eval17191$loading__6434__auto____17192.invoke(classpath.clj:1)
	at shadow.build.classpath$eval17191.invokeStatic(classpath.clj:1)
	at shadow.build.classpath$eval17191.invoke(classpath.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)

wilkerlucio02:06:44

any idea what that can be?

wilkerlucio02:06:34

I tried moving the deps and source-paths directly to shadow-cljs.edn, it works there, so it's something around lein integration

thheller06:06:22

@wilkerlucio probably a dependency pulling in an old CLJS version. when using shadow-cljs it adds :exclusions [org.clojure/clojurescript] automatically for every dep to avoid that

thheller06:06:44

@kwladyka this is a problem due to loading goog.History. It uses document.write to insert an iframe dynamically.

(defn do-route [token]
  (->> token
       (bidi/match-route route)
       (handler)))

(defn hook-browser-navigation! []
  (doto (History.)
    (goog-events/listen
      EventType/NAVIGATE
      (fn [event]
        (do-route (.-token event))))
    (.setEnabled true)))

(defn dev-setup []
  (when config/debug?
    (println "dev mode")))

(defn ^:dev/after-load mount-root []
  (re-frame/clear-subscription-cache!)
  (reagent/render [views/page]
    (.getElementById js/document "app")))

(defn ^:export init []
  (re-frame/dispatch-sync [::events/initialize-db])
  (dev-setup)
  (mount-root)
  (hook-browser-navigation!))

thheller06:06:46

this should work. basically you must avoid constructing a History object twice. so the above calls it in init once and not in start which is called for each live-reload.

thheller06:06:53

maybe makes sense to call it before mount-root. not sure what makes most sense.

thheller06:06:01

@tony.kay I use react v16. a React global only exists when you use cljsjs.react since by default there will be no React global exported?

thheller06:06:10

if you just (:require ["react" :as x]) that won't create a global?

thheller06:06:04

but that should be a problem during dev then too?

thheller06:06:00

maybe you "accidentally" get React during dev due to some preload?

kwladyka07:06:42

@thheller ah thank you. I don’t know how I was checking it yesterday. Maybe because it was after midnight 🙂

kwladyka07:06:18

What is your personal opinion about using such solution to let users copy & past URLs? Do you know something better? What do you use?

thheller07:06:37

what do you mean? using urls to route application state? that is pretty much considered best practice and good.

thheller07:06:21

although I probably recommend using goog.history.Html5History instead since that uses normal paths instead of append #/...

kwladyka07:06:25

yes, like url#/foo/bar

thheller07:06:42

but that requires server support so might not work always

kwladyka07:06:43

exactly, that is something what I am asking about

kwladyka07:06:58

I don’t have experience with it

kwladyka07:06:13

oh, thank your for that. I have to read about it.

kwladyka07:06:58

Oh I guess you talk about support for path to load right file

thheller07:06:00

the idea is the same, use the url to construct the initial state of your application. either the url can be /foo/bar or url#/foo/bar. the first requires server support while the second doesn't. but beyond that they pretty much work the same

thheller07:06:31

yeah the server must be aware of all the urls you may be constructing client side. with url# it just needs one

kwladyka07:06:31

That is interesting web browser doesn’t reload page. I have to read about it 🙂

kwladyka07:06:04

Will it work with shadow-cljs? When I will have localhost:8020/foo/bar/baz and click refresh?

thheller07:06:16

that is commonly known as pushState routing. the shadow-cljs server will serve the index.html by default for all urls that don't exist otherwise

kurt-o-sys08:06:09

if one wants to add spec instrumentation during dev: that's done with build-hooks, right?

kurt-o-sys08:06:15

oh, hooks are clojure functions? - need plain cljs functions to instrument only during dev. Will check docs again 😛

mitchelkuijpers10:06:13

@kurt-o-sys We had a lot of trouble adding spec-instrumentation, if you get it working correctly we would be interested in how you did that

tony.kay12:06:52

@thheller Well, it is a fulcro project, and Fulcro works fine with React 16. In fact, this project has worked in release mode before, it just started breaking. I tried backing out to an older versino of the compiler, but that does not fix it. I suspect perhaps some new dependency was added that perhaps confused things. If I compile in dev mode it is fine. If I do release with any level of optimization it breaks, but differently based on which optimization level I choose. At whitespace it crashes on a line that is trying to set goog.debug.Error= something (debug isn't defined). At simple and above it is React.

tony.kay12:06:14

the preloads is a good lead. I'll try removing them and see if that breaks dev mode

tony.kay13:06:19

@thheller You are right: removing the preloads breaks it…so it must be that react isn’t being pulled in correctly

tony.kay13:06:30

Ah, I think it was Fulcro…I was missing a require of cljsjs.react in one of the files, which confuses the dep order. Trying a fix

tony.kay13:06:51

yeah, that was it

kurt-o-sys14:06:41

@mitchelkuijpers I'm not 100% sure if it works as it should. I get too many validation errors, unless it's justified I get them 🙂 - still analyzing that part. However, this is what I did:

(ns ui-app.core
  (:require-macros [secretary.core :refer [defroute]])
  (:import goog.History)
  (:require ...
            [orchestra-cljs.spec.test :as st]
            ...))
 
...

(defn ^:dev/after-load start
  []
  (println "starting...")
  (let [...
        dev        ...
        ...]
    (when dev
      (println "instrumenting...")
      (st/instrument))
    ...))

...

kurt-o-sys14:06:31

in another namespace:

(ns ...
  (:require ...
            [orchestra.core :refer-macros [defn-spec]]
            ...))
 
(defn-spec some-fun #(repeatedly true)
  [arg (s/cat :arg #(repeatedly true))]
  ...)

kurt-o-sys14:06:40

(the repeatedly true works fine :)

mitchelkuijpers14:06:37

I thinks our problem was that it would only instrument directly required namespaces

kurt-o-sys14:06:09

it seems to instrument this, but changing #(repeatedly true) to anything else seems to make the specs fail for now

kurt-o-sys14:06:12

well, it's working... just had wrong specs 🙂

lilactown18:06:27

so I have a super bizarre problem / use-case that I'm wondering if anyone has ideas for

lilactown18:06:08

I'm currently server-rendering my pages using reagent on Node.js, as well as building client-side code with the same codebase - all via shadow-cljs

lilactown18:06:42

what I'd like to do is use module-splitting to split up some of the heavier parts of my client-side bundle, and conditionally load them if a certain component is server-rendered

lilactown18:06:53

e.g. I have a feature-container component that is conditionally rendered by the server, and when rendered it could put some code on the page that would dynamically load the feature client-side module

lilactown18:06:07

is this at all feasible? 😅

lilactown18:06:02

right now I basically have the client-side entry point look for a specific ID and if so, render that part of the page - so I could make that load the module.

lilactown18:06:07

but I'd like to move that behavior into the component itself instead of having to modify the entry point every time I add a new feature

thheller19:06:51

@lilactown in my setup I just collect which modules should be loaded and then emit that info into the HTML

thheller19:06:34

manifest.edn can help automate the config part

thheller19:06:36

I have helpers in my server code that emit stuff like (cljs 'some.app.entry 1 2 3) which basically calls (some.app/entry 1 2 3) and figures out which module some.app is in so ensures that the module is loaded first

thheller19:06:00

but that stuff is currently only used in an internal CMS admin app so can't share that part

thheller19:06:09

didnt' extract the code for that (yet)

lilactown19:06:32

nice. so if I'm understanding, (cljs 'some.app.entry 1 2 3) gets translated to some JS that gets put on the page that will call (some.app/entry 1 2 3)?

thheller19:06:11

this method basically

thheller19:06:42

so it emits the script tag and then later decides which modules to load

thheller19:06:54

when the modules load they look for the special script tags and call them

lilactown19:06:29

I think I can use that exact strategy with shadow.loader/load to do what I want 😄 yeah?

thheller19:06:06

yep that works. I just track is on the server and directly emit <script> tags for the required modules

thheller19:06:27

and also emit a <link rel="preload" ...> to make it even faster

thheller19:06:37

but shadow.loader totally works too

lilactown19:06:24

:thinking_face: would shadow.loader be a lot slower?

thheller19:06:51

with preloads the code will be available ASAP

lilactown19:06:52

I'm just not sure how I would determine which file to load. I guess that's what the manifest.edn is for?

thheller19:06:09

without you first download the base module, wait for parse and eval, and then trigger download of the additional modules

thheller19:06:21

exactly, manifest.edn should contain everything you need. it is what I use.

lilactown19:06:55

:thinking_face: OK so I would parse manifest.edn, and add the file according to the module ns passed in. but it does necessitate then that I have to have some custom way to know when the module has been loaded (like what your blog has with (ns-ready))

thheller19:06:52

yeah shadow.api + ns-ready you still need to do in all your :entries that have these exported methods basically

thheller19:06:44

if you use shadow.loader you may however use the load deferred to do stuff on load

lilactown19:06:50

would shadow.loader respect the preloads? as in, would it refetch the module or realize it was already loaded?

lilactown19:06:01

or would the browser just cache it anyway

thheller19:06:32

yes preloads work

lilactown19:06:30

awesome! thanks for the help 😄