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?
?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
then I'd recommend just having the test build include your regular app?
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
two release builds can work but even they might cause problems with each other
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 !
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.
yeah what you are trying to do is not possible
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
lots more for the user to download
if you need to share data you are doing to need to use transit/edn
but ideally don't have separate builds in the first place
did you try a release
build? unless you are on version 2.12.5
the :force-library-injection
only works for release
builds
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?@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.
it is NOT needed by reagent
what gives you that idea?
reagent already declares its own dependency on react. https://github.com/reagent-project/reagent/blob/master/src/reagent/core.cljs#L4
I'd like to understand why you are repeating it?
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.
ok so a couple notes
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
the problem is that webpack is not aware of the classpath
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?
or is it to get less support?
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.
as for requiring assets
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.
馃槃
so not sure that is really something shadow-cljs needs to do
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
but all CLJS code uses [react :as react]
so :as**
which would be import * as react from "react"
emitting an ESM import is difficult for that reason
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
for the code you control you could easily just switch the references
but that is not so easy for library code such as reagent
so the only reason it uses require
in the first place is compatibility
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/
that react via reagent alone doesn't work is a bug
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 <http://my.super.app|my.super.app> {:external/assets ["./foo.less"]} (:require ...))
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
Actually 2. is more suprising for me.
don't know why you are messing with provideplugin?
just compile the file with webpack without any plugins or special config
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 is the overall goal for this?
> I want to tree-shake antd-design
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?
in ns :require
no
Oh. But still I can use js/require am I right? 馃檪
Probably import is special statement and cannot be used like js/require 馃槃
adding either import
or js/require
is not valid
since ONLY the external index file will be processed by webpack
not the CLJS parts
I could add an extra mechanism to tell webpack to also include other stuff
but it will not be part of ns :require
could be ns metadata or so
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 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.
the ns you changed + all those that have a direct require for that one
init-fn ns only if that has a direct require, otherwise no
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
@thosmos version conflict. not using the updated 1.10.844 CLJS with 2.12.+ won't work
OK thanks will upgrade to that
That worked