Fork me on GitHub
#clojurescript
<
2021-09-04
>
john00:09:06

Sup y'all, I'm hacking on this auto-transducifying thread macro and I'm trying to figure out how best to integrate it with Clojurescript: https://github.com/johnmn3/injest

john00:09:39

It's currently broken in clojurescript

john01:09:31

I could store the things as quoted symbols but that works more anaphorically, so if you register 'x/reduce then all your namespaces have to use the same aliasing for the macro to see it, which kinda sucks. I'd prefer to namespace qualify the symbols in a given thread and test those against the namespace qualified versions stored at initialization, but I can't get it to work across both clj and cljs

john01:09:50

Probably doing something super simple wrong

john01:09:33

Could probably drop into the analyzer to get it done, but I'd think the clj macro would be able to resolve the symbol at compile time

john02:09:11

Also, I'm curious if when another person clicks on the codespaces link, if they get a vscode with calva that they can launch repls with. I think I have a basic one working

john02:09:32

Hmm, my other account doesn't seem to have access to codespaces :thinking_face:

ingesol09:09:29

@lilactown @dnolen Correct, stories and component in CLJS. They are compiled to JS, along with all dependencies. Then the folder containing all that compiled JS is processed by webpack somehow, node_modules too. I tested now with just this repo https://github.com/DavidVujic/clojurescript-amplified, commented out the amplify stuff. Findings in thread to not spam main thread here

ingesol09:09:29

Running that repo with no changes (except commenting out some amplify stuff that didn’t work), storybook is loaded in browser within 10-15 secs.

ingesol09:09:57

Then, if I add a dependency on specter and require it in the core NS, this is what happens after about 60 secs of run time.

David Vujic12:09:27

Storybook uses Webpack under the hood. In my example repo, I have used the very latest version supporting Webpack 5 with additional Webpack specific config, to make it work along the other things I’ve added in the repo (AWS Amplify and Material-UI). In short, this is how I think the ClojureScript and Storybook integration works: writing code in cljs -> shadow-cljs will compile it to JavaScript and put it in a public folder -> the running storybook process will pick up the changes and do some JavaScript packaging magic under the hood (involving Node.js and Webpack) 😄

David Vujic13:09:05

Sorry if I have missed any info about the issues in the discussion. I was thinking it might have something to do with the Node.js version running?

ingesol13:09:30

First of all thank you for providing the setup, it’s very helpful

David Vujic13:09:41

Alternatively, I’m thinking that it might be that Storybook doesn’t fully support Weback 5 yet. https://storybook.js.org/blog/storybook-for-webpack-5/

ingesol13:09:51

running this on node 14.17.6

👍 2
ingesol13:09:01

I tried with both webpack 4 and 5

ingesol13:09:25

both grind to a halt when linking in more of the heavy cljs namespaces

David Vujic13:09:25

I’m not familiar with the specter.js library, do you have any code that can be shared? I would like to help out finding what’s causing this.

ingesol13:09:19

I’m just randomly selecting that particular library because it’s rather big and predictably breaks storybook build. Added dependency

[com.rpl/specter "1.1.3"]
And require
[com.rpl.specter]

ingesol13:09:03

Requiring re-frame or fulcro also severely increases build time, but comes through.

ingesol13:09:22

Seems like lots of 100k+ files in the same folder as the stories js files isn’t a common usage pattern for normal storybook users

David Vujic13:09:02

Good find! Maybe it could be solved by setting the storybook alias in “production” mode (I’m not sure how, though)? I’ll try that out with my setup.

ingesol13:09:26

I’ve seen a lot of talk about excluding folders or file pattern from babel/webpack processing. Not been able to configure anything that makes a difference, though

David Vujic15:09:03

I’m not sure if this is an approach that is good or bad. Currently experimenting … and not fully automated yet. When changing the folder Storybook is looking for files to:

"stories": [
    "../public/js/stories/release/*_stories.js"
  ],
(.storybook/main.js) And triggering a cljs release build:

David Vujic15:09:21

shadow-cljs release stories --config-merge '{:output-dir \"public/js/stories/release\"}'

David Vujic15:09:12

With both re-frame and specter included, Storybook seems to manage building fine. Before that, I experienced the same issues as you.

David Vujic15:09:10

The release build should be automated somehow, to have a nice dev experience. Something should be watching the dev output and trigger the shadow release process.

David Vujic15:09:25

I’ll continue trying out this, and will update the example repo if this is realistic in practice.

ingesol15:09:18

Interesting! To me it sounds likely that the release build works because it’s only one file, and much less processing need for webpack?

thheller18:09:16

yeah release builds will be much smaller

thheller18:09:04

feel free to open an issue about this. ideally without a reproducible repo. I can see if I can figure something out to hide the CLJS output since it really doesn't need to go through webpack

ingesol19:09:05

