shadow-cljs

https://github.com/thheller/shadow-cljs | https://github.com/sponsors/thheller | https://www.patreon.com/thheller
Sean Poulter 2021-02-08T05:31:57.062200Z

Thomas. Your docs are amazing. Thank you! It has helped me make sense of ClojureScript macros. πŸ™‚

Sean Poulter 2021-02-08T05:36:29.064600Z

Is it possible to write a macro that uses an npm package as a dependency? I’ve run into Gotcha #1: Namespace Aliases and Dependencies from https://code.thheller.com/blog/shadow-cljs/2019/10/12/clojurescript-macros.html. My goal was to write a quick utility for faking timers in tests with @sinonjs/fake-timers but it seems like that’s not possible with deps from npm. :/ my.util.clj

(ns test.fixtures)

(defmacro with-fake-timers
  [symbol & body]
  `(let [~symbol (fake-timers/install)]
    (try ~@body
      (finally (.uninstall ~symbol)))))
my.util.cljs
(ns test.fixtures
  (:require
   ["@sinonjs/fake-timers" :as fake-timers])
  (:require-macros
   [test.fixtures :refer [with-fake-timers]]))
my.util-test.cljs
(ns test.fixtures-test
  (:require
    [cljs.test :refer [deftest testing is]]
    [test.fixtures :refer [with-fake-timers]]))

(deftest macro-test
  (is (= '_ (macroexpand-1 '(with-fake-timers clock (prn "Hello, world"))))))

(deftest with-fake-timers-test
  (testing "Given a symbol to bind to and a body to execute, "
           "it should replace the global timer APIs and execute the body"
    (let [previous-value js/window.setTimeout]
      (with-fake-timers clock
        (let [actual (= js/window.setTimeout previous-value)
              expected false]
          (is (= actual expected)))))))

Sean Poulter 2021-02-08T05:51:18.067Z

Could this be related to it being a :karma build target? The macro expands as expected but uses the missing ns alias, then throws: > Chrome Headless 88.0.4324.150 (Mac OS 10.14.6) test.fixtures-test with-fake-timers-test FAILED > FAIL in (with-fake-timers-test) (ReferenceError:NaN:NaN) > failed with ReferenceError: fake_timers is not defined > message: Uncaught exception, not in assertion. >

Sean Poulter 2021-02-08T05:52:22.067600Z

I’m guessing I should stop fighting and give up on the macro. πŸ˜•

johnjelinek 2021-02-08T06:56:21.070600Z

hihi, I'm trying to use shadow-cljs where I need to consume some javascript generated by the typescript compiler. I've tried different ways to transpile and I just don't seem to be getting it right. If I transpile in ES2015+, shadow-cljs complains about references to import * as cdktf from 'cdktf';. If I transpile to CommonJS, it complains as well:

; Execution error (ReferenceError) at (<cljs repl>:1).
module$docker$index is not defined

thheller 2021-02-08T09:24:33.073800Z

with which shadow-cljs version did you try? I fixed a bug related to this very recently. try 2.11.17

johnjelinek 2021-02-08T13:26:04.074700Z

"shadow-cljs": "^2.11.17",

johnjelinek 2021-02-08T18:52:51.085Z

@thheller: looks like the same version ☝️

thheller 2021-02-08T18:58:06.085200Z

on problem is that for all npm dependencies the closure compiler will see them as commonjs

thheller 2021-02-08T18:58:22.085400Z

thus import * as cdktf from 'cdktf'; is invalid and would need to be import cdktf from 'cdktf';. which the warning is telling you basically

johnjelinek 2021-02-08T19:18:13.085800Z

@thheller: ok, so that's something I'd need to manually change after the transpiler completes if I want to target ES2015? But if it's CommonJS as the target and I'm getting module$docker$index is not define -- what should I do in that case?

johnjelinek 2021-02-08T19:22:43.086Z

regarding ES2015 output and your suggestion about manual change, magic!

> docker
#js {:Config #object[Config$$module$docker$config], :Container #object[Container$$module$docker$container], :ContainerNetworkData #object[ContainerNetworkData$$module$docker$container], :DataDockerNetwork #object[DataDockerNetwork$$module$docker$data_docker_network], :DataDockerNetworkIpamConfig #object[DataDockerNetworkIpamConfig$$module$docker$data_docker_network], :DataDockerRegistryImage #object[DataDockerRegistryImage$$module$docker$data_docker_registry_image], :DockerProvider #object[DockerProvider$$module$docker$docker_provider], :Image #object[Image$$module$docker$image], :Network #object[Network$$module$docker$network], :Secret #object[Secret$$module$docker$secret], :Service #object[Service$$module$docker$service], :Volume #object[Volume$$module$docker$volume]}

> Container
#object[Container$$module$docker$container]

thheller 2021-02-08T19:22:46.086300Z

I don't know. can you try with a browser-repl or an actual build?

johnjelinek 2021-02-08T19:23:43.086500Z

I didn't make any changes to the references or to the shadow-cljs.edn after transpiling to ES2015 and changing the import statement for every single generated .js file

johnjelinek 2021-02-08T19:24:59.086700Z

note, my current ns where it's working with ES2015 now:

(ns demo.script
  (:require ["constructs" :refer (Construct)]
            ["cdktf" :refer (App TerraformStack)]
            ["/docker/index" :as docker :refer (Container)]))

johnjelinek 2021-02-08T19:25:21.086900Z

what should I look for in a browser-repl for this?

johnjelinek 2021-02-08T19:26:31.087100Z

here's my shadow-cljs.edn:

{:source-paths ["src/main" ".gen/providers-out"]
 :builds {:app {:target :node-script
                :output-to "target/script.js"
                :main demo.script/main}}}

thheller 2021-02-08T19:26:51.087300Z

browser-repl for the cases where you get module$docker$index is not defined now

thheller 2021-02-08T19:27:09.087500Z

if things even run in the browser. seems to be a node target so that may not be an option.

johnjelinek 2021-02-08T19:35:06.087700Z

ya, it's definitely a node target

johnjelinek 2021-02-08T19:39:37.087900Z

I'll go with the ES2015 module output for now, thanks! I'm not sure how I would've troubleshooted this without you.

johnjelinek 2021-02-08T06:57:45.070800Z

> tree ./.gen/providers-out/
./.gen/providers-out/
└── docker
    β”œβ”€β”€ config.d.ts
    β”œβ”€β”€ config.js
    β”œβ”€β”€ container.d.ts
    β”œβ”€β”€ container.js
    β”œβ”€β”€ data-docker-network.d.ts
    β”œβ”€β”€ data-docker-network.js
    β”œβ”€β”€ data-docker-registry-image.d.ts
    β”œβ”€β”€ data-docker-registry-image.js
    β”œβ”€β”€ docker-provider.d.ts
    β”œβ”€β”€ docker-provider.js
    β”œβ”€β”€ image.d.ts
    β”œβ”€β”€ image.js
    β”œβ”€β”€ index.d.ts
    β”œβ”€β”€ index.js
    β”œβ”€β”€ network.d.ts
    β”œβ”€β”€ network.js
    β”œβ”€β”€ secret.d.ts
    β”œβ”€β”€ secret.js
    β”œβ”€β”€ service.d.ts
    β”œβ”€β”€ service.js
    β”œβ”€β”€ volume.d.ts
    └── volume.js

1 directory, 22 files

johnjelinek 2021-02-08T06:59:04.071100Z

all the exports are within index.js and the modules it refers to call out to cdktf (expected from node_modules)

johnjelinek 2021-02-08T06:59:29.071500Z

any help would be much appreciated πŸ™‚

johnjelinek 2021-02-08T07:05:03.071700Z

for CommonJS:

> (require '["/docker"])
FileNotFoundException: ~/hello-world/.gen/providers-out/docker (Is a directory)

> (require '["/docker/index" :as docker])
nil

> docker

Execution error (ReferenceError) at (<cljs repl>:1).
module$docker$index is not defined
nil

johnjelinek 2021-02-08T07:14:50.072300Z

for ES2015:

ExceptionInfo closure errors {:tag :shadow.build.closure/errors, :errors [{:resource-name "docker/config.js", :source-name "docker/config.js", :line 3, :column 0, :msg "Namespace imports (goog:some.Namespace) cannot use import * as. Did you mean to import cdktf from 'goog:shadow.js.shim.module$cdktf';?"}]

johnjelinek 2021-02-08T07:19:50.072500Z

and here's how the module looks like in the node REPL after transpiling to CommonJS:

> var docker = require('./.gen/providers-out/docker')
undefined
> docker
{
  Config: [Getter],
  ContainerNetworkData: [Getter],
  Container: [Getter],
  Image: [Getter],
  Network: [Getter],
  Secret: [Getter],
  Service: [Getter],
  Volume: [Getter],
  DataDockerNetworkIpamConfig: [Getter],
  DataDockerNetwork: [Getter],
  DataDockerRegistryImage: [Getter],
  DockerProvider: [Getter]
}

thheller 2021-02-08T09:23:13.073700Z

@sean.poulter the easiest path is just creating a function in CLJS that delegates to the npm dep (defn install-timer [] (fake-timers/install)) and call that from the macro instead.

❀️ 1
Sean Poulter 2021-02-08T12:38:22.074500Z

Thanks for keeping it simple. πŸ™‚

Sean Poulter 2021-02-08T14:01:31.074900Z

That works perfectly. πŸ™

Sean Poulter 2021-02-08T14:02:09.075500Z

Do you have a preference for Patreon or PayPal?

Helins 2021-02-08T14:04:03.076900Z

Wouldn't it be useful to also take into account file deletions when it comes to live reloading? (Eg. UI reflects that a CSS file has been deleted (browser will reload non-existing file))

thheller 2021-02-08T14:06:35.077300Z

how would it reload a non-existing file? doesn't make much sense to me

thheller 2021-02-08T14:08:00.077500Z

all fine πŸ™‚

Helins 2021-02-08T14:25:56.078800Z

Do what it does for modifications (ie. modify the r query argument -> browser tries to reload file and fails)

thheller 2021-02-08T14:46:38.079200Z

yes but why would you willingly force a 404?

Helins 2021-02-08T15:06:34.080900Z

For instance in the context of live reloading CSS, it's a bit confusing that nothing changes when a CSS file gets deleted (because the browser knows nothing about that)

thheller 2021-02-08T15:08:48.081400Z

yeah still no clue what you are doing but so far I'm not interested in adding that

Helins 2021-02-08T15:13:25.082900Z

Haha, all right, it's just I would expect that deleting any live-reloaded asset that is still being used by my code would induce a change to help me notice it, but all right

thheller 2021-02-08T16:50:44.083700Z

yeah I can only imagine getting into weird situations. the only sensible things to do would be deleting the link tag used to import the style as thats the only way to remove the css

πŸ‘ 1
thheller 2021-02-08T16:51:00.084200Z

but then when you add it again the HTML doesn'lt have the link anymore so it won't be loaded

thheller 2021-02-08T16:51:35.084900Z

in general you should only have one css file anyways. at least thats what I have always done. take the many files and generate one that is loaded in the html