shadow-cljs

https://github.com/thheller/shadow-cljs | https://github.com/sponsors/thheller | https://www.patreon.com/thheller
2021-04-15T05:48:06.225600Z

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? ?

2021-04-15T07:39:40.227500Z

My use case is Cypress tests (`A`) with my webapp (`B`) via https://github.com/viesti/cypress-clojurescript-preprocessor

2021-04-15T07:40:00.227700Z

I try to read the values in re-frame.db/app-db

thheller 2021-04-15T07:41:52.228Z

then I'd recommend just having the test build include your regular app?

thheller 2021-04-15T07:42:58.228200Z

or create an exported function in your main app that you can call to get the DB as transit or so

馃憤 1
thheller 2021-04-15T07:43:39.228400Z

including two separate development builds at the same time is guaranteed to cause issues as well

thheller 2021-04-15T07:44:06.228700Z

two release builds can work but even they might cause problems with each other

2021-04-15T07:54:34.229100Z

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))

thheller 2021-04-15T07:57:38.229400Z

you are basically asking for trouble, nothing will work as expected. don't do this. I can't do more than warn you 馃槈

馃槄 1
2021-04-15T09:18:07.231500Z

I tried your approach and exported a function which spits the db as edn in a string. It works very well. Thank you !

2021-04-15T06:04:50.225700Z

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.

2021-04-15T06:11:01.225900Z

I guess that there is no way for shadow-cljs to help, here.

thheller 2021-04-15T06:49:57.226100Z

yeah what you are trying to do is not possible

thheller 2021-04-15T06:50:30.226300Z

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

thheller 2021-04-15T06:50:35.226500Z

lots more for the user to download

thheller 2021-04-15T06:50:52.226700Z

if you need to share data you are doing to need to use transit/edn

thheller 2021-04-15T06:51:10.227100Z

but ideally don't have separate builds in the first place

thheller 2021-04-15T06:52:44.227300Z

did you try a release build? unless you are on version 2.12.5 the :force-library-injection only works for release builds

Karol W贸jcik 2021-04-15T09:29:43.234Z

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?

Karol W贸jcik 2021-04-16T08:24:33.250900Z

@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.

thheller 2021-04-16T08:59:16.251300Z

can you explain why this exists? https://github.com/FieryCod/ant-design-in-shadow-cljs/blob/master/src/web/globals.cljs

Karol W贸jcik 2021-04-16T11:09:42.252Z

Yep. It's needed by reagent. Alternatively I can put those line in app.cljs.

thheller 2021-04-16T11:16:25.253Z

it is NOT needed by reagent

thheller 2021-04-16T11:16:28.253200Z

what gives you that idea?

thheller 2021-04-16T11:18:04.253400Z

reagent already declares its own dependency on react. https://github.com/reagent-project/reagent/blob/master/src/reagent/core.cljs#L4

thheller 2021-04-16T11:18:18.253700Z

I'd like to understand why you are repeating it?

Karol W贸jcik 2021-04-16T11:19:15.253900Z

Ok. Just please remove those lines. You will see that shadow does not include react.

Karol W贸jcik 2021-04-16T11:20:21.254200Z

We're going through the stuff I already described above.

thheller 2021-04-16T11:31:30.254500Z

ok so a couple notes

thheller 2021-04-16T11:32:18.254700Z

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

thheller 2021-04-16T11:33:16.255Z

but you only seem to want to include it for the side effects right? it is never actually used anywhere

thheller 2021-04-16T11:33:28.255200Z

the problem is that webpack is not aware of the classpath

thheller 2021-04-16T11:33:43.255400Z

so the path you include that way needs to be relative from the output file, not the source file

thheller 2021-04-16T11:35:05.255600Z

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?

thheller 2021-04-16T11:36:56.255900Z

or is it to get less support?

Karol W贸jcik 2021-04-16T11:37:00.256100Z

I want to 1. Replace ["antd/lib/divider" :default AntDivider] with ["antd" :refer (Divider)] 2. Include less, scss via webpack.