@U05224H0W great! But wouldn’t it be easier if we could configure webpack to skip all files in that folder?

David Vujic19:09:40

I don’t know it it would be possible (or make sense 😄 ) - if a dev build could be tweaked with compile-options to output something similar to release builds? Then it would be possible to watch changes in an alias.

👍 2
thheller20:09:56

it has to process the files. kind of at least. needs to be able to find the stories after all

ingesol21:09:45

Correct me if I’m wrong, but seems to me storybook uses babel for transpiling things like typescript and JSX in stories. None of this is needed for compiled CLJS, right?

👏 2
David Vujic22:09:24

I think you might have solved it @U21QNFC5C 😄 I have replaced all the storybook webpack rules and it seems to build very very fast. Probably, there are some of the existing rules that should be there, but this is probably the way to solve this issue. Great!

David Vujic22:09:01

Here’s the rules replaced (i just console-logged the config):

[
  {
    test: /\.(mjs|tsx?|jsx?)$/,
    use: [ [Object] ],
    include: [ '/Users/david/github/clojurescript-amplified' ],
    exclude: /node_modules/
  },
  { test: /\.js$/, use: [ [Object] ], include: [Function: include] },
  { test: /\.md$/, type: 'asset/source' },
  {
    test: /\.js$/,
    include: /node_modules\/acorn-jsx/,
    use: [ [Object] ]
  },
  { test: /(stories|story)\.mdx$/, use: [ [Object], [Object] ] },
  {
    test: /\.mdx$/,
    exclude: /(stories|story)\.mdx$/,
    use: [ [Object], [Object] ]
  },
  {
    test: /\.(stories|story)\.[tj]sx?$/,
    loader: '/Users/david/github/clojurescript-amplified/node_modules/@storybook/source-loader/dist/cjs/index.js',
    options: { injectStoryParameters: true, inspectLocalDependencies: true },
    enforce: 'pre'
  },
  {
    test: /\.css$/,
    sideEffects: true,
    use: [
      '/Users/david/github/clojurescript-amplified/node_modules/style-loader/dist/cjs.js',
      [Object]
    ]
  },
  {
    test: /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
    type: 'asset/resource',
    generator: { filename: 'static/media/[path][name].[ext]' }
  },
  {
    test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/,
    type: 'asset',
    parser: { dataUrlCondition: [Object] },
    generator: { filename: 'static/media/[path][name].[ext]' }
  }
]

David Vujic22:09:28

in .storybook/main.js I have set the rules like this:

config.module.rules = appConfig.module.rules;

David Vujic22:09:53

and that seems to have worked 😄 Will continue with this tomorrow.

ingesol22:09:50

I did the same thing, but not able to get it working with just an empty rules set

ingesol22:09:41

oh hey, setting to [] did not work but your snippet did 🙂

thheller07:09:00

one thing it might have been doing is combining the source maps. that usually is a bit expensive.

David Vujic07:09:29

I think I tried with :compiler-options {:source-map false} in the stories alias (in my example storybook repo) but there still were map files generated. Is there another way to tell the compiler to not add map files?

thheller07:09:16

development always adds them, you really really want them anyways. otherwise any errors are going to be a nightmare to debug

👍 2
ingesol08:09:59

It’s a mystery to me why this fix works. Been looking through those rules, and only 2 of them match JS files, none match MAP files. And the 2 JS matchers are directed at very specific modules inside node_modules

ingesol08:09:09

But maybe some JSX rule processes the storybook JSX files, and from there drills down into stories specifics

David Vujic10:09:20

I have pushed a solution I think will work. With less Storybook/Webpack hacks. Try it out (I also bumped the storybook and webpack libs to the latest version). This will also make the Storybook watch & build way faster. The update: https://github.com/DavidVujic/clojurescript-amplified/commit/cf5bd1700581f62abbc760000d0cb3d15ec24ca3

ingesol10:09:22

excellent, will test right away

thheller10:09:33

ah so its babel causing the issues again. makes sense to disable, not needed at all anyways

ingesol11:09:49

tested now, works! Thank you @U018VMC8T0W. I’ve been searching for that particular config change 🙂

👍 2
🥳 2
cljs 2
📖 2
borkdude19:09:14

can someone give me a succinct example of this-as without any libs like react, etc?

nenadalm19:09:06

