shadow-cljs

Roman Liutikov 2025-02-07T10:01:48.429869Z

I noticed that shadow moved cljs constants table into a private Java package, so no way to extend constants table from Clojure?

thheller 2025-02-07T11:01:20.888289Z

there is no such thing as a constants table in shadow-cljs?

thheller 2025-02-07T11:01:53.122489Z

there is a closure compiler pass that basically achieves the same end result, but there is no equivalent to the cljs constant-table thing

Roman Liutikov 2025-02-07T11:19:24.507759Z

maybe it's that, but anyway, in cljs it's possible to write into constants table from a macro, seems like it's not possible with shadow? https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/build/closure/ReplaceCLJSConstants.java

thheller 2025-02-07T13:19:24.580329Z

I do not know what that would do?

2025-02-07T10:45:21.495189Z

Shadow / Closure seems to be adding some goog.provide calls in the output JS before goog itself is setup. This only happens in release. Details in ๐Ÿงต

2025-02-07T10:46:04.948089Z

Here's the first bit of shared.js. Those provide calls look out of place to me:

var shadow$provide = {};
var $APP = {};
(function() {
    'use strict';
    goog.provide("goog.events.EventWrapper");
    goog.provide("goog.events.EventLike");
    goog.provide("goog.debug.ErrorHandler"); /*

     Copyright The Closure Library Authors.
     SPDX-License-Identifier: Apache-2.0
    */




    var CLOSURE_NO_DEPS = true;
    var CLOSURE_BASE_PATH = "js/cljs-runtime/";
    var CLOSURE_DEFINES = {
        "goog.DEBUG": false,
        "goog.LOCALE": "en",
        "goog.TRANSPILE": "never",
        "goog.ENABLE_DEBUG_LOADER": false
    };
    var COMPILED = false;
    var goog = goog || {};
    goog.global = this || self;
    goog.global.CLOSURE_UNCOMPILED_DEFINES;
    goog.global.CLOSURE_DEFINES;

2025-02-07T10:47:30.732059Z

shadow-cljs.edn:

{:deps true
 :builds {:frontend {:target :browser
                     :modules {:shared {:entries []}
                               :main   {:init-fn my.project.main/main
                                        :depends-on #{:shared}}
                               :a {:init-fn my.project.main-a/main
                                   :depends-on #{:shared}}
                               :b {:init-fn my.project.main-b/main
                                   :depends-on #{:shared}}}
                     :devtools {:watch-dir "../dist/"}
                     :compiler-options {:optimizations :whitespace ;;TODO: get advanced working?
                                        :infer-externs false
                                        :externs ["datascript/externs.js"]
                                        :warnings {:extending-base-js-type false
                                                   :fn-deprecated false
                                                   ;;ignore warning from loom/alg_generic.cljc:494:19
                                                   :invalid-arithmetic false}}
                     :output-dir "../dist/js/"
                     :asset-path "js"}}}
deps.edn:
{:paths ["src/"]
 :deps {org.clojure/clojure {:mvn/version "1.12.0"}
        org.clojure/core.match {:mvn/version "1.0.1"}
        org.clojure/core.async {:mvn/version "1.7.701"}
        fipp/fipp {:mvn/version "0.6.24"}
        lonocloud/synthread {:mvn/version "1.0.4"}
        no.cjohansen/replicant {:mvn/version "2025.02.02"}
        org.suskalo/coffi {:mvn/version "1.0.486"}
        cheshire/cheshire {:mvn/version "5.13.0"}
        datascript/datascript {:mvn/version "1.7.3"}
        com.rpl/specter {:mvn/version "1.1.4"}
        instaparse/instaparse {:mvn/version "1.5.0"}
        aysylu/loom {:mvn/version "1.0.2"}
        com.taoensso/tufte {:mvn/version "2.6.3"}}

 :aliases {:shadow-cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.28.20"}
                                      binaryage/devtools {:mvn/version "1.0.7"}
                                      cider/cider-nrepl {:mvn/version "0.50.2"}}
                         :main-opts ["-m" "shadow.cljs.devtools.cli"]}}}

2025-02-07T10:48:33.756199Z

The shared.js was built with clojure -M:shadow-cljs release frontend

2025-02-07T10:50:43.006829Z

This is from a clean build too. I'm running rm -rf dist/js/ before I run shadow.

2025-02-07T10:52:59.186759Z

Interestingly, I get different errors when :simple optimizations are used

thheller 2025-02-07T10:56:53.068079Z

:whitespace is not supported. use :simple

2025-02-07T10:58:18.836829Z

Ah, I didn't realize that. Want me to open a PR that throws an error if :whitespace is used?

thheller 2025-02-07T10:58:32.959089Z

and you might need to add :compiler-options {:output-wrapper false} when using multiple modules (and :simple)

2025-02-07T11:00:01.851529Z

Ah, that did the trick, thanks!

๐Ÿ‘ 1
2025-02-07T11:02:16.991779Z

Would you like me to do a separate PR for this situation as well? Two behaviors I can think of: โ€ข Shadow should set output-wrapper false if there are multiple modules and simple optimizations, warning/erroring if the explicit config had it set otherwise โ€ข Just always error, so that the user is forced to set it

2025-02-07T11:02:45.014119Z

Thanks for all of your work on shadow-cljs, btw. I've been using ClojureScript since 2012 and your tooling makes everything so much better!

thheller 2025-02-07T11:04:25.249819Z

output wrapper is a bit tricky, since generally it shouldn't be a problem. I'm not entirely sure :simple would always require it

thheller 2025-02-07T11:04:41.271659Z

throwing for :whitespace would be fine

thheller 2025-02-07T11:06:16.736559Z

no need for a PR though, fixed it https://github.com/thheller/shadow-cljs/commit/e73b2666506362532227606ae3b8ad81e1ef1454

๐Ÿ™Œ 1
2025-02-07T11:06:19.765599Z

Sweet! I don't think I'd ever have figured out the output wrapper thing myself, though, and so if possible I'd love to sand down that rough edge somehow. I always get such whiplash from enjoying the cljs dev experience, then having to debug weirdness when it comes to making a production build. Would love to save other people those, uh, adventures.

thheller 2025-02-07T11:08:38.361829Z

just go with :advanced, that is definitely the most common way and has the most information about it. I wouldn't recommend :simple for any browser targeted build

thheller 2025-02-07T11:09:58.824509Z

just remove :infer-externs false and fix the warnings ๐Ÿ˜‰

2025-02-07T11:11:33.912659Z

Aight, challenge accepted. I'll make myself lunch, harden my soul, and set a 1 hour timer. Wish me luck =D

thheller 2025-02-07T11:12:17.489449Z

feel free to ask if you have questions/problems

๐Ÿ‘ 1
2025-02-07T11:12:33.898969Z

If you are ever around Amsterdam, btw, drop me a line so I can buy you a coffee/beer/lunch.

2025-02-07T14:32:05.559299Z

Any tips for how to debug some inference errors that show up in my main project but don't show up in a minimal reproduction project? Both have the same versions of shadow and core.async. Here's an example: This namespace compiles fine in the minimal project:

(ns repro.main
  (:require [cljs.core.async :refer [go <! chan]]
            [cljs.core.async.interop :refer [<p!]]))

(defonce !tray-icon (atom nil))

(def Menu
  (aget js/window "__TAURI__" "menu" "Menu" "new"))

(defn update-tray-menu!
  []
  (go
    (let [^js tray-icon @!tray-icon
          menu (<p! (Menu (clj->js {:items []})))]
      (.setMenu tray-icon menu))))


(defn main
  []
  (update-tray-menu!))
but if I take the exact same code and put it in a namespace in my full project (and reference the main fn, so it doesn't get optimized out), I get an inference warning:
------ WARNING #1 - :infer-warning ---------------------------------------------
 File: ...foo.cljs:14:3
--------------------------------------------------------------------------------
  11 |
  12 | (defn update-tray-menu!
  13 |   []
  14 |   (go
---------^----------------------------------------------------------------------
 Cannot infer target type in expression (. inst_37022 (setMenu inst_37044))
--------------------------------------------------------------------------------
  15 |     (let [^js tray-icon @!tray-icon
  16 |           menu (<p! (Menu (clj->js {:items []})))]
  17 |       (.setMenu tray-icon menu))))
  18 |
--------------------------------------------------------------------------------

thheller 2025-02-07T18:14:11.743879Z

go unfortunately notorious for loosing type hint info. so typehints in there are rather unreliable

2025-02-07T18:14:40.556789Z

hah, good timing I just finished dinner and just now threw together a minimal repro: https://github.com/lynaghk/repro-cljs-go-unreachable

thheller 2025-02-07T18:15:01.586789Z

FWIW you shouldn't be using aget there

2025-02-07T18:15:19.969209Z

doh, actually this repro is for a separate issue I ran into about "unreachable" warning. not related to type hints as far as I know

thheller 2025-02-07T18:17:06.877579Z

dunno what that is but yeah avoid go as much as possible ๐Ÿ˜›

2025-02-07T18:17:12.894069Z

what's the appropriate alternative? nested property access like

(def Menu
  (-> js/window
      .-__TAURI__
      .-menu
      .-Menu
      .-new))

thheller 2025-02-07T18:17:51.399679Z

not sure what the new is doing there, but just js/__TAURI__.menu.Menu is fine

thheller 2025-02-07T18:18:16.335439Z

if that is supposed to be a constructor js/__TAURI__.menu.Menu.

2025-02-07T18:19:15.637589Z

they have a method called new on the object https://v2.tauri.app/learn/window-menu/

2025-02-07T18:20:12.657449Z

> js/__TAURI__.menu.Menu Ah, so it's okay to do dotted property access like this? I couldn't remember if that was allowed or just a "happens to work sometimes" thing.

thheller 2025-02-07T18:20:47.434269Z

ah ok, looks like a regular function then

thheller 2025-02-07T18:21:08.142159Z

yes, this dotted property access is fine. externs are generated for all properties

๐Ÿ‘ 1
2025-02-07T18:21:12.600469Z

yeah, I did a double take myself when I was first writing the code

thheller 2025-02-07T18:21:29.500739Z

externs are generated for all js/ forms

2025-02-07T18:25:15.742289Z

Aight, well I switched to the dotted property access but have the same type hint warning. Lifting it out of the go block resolves the problem, so I guess I'll rewrite to use explicit promise callbacks and open an issue with cljs / core.async and/or settle down for some quality time with the macroexpansion to see if I can track down the bug myself.

thheller 2025-02-07T19:00:54.985759Z

the problem has been around for a while, not sure anyone ever looked into it. I don't really use core.async for frontend stuff so dunno