Karol W贸jcik 2021-04-16T11:37:10.256300Z

I want both 馃槃

thheller 2021-04-16T11:37:24.256500Z

and antd doesn't require any additional setup to support this? thought it required a plugin to do that?

Karol W贸jcik 2021-04-16T11:37:53.256700Z

There is https://www.npmjs.com/package/babel-plugin-import

thheller 2021-04-16T11:38:43.257Z

yes but it isn't setup anywhere. I'm trying to figure out what is actually needed from the shadow-cljs side

Karol W贸jcik 2021-04-16T11:40:47.257200Z

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.

Karol W贸jcik 2021-04-16T11:41:38.257400Z

I will add babel-loader and babel-plugin-import to project in couple of hours.

thheller 2021-04-16T11:41:55.257600Z

as for requiring assets

thheller 2021-04-16T11:42:50.257800Z

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

Karol W贸jcik 2021-04-16T11:42:53.258Z

I'm wondering whether it's possible to create a .js file which I import in shadow-cljs that would point to assets.

Karol W贸jcik 2021-04-16T11:43:00.258200Z

馃槃

thheller 2021-04-16T11:43:02.258400Z

so not sure that is really something shadow-cljs needs to do

thheller 2021-04-16T11:44:11.258600Z

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

thheller 2021-04-16T11:44:29.258800Z

but all CLJS code uses [react :as react] so :as**

thheller 2021-04-16T11:44:40.259Z

which would be import * as react from "react"

thheller 2021-04-16T11:45:09.259200Z

emitting an ESM import is difficult for that reason

Karol W贸jcik 2021-04-16T11:45:42.259400Z

Ahhh.. Got it

thheller 2021-04-16T11:46:17.259900Z

the problem is that pretty much all CLJS libs break because of their now "incorrect" requires

thheller 2021-04-16T11:47:05.260200Z

for the code you control you could easily just switch the references

thheller 2021-04-16T11:47:24.260400Z

but that is not so easy for library code such as reagent

thheller 2021-04-16T11:47:43.260600Z

so the only reason it uses require in the first place is compatibility

thheller 2021-04-16T11:48:20.260800Z

I don't really have a solution for this but the problem will be present until all JS libraries uniformly written in ESM

thheller 2021-04-16T11:53:27.261Z

and everything will likely change again once packages such as antd adopt https://webpack.js.org/guides/package-exports/

thheller 2021-04-16T11:54:11.261300Z

that react via reagent alone doesn't work is a bug

Karol W贸jcik 2021-04-16T11:59:10.261500Z

I've forgotten how much js world is a mess by working with cljs for 2 years.

Karol W贸jcik 2021-04-16T11:59:39.261700Z

Totally understandable why you're resisting to adopt asset require in shadow-cljs.

thheller 2021-04-16T12:02:58.262Z