Here is documentation on how this works (`this-as` just makes this accessible in given "block": https://cljs.github.io/api/cljs.core/this-as) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this (not really an example but it might make things more clear.... maybe.)

borkdude19:09:21

I'm specifically looking for an example, to test things around this-as

borkdude19:09:35

I found the docs, but I don't find any good standalone examples

nenadalm19:09:08

maybe this would be ok example then? (tested using http://clojurescript.net/):

(defn test
  "Increments counter and returns current value."
  []
  (this-as this
    (set! (.-counter this) (inc (or (.-counter this) 0)))
    (.-counter this)))

(test) ;; => 1
(test) ;; => 2

(defn test2 [])

(def test3 (.bind test test2))

(test3) ;; => 1
(test) ;; => 3

borkdude19:09:17

@U662GKS3F what is this in the test function?

borkdude19:09:42

is it the global object?

lilactown19:09:45

in JS, this refers to the JS object a method is attached to

borkdude19:09:13

yes, I understand that, but in the case of the above example?

lilactown19:09:23

so calling (test) bare will make this refer to the global object (i.e. window)

nenadalm19:09:47

> what is `this` in the `test` function? that's easy to test:

(defn return-this []
  (this-as this
    this))

(return-this) ;; => #object[Window [object Window]]
meaning this equals to js/window.

borkdude19:09:58

right, so I was looking for an example where this-as this doesn't refer to the global object in CLJS

borkdude19:09:18

I tried making something with #js {:a (fn [] (this-as this)} but it didn't work for me

lilactown19:09:55

what didn't work? how did you call it?

borkdude19:09:23

it didn't work as expected, as in this was still the global object

lilactown19:09:49

it is highly dependent on how you call it. can you show an example of how you tried to execute the a method?

nenadalm19:09:52

> right, so I was looking for an example where `this-as this` doesn't refer to the global object in CLJS I don't really use this much in js, but in the example - test3 has this bound to test2 function. In js, functions work kind of like objects (yeah - it's messy)

borkdude19:09:24

The reason I'm asking all of this is that I would like SCI to have more compatibility with CLJS and I'm looking for test cases

borkdude19:09:34

And I have trouble finding test cases that makes sense to me

nenadalm19:09:40

this depends on where you call the function from and bind can fix the this value so that it is not dependent on caller (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind)

borkdude19:09:43

without bringing in a whole lot of deps

lilactown19:09:53

@U04V15CAJ without seeing the full example I can't tell you what was wrong with how you were testing it, but this worked for me:

cljs.user> (def o #js {:a (fn [] (this-as this this))})
#'cljs.user/o
cljs.user> (.a o)
#js {:a #object[a]}

borkdude19:09:34

ah I think I called it wrong then

borkdude19:09:44

I got the function out of the object and then called it

lilactown19:09:59

right, that's what I was saying, it's highly dependent on how you call it

borkdude19:09:07

yikes, this really seems like a footgun

🙂 2
lilactown19:09:14

it is also very flexible, i.e. you can create methods outside the context of the object and then attach them after the fact

lilactown19:09:27

cljs.user> (defn a [] (this-as this this))
#'cljs.user/a
cljs.user> (def o #js {:a a :name "o"})
#'cljs.user/o
cljs.user> (def o2 #js {:a a :name "o2"})
#'cljs.user/o2
cljs.user> (.a o)
#js {:a #object[cljs$user$a], :name "o"}
cljs.user> (.a o2)
#js {:a #object[cljs$user$a], :name "o2"}

lilactown19:09:53

there are ways to statically bind what this refers to in a function, like what @U662GKS3F linked above

lilactown19:09:36

in modern JS, it's now popular to use the arrow syntax () => {} for constructing functions which statically binds this in the way one would expect coming from other languages, i.e. this stays the same after construction no matter what object it is "attached" to

borkdude20:09:45

makes sense, thanks :)

borkdude09:09:28

I'm trying to figure out if there is a way to get to "this" in SCI. Because it's not a compiler, it seems... difficult. E.g. this returns true:

cljs.user=> (sci/eval-string "(def o (clj->js {:a (fn [] (identical? js/goog.global (js/eval \"this\")))})) (.a o) " {:classes {'js goog/global :allow :all}})
true
while it should not.

borkdude10:09:40

It does seem like the js/eval approach works in CLJS:

cljs.user=> (def o (clj->js {:a (fn [] (js/eval "this"))})) (.a o)
#'cljs.user/o
#js {:a #object[Function]}
so I do think it should be possible to make it work in SCI as well

borkdude11:09:58

Oh, perhaps this has to do with .bind stuff!

borkdude11:09:37

Hmm, .apply should be sufficient for this

thheller15:09:50

to get a new non global this you need new

(defn my-constructor []
  (this-as this
    (set! this -foo "bar")))

(let [obj (my-constructor.)]
  (js/console.log obj))

thheller15:09:18

you can also set it for any function via .call or .bind but typically this is just the current object assumed to be constructed via new (or the thing. sugar for that)

thheller15:09:59

using this in a regular function thats not part of a prototype is uncommon

borkdude19:09:32

something which I can type in a CLJS REPL

john22:09:00

Got a temporary work around in place for ClojureScript - gotta register symbols in a clojure (`.clj`) namespace. Transducer authors could probably ship with injest registrations that work in clj and cljs seemlessly for their users though. If anybody notices a better workaround that allows registration from cljs, let me know