This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-04-15
Channels
- # announcements (2)
- # babashka (41)
- # beginners (10)
- # calva (62)
- # chlorine-clover (70)
- # cider (19)
- # clara (2)
- # clj-http (1)
- # clj-kondo (1)
- # cljs-dev (3)
- # cljsrn (8)
- # clojure (168)
- # clojure-austin (1)
- # clojure-australia (2)
- # clojure-canada (1)
- # clojure-europe (15)
- # clojure-france (9)
- # clojure-nl (3)
- # clojure-serbia (5)
- # clojure-spec (14)
- # clojure-uk (8)
- # clojurescript (14)
- # community-development (30)
- # core-async (42)
- # core-typed (1)
- # datahike (2)
- # datalog (23)
- # datomic (4)
- # emacs (4)
- # figwheel-main (4)
- # fulcro (67)
- # ghostwheel (4)
- # girouette (1)
- # gorilla (7)
- # graalvm (11)
- # heroku (8)
- # integrant (42)
- # jobs (6)
- # jobs-discuss (47)
- # kaocha (7)
- # lambdaisland (2)
- # leiningen (5)
- # lsp (29)
- # off-topic (1)
- # pathom (9)
- # portal (6)
- # re-frame (5)
- # reagent (11)
- # releases (6)
- # remote-jobs (10)
- # shadow-cljs (112)
- # testing (55)
- # vrac (1)
I am compiling 2 independent CLJS projects A
and B
with shadow-cljs, and load them both into the same browser webpage as compiled JS scripts. I am trying to make A
read a CLJS hashmap produced by B
.
The transmission of the data is made via JS property js/window.b-data
. This part is working.
Now, when A
runs (get b-data :foobar)
it returns nil
. The keyword is not found because :foobar
in A
is different than :foobar
in B
.
;; We are in A's runtime.
(def b-foobar (first (keys js/window.b-data)))
(keyword-identical? :foobar b-foobar) ;; false
(identical? (.-fqn k) (.-fqn :ui/alert)) ;; true
(keyword? b-foobar) ;; false
The problem of (get b-data :foobar)
is that b-foobar
fails keyword?
.
(core/defmacro keyword? [x]
(bool-expr `(instance? Keyword ~x)))
Maybe Keyword
is a different in A
and in B
?
Would there be a way with Shadow-CLJS to make b-data
pass the predicate keyword?
?I found the definition of Keyword
inside cljs/core.js
:
cljs.core.Keyword = (function (ns,name,fqn,_hash){
this.ns = ns;
this.name = name;
this.fqn = fqn;
this._hash = _hash;
this.cljs$lang$protocol_mask$partition0$ = 2153775105;
this.cljs$lang$protocol_mask$partition1$ = 4096;
})
Since both compiled CLJS runtime are using a different function (the same implementation but different "instance"), it makes sense that the instance?
fails.I guess that there is no way for shadow-cljs to help, here.
and I strongly recommend against doing it at all, two separate builds means two separate cljs.core and whatever other dependencies they might be sharing
My use case is Cypress tests (`A`) with my webapp (`B`) via https://github.com/viesti/cypress-clojurescript-preprocessor
I try to read the values in re-frame.db/app-db
or create an exported function in your main app that you can call to get the DB as transit or so
including two separate development builds at the same time is guaranteed to cause issues as well
I succeeded using a hack, but it is very brittle
(defn rewrite [x]
(walk/prewalk
(fn [x]
(case (pr-str (type x))
"cljs.core/Symbol" (symbol (.-ns ^js x) (.-name ^js x))
"cljs.core/Keyword" (keyword (.-ns ^js x) (.-name ^js x))
x))
x))
you are basically asking for trouble, nothing will work as expected. don't do this. I can't do more than warn you 馃槈
I tried your approach and exported a function which spits the db as edn in a string. It works very well. Thank you !
1. How can I provide "react" and react-dom using :provider :external and using webpack.ProvidePlugin? This is what I've tried:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './public/js/index.js',
mode: 'development',
output: {
path: __dirname + '/public/js',
filename: "libs.js"
},
devServer: {
stats: 'errors-only',
contentBase: path.resolve(__dirname, '/public/js'),
publicPath: '/'
},
plugins: [
new webpack.ProvidePlugin({
react: 'react',
ReactDOM: 'react-dom',
})
],
module: {
rules: [
]
}
};
Still getting:
app.js:1563 Error: Dependency: react-dom not provided by external JS. Do you maybe need a recompile?
at shadow$bridge (index.js:14)
at eval (shadow.js.shim.module$react_dom.js? [sm]:3)
at eval (<anonymous>)
at Object.goog.globalEval (app.js:497)
at Object.env.evalLoad (app.js:1560)
at app.js:1699
env.evalLoad @ app.js:1563
(anonymous) @ app.js:1699
11:27:52.383
2. Btw: Specifying global require like (:require ["react"]) does not work. It has to be (:require ["react" :as react]). Why?Actually 2. is more suprising for me.
the external index js does not require that, only if you add additional custom js you may need it for that?
Stopped messing with providePlugin. I've used something like this instead:
(ns web.globals
(:require
["react" :as react]
["react-dom" :as react-dom]))
Still I don't understand why it needs this :as 馃槙I need webpack config. I want to tree-shake antd-design.
@thheller Could you please elaborate what you mean by custom js?
Ok. I think I found a bug in shadow$bridge. Without :as the dependency is not registered. Shouldn't (:require ["react"]) generate require('react')? in external-provider file? This is with :as:
// WARNING: DO NOT EDIT!
// THIS FILE WAS GENERATED BY SHADOW-CLJS AND WILL BE OVERWRITTEN!
var ALL = {};
ALL["@ant-design/icons"] = require("@ant-design/icons");
ALL["antd/lib/divider"] = require("antd/lib/divider");
ALL["antd/lib/input/Search"] = require("antd/lib/input/Search");
ALL["antd/lib/layout/Sider"] = require("antd/lib/layout/Sider");
ALL["antd/lib/layout/layout"] = require("antd/lib/layout/layout");
ALL["react"] = require("react");
ALL["react-dom"] = require("react-dom");
global.shadow$bridge = function shadow$bridge(name) {
var ret = ALL[name];
if (ret === undefined) {
throw new Error("Dependency: " + name + " not provided by external JS. Do you maybe need a recompile?");
}
return ret;
};
shadow$bridge.ALL = ALL;
This is without :as:
// WARNING: DO NOT EDIT!
// THIS FILE WAS GENERATED BY SHADOW-CLJS AND WILL BE OVERWRITTEN!
var ALL = {};
ALL["@ant-design/icons"] = require("@ant-design/icons");
ALL["antd/lib/divider"] = require("antd/lib/divider");
ALL["antd/lib/input/Search"] = require("antd/lib/input/Search");
ALL["antd/lib/layout/Sider"] = require("antd/lib/layout/Sider");
ALL["antd/lib/layout/layout"] = require("antd/lib/layout/layout");
global.shadow$bridge = function shadow$bridge(name) {
var ret = ALL[name];
if (ret === undefined) {
throw new Error("Dependency: " + name + " not provided by external JS. Do you maybe need a recompile?");
}
return ret;
};
shadow$bridge.ALL = ALL;
first of all regarding the :as
. without it is is basically unusable so I don't understand why that is even a question?
what does that mean for you? I mean you already have separate requires which basically is tree shaking already?
I'm unsure why the external index doesn't include requires without :as
and that could be considered a bug but I'd still like to understand why you don't want the :as
? I mean you do intend to use react
right?
by custom JS I mean JS code you write that you want to compile with webpack and include alongside the external index
@thheller Ok let me explain myself a little bit. I don't know whether I will ever use "react" in the code like react/createElement or so. "react" and "react-dom" are only required so that I can use reagent, therefore requiring "react" :as react is now something I don't need. (it's just triggers clj-kondo warning) Regarding ant-design yes you're right. Now it's treeshaked since I'm directly specifying the path to component, but I would rather use ant plugin for webpack and simply refer the components I want. Another story is including css which I also plan to do with webpack. At some point I want to require less in shadow-cljs like so: (:require ["./core.less"]). Don't know whether it will work with shadow-cljs. https://ant.design/docs/react/use-with-create-react-app
ok so. reagent.dom will be including react-dom so if you don't plan on using it in your code then don't
Ahhh.. I think I got you!
tree shaking in webpack is based on the import
statements but since the shadow-cljs external index generates require
that won't work
I did start to implement with supporting :refer
to emit import
instead but that'll have a few compatibility issues so I used require for now
(:require ["./core.less"])
will not work since classspath relative imports will always be processed by shadow-cljs directly and only supports JS
Ok I will somehow live with using full path for importing ant components. Is it possible to somehow bypass shadow-cljs so that "./core.less" will not be processed by shadow-cljs?
Oh. But still I can use js/require am I right? 馃檪
Probably import is special statement and cannot be used like js/require 馃槃
would be much easier to talk about this if you setup a demo repo of what you'd like to have
doesn't need to be functional but something code wise we can talk about. its all a bit abstract in a slack thread
Sure. I will provide a demo.
Thank you @thheller
@thheller Here is a demo of what I'm trying to achieve: https://github.com/FieryCod/ant-design-in-shadow-cljs Basically I want to import less, and scss files into .cljs so that webpack could handle compilation of both.
can you explain why this exists? https://github.com/FieryCod/ant-design-in-shadow-cljs/blob/master/src/web/globals.cljs
Yep. It's needed by reagent. Alternatively I can put those line in app.cljs.
reagent already declares its own dependency on react. https://github.com/reagent-project/reagent/blob/master/src/reagent/core.cljs#L4
Ok. Just please remove those lines. You will see that shadow does not include react.
We're going through the stuff I already described above.
this will never work and there is no way to make it work https://github.com/FieryCod/ant-design-in-shadow-cljs/blob/master/src/web/app.cljs#L9
but you only seem to want to include it for the side effects right? it is never actually used anywhere
so the path you include that way needs to be relative from the output file, not the source file
and the point of this repo is that you want to replace ["antd/lib/divider" :default AntDivider]
with just ["antd" :refer (Divider)]
or whatever that would be?
I want to 1. Replace ["antd/lib/divider" :default AntDivider] with ["antd" :refer (Divider)] 2. Include less, scss via webpack.
I want both 馃槃
and antd doesn't require any additional setup to support this? thought it required a plugin to do that?
yes but it isn't setup anywhere. I'm trying to figure out what is actually needed from the shadow-cljs side
There are two things. 1. Generate imports not require (you said that it requires some work therefore I'll probably wait for it) 2. Allow to require assets in external provider.
I will add babel-loader and babel-plugin-import to project in couple of hours.
you are now using webpack to process public/js/index.js
. you could write a manual public/js/index-with-styles.js
or whatever that just does require("./index.js"); require("./ant.less")
etc
I'm wondering whether it's possible to create a .js file which I import in shadow-cljs that would point to assets.
generating imports is a problem for one simple reason. the convention is that commonjs packages such as react must be imported as import React from "react"
which in JS terms is a singular default export
Ahhh.. Got it
the code actually already exists to generate ESM https://github.com/thheller/shadow-cljs/blob/989590bb373e56b1d0ccf3a4029e2d3ec294ffe3/src/main/shadow/build/targets/external_index.clj#L68-L84
the problem is that pretty much all CLJS libs break because of their now "incorrect" requires
I don't really have a solution for this but the problem will be present until all JS libraries uniformly written in ESM
and everything will likely change again once packages such as antd adopt https://webpack.js.org/guides/package-exports/
I've forgotten how much js world is a mess by working with cljs for 2 years.
Totally understandable why you're resisting to adopt asset require in shadow-cljs.
well the external code already has partial support for (ns
it is useful to be able to express such things in code, just doing it via require
is bad IMHO
but JS doesn't have namespaces or metadata so they have a harder time to get something like this 馃槢
:external/assets are great! Love it
@thheller reading your post: https://code.thheller.com/blog/shadow-cljs/2019/08/25/hot-reload-in-clojurescript.html. What will be reloaded when a cljs file changes. Will the ns containing the init-fn be reloaded, of the ns just containing the modified code.
so are you saying the ns has in-direct require to the changed ns won鈥檛 be re-required ?
Seems not documented: https://shadow-cljs.github.io/docs/UsersGuide.html#_hot_code_reload
I see: shadow-cljs
聽will only automatically recompile the direct dependents since in theory dependencies further up cannot be directly affected by those interface changes.
> On the backend the shadow-cljs watch app process will compile a namespace when it is changed and also recompile the direct dependents of that namespace (ie. namespaces that :require it). This is done to ensure that changes to the namespace structure are reflected properly and code isn鈥檛 using old references.
you can change the reload strategy to reload all files or set :dev/always
metadata if you want to force recompile a namespace on every change
I鈥檓 getting some errors when watching my project after updating the version to 2.12.5
. Not sure how to troubleshoot this:
[2021-04-15 12:37:05.387 - WARNING] :shadow.cljs.devtools.server/nrepl-ex
CompilerException Unexpected error macroexpanding if-ns at (cider/piggieback.clj:22:1).
...
[2021-04-15 12:44:11.955 - WARNING] :shadow.cljs.devtools.server.util/handle-ex - {:msg {:type :start-autobuild}}
NoClassDefFoundError Could not initialize class cljs.repl__init