well the external code already has partial support for (ns <http://my.super.app|my.super.app> {:external/assets ["./foo.less"]} (:require ...))

鉂わ笍 1
thheller 2021-04-16T12:03:27.262200Z

it is useful to be able to express such things in code, just doing it via require is bad IMHO

thheller 2021-04-16T12:03:46.262400Z

but JS doesn't have namespaces or metadata so they have a harder time to get something like this 馃槢

Karol W贸jcik 2021-04-16T12:22:44.262700Z

:external/assets are great! Love it

Karol W贸jcik 2021-04-15T09:58:45.234100Z

Actually 2. is more suprising for me.

thheller 2021-04-15T10:13:10.234300Z

don't know why you are messing with provideplugin?

thheller 2021-04-15T10:14:15.234500Z

just compile the file with webpack without any plugins or special config

thheller 2021-04-15T10:14:34.234700Z

the external index js does not require that, only if you add additional custom js you may need it for that?

Karol W贸jcik 2021-04-15T10:19:59.234900Z

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 馃槙

Karol W贸jcik 2021-04-15T10:20:24.235100Z

I need webpack config. I want to tree-shake antd-design.

Karol W贸jcik 2021-04-15T10:51:09.235400Z

@thheller Could you please elaborate what you mean by custom js?

Karol W贸jcik 2021-04-15T10:58:16.235600Z

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;

thheller 2021-04-15T11:03:17.235900Z

first of all regarding the :as. without it is is basically unusable so I don't understand why that is even a question?

thheller 2021-04-15T11:04:11.236100Z

what is the overall goal for this?

thheller 2021-04-15T11:04:12.236300Z

> I want to tree-shake antd-design

thheller 2021-04-15T11:04:36.236500Z

what does that mean for you? I mean you already have separate requires which basically is tree shaking already?

thheller 2021-04-15T11:05:41.236700Z

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?

thheller 2021-04-15T11:06:50.236900Z

by custom JS I mean JS code you write that you want to compile with webpack and include alongside the external index

Karol W贸jcik 2021-04-15T11:17:30.237200Z

@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

thheller 2021-04-15T12:53:10.237600Z

ok so. reagent.dom will be including react-dom so if you don't plan on using it in your code then don't

Karol W贸jcik 2021-04-15T12:53:47.237800Z

Ahhh.. I think I got you!

thheller 2021-04-15T12:53:56.238Z

tree shaking in webpack is based on the import statements but since the shadow-cljs external index generates require that won't work

thheller 2021-04-15T12:54:35.238200Z

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

thheller 2021-04-15T12:55:03.238400Z

(:require ["./core.less"]) will not work since classspath relative imports will always be processed by shadow-cljs directly and only supports JS

Karol W贸jcik 2021-04-15T12:56:10.238700Z

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?

thheller 2021-04-15T12:59:50.239Z

in ns :require no

Karol W贸jcik 2021-04-15T13:01:35.239200Z

Oh. But still I can use js/require am I right? 馃檪

Karol W贸jcik 2021-04-15T13:02:37.239400Z

Probably import is special statement and cannot be used like js/require 馃槃

thheller 2021-04-15T13:03:32.239600Z

adding either import or js/require is not valid

thheller 2021-04-15T13:03:48.239800Z

since ONLY the external index file will be processed by webpack

thheller 2021-04-15T13:03:57.240Z

not the CLJS parts

thheller 2021-04-15T13:04:16.240200Z

I could add an extra mechanism to tell webpack to also include other stuff

thheller 2021-04-15T13:04:24.240400Z

but it will not be part of ns :require

thheller 2021-04-15T13:04:30.240600Z

could be ns metadata or so

thheller 2021-04-15T13:05:06.240800Z

would be much easier to talk about this if you setup a demo repo of what you'd like to have

thheller 2021-04-15T13:05:28.241Z

doesn't need to be functional but something code wise we can talk about. its all a bit abstract in a slack thread

Karol W贸jcik 2021-04-15T13:06:36.241200Z

Sure. I will provide a demo.

Karol W贸jcik 2021-04-15T13:06:37.241400Z

馃檪

Karol W贸jcik 2021-04-15T13:06:42.241600Z

Thank you @thheller

2021-04-15T13:37:51.243200Z

@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.

thheller 2021-04-15T13:46:02.243800Z

the ns you changed + all those that have a direct require for that one

thheller 2021-04-15T13:46:43.244300Z

init-fn ns only if that has a direct require, otherwise no

2021-04-15T13:59:12.244400Z

so are you saying the ns has in-direct require to the changed ns won鈥檛 be re-required ?

2021-04-15T14:00:55.244600Z

Seems not documented: https://shadow-cljs.github.io/docs/UsersGuide.html#_hot_code_reload

2021-04-15T14:08:55.244800Z

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.

thheller 2021-04-15T16:57:09.245500Z

> 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.

thheller 2021-04-15T16:58:23.245700Z

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

thosmos 2021-04-15T19:47:11.249300Z

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

thheller 2021-04-15T19:48:10.249900Z

@thosmos version conflict. not using the updated 1.10.844 CLJS with 2.12.+ won't work

thosmos 2021-04-15T19:48:34.250Z

OK thanks will upgrade to that

thosmos 2021-04-15T20:13:53.250600Z

That worked