shadow-cljs

tianshu 2025-05-31T10:24:56.062829Z

Sorry I asked in the wrong place. 😂 Do we have lazy module loading in web worker?

thheller 2025-05-31T10:35:13.923499Z

what are you trying to do exactly? the standard shadow.loader based stuff will not work no, but :esm in theory could

tianshu 2025-05-31T10:41:46.168909Z

I want to delay some loading in my web worker. So I should build those modules with :npm-module target?

thheller 2025-05-31T10:42:43.283459Z

no, :target :esm is the only thing that'll work for that

tianshu 2025-05-31T10:43:11.940939Z

How can I load this :esm stuff?

thheller 2025-05-31T10:43:14.812599Z

using the dynamic import referenced here https://clojureverse.org/t/generating-es-modules-browser-deno/6116

❤️ 1
tianshu 2025-05-31T10:46:22.146329Z

This looks good. Now I have several builds, and they all have the same "duplicated" modules to be lazy loaded. It seems with this, I can move these modules into a :esm build.

thheller 2025-05-31T10:46:57.831619Z

you could and should have done this with a :browser build as well?

thheller 2025-05-31T10:47:31.629799Z

but yes, each worker should be a module in a :esm build

tianshu 2025-05-31T10:48:37.955109Z

I have some code want to be lazy loaded across multiple builds, including the :browser target builds and the worker builds.

thheller 2025-05-31T10:49:25.283969Z

I don't think I understand what you are trying to do or why it needs to be multiple builds?

thheller 2025-05-31T10:49:43.506469Z

one build with many modules is the recommended approach

thheller 2025-05-31T10:50:13.759369Z

the fact that workers are involved doesn't change this in any way really

thheller 2025-05-31T10:52:18.998509Z

(the way you actually load stuff is different in esm though)

tianshu 2025-05-31T10:52:19.004459Z

Can I have multiple entries from one build? I'm build a website mostly single paged, but still have some pages.

thheller 2025-05-31T10:52:56.176689Z

so you want a module per page?

tianshu 2025-05-31T10:53:23.004939Z

Yes. It's like A depends C, D, E B depends C, D, E etc

tianshu 2025-05-31T10:53:40.086639Z

So A, B are entries, and C, D, E are shared code. And C, D, E should be lazy loaded.

thheller 2025-05-31T10:54:07.748999Z

then you are using the description wrong 😛 if its lazy loaded it doesn't depend on it directy

tianshu 2025-05-31T10:54:20.112799Z

Ops

thheller 2025-05-31T10:54:32.531359Z

given that A cannot work without C,D,E they must be loaded first

tianshu 2025-05-31T10:55:00.469939Z

Hmm, I was wrong. And yeah, that's why I don't know how to do this in one build.

thheller 2025-05-31T10:55:28.346989Z

this is from the blog post, pretty much your exact use case

thheller 2025-05-31T10:55:30.061089Z

{...
 :builds
 {:app
  {:target :browser
   ...
   :module-loader true
   :modules
   {:main
    {:init-fn }

    :account-overview
    {:entries [demo.components.account-overview]
     :depends-on #{:main}}

    :product-detail
    {:entries [demo.components.product-detail]
     :depends-on #{:main}}

    :product-listing
    {:entries [demo.components.product-listing]
     :depends-on #{:main}}

    :sign-in
    {:entries [demo.components.sign-in]
     :depends-on #{:main}}

    :sign-up
    {:entries [demo.components.sign-up]
     :depends-on #{:main}}}
   }}}

tianshu 2025-05-31T10:55:31.800939Z

For example, now my "A" build. It has 4 modules, D, C, E depends on A. And A is the entry.

thheller 2025-05-31T10:55:52.210219Z

forget about it being :target :browser for a sec, but the :modules part is identical in :esm

thheller 2025-05-31T10:56:15.845849Z

so using that example above, lets say :sign-up is an individual page

thheller 2025-05-31T10:56:33.567549Z

that page would just include <script type="module" src="/js/sign-up.js">

thheller 2025-05-31T10:56:45.902429Z

that would in turn also load the :main module, automatically

thheller 2025-05-31T10:57:27.164169Z

if you want to run something on page load each module could have its own :init-fn

tianshu 2025-05-31T10:57:52.534159Z

Ah, yeah, this is exactly what I want to do. I need this for my current browser build!

thheller 2025-05-31T10:58:16.975169Z

{...
 :builds
 {:app
  {:target :esm
   ...
   :modules
   {:main
    {:entries []}

    ...
    :sign-up
    {:init-fn demo.app.sign-up/init
     :depends-on #{:main}}}
   }}}

thheller 2025-05-31T10:59:17.406179Z

works ok for :browser too but there each page would also need to have script tags for each shared module, :esm makes that simpler

tianshu 2025-05-31T11:00:07.023999Z

I didn't get this, do you mean I can just use :esm target instead of :browser target?

thheller 2025-05-31T11:01:06.282099Z

pretty much yeah. well kinda depends on what you are doing, so I guess it depends

tianshu 2025-05-31T11:01:49.718829Z

And how to introduce the worker here? Just add :web-worker true to make it an entry for worker?

thheller 2025-05-31T11:02:25.850919Z

no, its just a normal module. no extra config needed

tianshu 2025-05-31T11:02:50.661019Z

I saw in the document, :web-worker is required?

thheller 2025-05-31T11:03:08.998889Z

no, that is a :browser only thing and does absolutely nothing in :esm. you can have it, it just doesn't do anything

thheller 2025-05-31T11:04:21.144109Z

you'll also need to construct the workers with type: "module"

tianshu 2025-05-31T11:06:45.292179Z

What is type: "module" , the property in package.json?

tianshu 2025-05-31T11:06:54.090599Z

{:builds
 {:app {
        :target :esm
        ;; I no longer need that :module-loader true, right?
        ;; Because now it's dealing with ESM modules
        :modules
        {:shared {:entries []}

         :page-a {:init-fn    page-a/init
                  :depends-on #{:shared}}

         :page-b {:init-fn    page-b/init
                  :depends-on #{:shared}}

         :worker {:init-fn    worker/init
                  :depends-on #{:shared}}

         :module-to-load-later-a {:entries [lazy-a] :depends-on #{:shared}}
         
         :module-to-load-later-b {:entries [lazy-b] :depends-on #{:shared}}}}}}

tianshu 2025-05-31T11:07:03.977109Z

This is what I have in mind for now.

thheller 2025-05-31T11:07:12.397019Z

https://web.dev/articles/module-workers#enter_module_workers

❤️ 1
tianshu 2025-05-31T11:07:40.198569Z

New knowledge learned.

thheller 2025-05-31T11:07:53.964979Z

:module-to-load-later-a also needs :init-fn, :entries or :exports, but yes thats correct

tianshu 2025-05-31T11:09:07.228989Z

With this config, I could load :module-to-load-after-a in :worker too?

thheller 2025-05-31T11:09:14.988379Z

yes

tianshu 2025-05-31T11:09:26.566069Z

Sounds awesome! I'm going to try this out.

thheller 2025-05-31T11:09:40.105809Z

(shadow.esm/dynamic-import "./module-to-load-later-a.js")

❤️ 1
thheller 2025-05-31T11:12:24.547299Z

if you are on the very latest shadow-cljs I added a new utility for lazy loading in esm

thheller 2025-05-31T11:12:55.372319Z

might be useful but I haven't documented it yet

tianshu 2025-05-31T11:13:42.001589Z

I can use it for sure

thheller 2025-05-31T11:13:57.165389Z

basically the idea is that you can load stuff by name. (shadow.esm/load-by-name 'some.ns/some-var)

tianshu 2025-05-31T11:13:58.960649Z

I'm also using shadow.css, btw

thheller 2025-05-31T11:14:27.343879Z

basically an abstraction so that you don't need to know module filenames and stuff

tianshu 2025-05-31T11:14:34.466379Z

So it's more like the shadow.lazy way?

thheller 2025-05-31T11:14:39.425059Z

yes

thheller 2025-05-31T11:16:31.134619Z

basically the gist is that the thing you want to load must have a metadata tag

thheller 2025-05-31T11:16:53.299359Z

so (defn ^:lazy-loadable some-var [...] ...)

thheller 2025-05-31T11:17:23.874029Z

load-by-name gives you a promise that resolves to a function that you can call to get the actual thing

tianshu 2025-05-31T11:17:58.829899Z

I can try it I think.

tianshu 2025-06-14T07:38:36.666879Z

I'm trying to change my build to :esm, and I'm trying to use shadow.esm/load-by-name to load function with ^:lazy-loadable. But it says "could not find loadable info".

thheller 2025-06-14T07:45:56.436979Z

is it tagged correctly?

thheller 2025-06-14T07:46:08.094209Z

(defn ^:lazy-loadable foo [] ...)

thheller 2025-06-14T07:46:22.855989Z

and using 'the.fully.qualified/foo?

thheller 2025-06-14T07:46:30.298449Z

needs the full symbol name

tianshu 2025-06-14T07:48:29.223319Z

I think I'm doing it correctly. I paste the following from my code, I hope I didn't make any stupid mistake.

(ns racepoker.shared.transport.facade
  ...)

(defn ^:lazy-loadable create-facade-transport
  [rpc]
  (FacadeTransport. rpc))
And for where it's load (but this is done in a .cljc file, not sure if it matters)
(esm/load-by-name 'racepoker.shared.transport.facade/create-facade-transport)

thheller 2025-06-14T07:49:52.179829Z

looks fine

tianshu 2025-06-14T07:49:59.789209Z

In build, their modules' relation is

...
         :facade {:entries    [racepoker.shared.transport.facade racepoker.shared.wallet.facade]
                  :depends-on #{:shared}}
         :shared {:entries []}
         ...

tianshu 2025-06-14T07:50:31.863019Z

This facade thing depends on shared. And the code to load is in shared module.

thheller 2025-06-14T07:50:48.352169Z

looks fine to me

tianshu 2025-06-14T07:50:49.866569Z

I debugged the esm.cljs, it seems that loadable is empty.

tianshu 2025-06-14T07:51:15.729929Z

I should expect all the names tagged are registered?

thheller 2025-06-14T07:51:43.961579Z

yes

tianshu 2025-06-14T07:51:59.499919Z

So it shouldn't be a problem with .cljc

thheller 2025-06-14T07:52:14.507299Z

nope, doesn't matter

thheller 2025-06-14T07:52:34.855319Z

shadow.esm.loadables in the browser console should be an object

tianshu 2025-06-14T07:53:04.840529Z

It's empty.

tianshu 2025-06-14T07:53:18.784879Z

But how I confirm that the module to be loaded is compiled?

thheller 2025-06-14T07:53:51.612049Z

in :output-dir grep for shadow.esm.add_loadable

thheller 2025-06-14T07:54:19.201839Z

that should be somewhere?

thheller 2025-06-14T07:55:02.409349Z

normally in :output-dir/shared.js?

tianshu 2025-06-14T07:55:28.128319Z

Yes, they are there

thheller 2025-06-14T07:56:00.400419Z

then unclear why shadow.esm.loadables would be empty? given that the only thing the function does is add stuff?

thheller 2025-06-14T07:56:31.912019Z

it currently does not have defonce? did you mess with that namespace in the REPL or something?

tianshu 2025-06-14T07:56:42.364999Z

I assume this is the definition of this add_loadable

shadow.esm.add_loadable = (function shadow$esm$add_loadable(the_name,module,accessor){
return (shadow.esm.loadables[cljs.core.str.cljs$core$IFn$_invoke$arity$1(the_name)] = ({"module": module, "get": accessor}));

tianshu 2025-06-14T07:57:06.608899Z

No, I didn't use repl yet. Just run npx shadow-cljs watch app

tianshu 2025-06-14T07:57:34.995079Z

I think I can change this add_loadable function to see the parameters

thheller 2025-06-14T07:57:59.176469Z

the parameters are literally in the code you just posted?

thheller 2025-06-14T07:58:12.407139Z

everything looks correct on this end?

tianshu 2025-06-14T07:58:55.778059Z

Hmm, no. I change it to

shadow.esm.add_loadable = (function shadow$esm$add_loadable(the_name,module,accessor){
    console.log(the_name, module, accessor);
return (shadow.esm.loadables[cljs.core.str.cljs$core$IFn$_invoke$arity$1(the_name)] = ({"module": module, "get": accessor}));
})
But I didn't see anything logged in console.

tianshu 2025-06-14T07:59:20.450539Z

There are add_loadable(...) in code, but they are not called?

tianshu 2025-06-14T08:00:28.520139Z

I think the shared.js in my case wasn't introduced in runtime.

tianshu 2025-06-14T08:00:58.189339Z

I added some logs in that file, nothing logged in console neither.

tianshu 2025-06-14T08:01:43.915779Z

Weird, but in network console, I can see the shared.js is loaded.

tianshu 2025-06-14T08:02:19.190329Z

thheller 2025-06-14T08:02:38.309989Z

actually I have a guess

thheller 2025-06-14T08:02:49.979749Z

I'm guessing you are calling load-by-name too early?

tianshu 2025-06-14T08:03:04.195779Z

Hmm, probably

tianshu 2025-06-14T08:03:14.170699Z

Because I don't have to do def for them

thheller 2025-06-14T08:03:22.173839Z

:shared doesn't have any :init-fn. so I'm assuming you are just running some code immediately when some namespace is loaded

tianshu 2025-06-14T08:03:23.030099Z

let me change it

tianshu 2025-06-14T08:03:32.239899Z

Yes

tianshu 2025-06-14T08:03:34.295029Z

You are right

tianshu 2025-06-14T08:03:36.608069Z

let me change it

thheller 2025-06-14T08:03:38.711409Z

that then fails and throws, and thus never gets to the code that actually adds the loadable

thheller 2025-06-14T08:03:48.586079Z

should really use :init-fn

thheller 2025-06-14T08:05:31.919529Z

fwiw if a module is loaded immediately it makes no sense to have it as a separate module

thheller 2025-06-14T08:05:41.529279Z

would be faster to just not split it out at all

tianshu 2025-06-14T08:06:23.467199Z

Well, in my old code, I do this and define them as vars. But they are promises, so wait them to be loaded when use it.

tianshu 2025-06-14T08:10:46.587399Z

I have a question about esm build. When I use esm, the page loading(mainly downloading js modules locally) takes much longer time. Is it normal?

thheller 2025-06-14T08:12:36.047009Z

in development you mean? release shouldn't be any different

thheller 2025-06-14T08:13:03.452549Z

development loads each file individually, so it may be slower if you webserver is slow or if you are loading thousands of files

thheller 2025-06-14T08:13:42.835699Z

haven't seen much of a difference personally, but it can be a little slower. depending on the amount of files and how fast the webserver serves them I guess

tianshu 2025-06-14T09:18:20.926739Z

I've changed our configuration to use :esm , it seems to work fine.

tianshu 2025-06-14T09:18:37.450579Z

Now we could have a much smaller worker, it's really nice.

tianshu 2025-06-14T09:23:09.568839Z

Hmm, in production build, there's an error

Uncaught SyntaxError: The requested module '' doesn't provide an export named: '$jscomp'
I release the build, serve it with a static web server on my port 9000.

tianshu 2025-06-14T09:25:04.701279Z

But the first line of my shared.js is something like

$var jscomp = ...

thheller 2025-06-14T09:41:01.644269Z

hmm might be polyfill related. not sure why it wouldn't be exposed though

thheller 2025-06-14T09:41:34.258239Z

try :prepend-js "globalThis.jscomp = jscomp" in the :shared module

tianshu 2025-06-14T09:42:16.038689Z

Is it a compiler option?

tianshu 2025-06-14T09:43:12.827109Z

Oh, sorry, in :shared module.

thheller 2025-06-14T09:43:36.233419Z

or better try :compiler-options {:output-feature-set :es-next}. probably no need for polyfills anyway

tianshu 2025-06-14T09:44:11.068009Z

Hmm, I think neither way works. I've tried output-feature-set.

tianshu 2025-06-14T09:45:23.982049Z

After adding :prepend-js "globalThis.jscomp = jscomp", I still don't see this line in shared.js.

tianshu 2025-06-14T09:47:15.145449Z

[:app] Build CSS completed.
------ WARNING #1 -  -----------------------------------------------------------
 Resource: shadow/module/shared/prepend.js:1:20
 variable jscomp is undeclared
--------------------------------------------------------------------------------
I guess it should be append instead of prepend?

tianshu 2025-06-14T09:48:43.418629Z

It doesn't work, both jscomp and $jscomp are not defined.

tianshu 2025-06-14T10:03:39.907029Z

If I add it via :append-js , the globalThis.jscomp = jscomp will become something like globalThis.G1 = jscomp.

thheller 2025-06-14T10:27:48.109069Z

yeah forget about that

tianshu 2025-06-14T10:28:32.190449Z

Manually adding something like export { $jscomp } gets rid of the error.

thheller 2025-06-14T10:29:08.349099Z

but not sure why that would be needed

thheller 2025-06-14T10:30:12.872789Z

:append "export { $jscomp }" for the :shared module should automate that append, so you don't have to do it manually

tianshu 2025-06-14T10:35:42.581129Z

Is there some rules for whether doing this?

tianshu 2025-06-14T10:35:52.050819Z

Or where can I debug this behavior?

tianshu 2025-06-14T10:38:52.562709Z

Add it via :append-js won't work, it says $jscomp is not defined during compilation.

thheller 2025-06-14T11:36:23.319539Z

I said :append not :append-js, wasn't a typo 😛

👍 1
tianshu 2025-06-14T11:50:35.272679Z

Thanks, I'll try it when with laptop. But should I debug where it generates this export statement?

thheller 2025-06-14T11:51:42.311559Z

I don't know why it would be need in the first place

thheller 2025-06-14T11:51:57.265479Z

kinda hard to guess at this. I haven't had this problem before, so no clue what is going on

thheller 2025-06-14T11:52:06.749339Z

kinda hard if there is no code I can look at

tianshu 2025-06-14T14:17:41.287599Z

Is this $jscomp handled by cljs compiler? I recently updated to 1.12.42.

thheller 2025-06-14T14:27:24.813559Z

please provide me with more information about what exactly the problem is

thheller 2025-06-14T14:27:28.614609Z

like some lines of generated JS or something

thheller 2025-06-14T14:27:43.502049Z

it is kinda frustrating to guess in the blind

thheller 2025-06-14T14:28:54.506419Z

for example this is the generated JS code of my "base" module

thheller 2025-06-14T14:28:56.235049Z

export const $APP = {};
export const shadow$provide = {};
const shadow_esm_import = function(x) { return import(x) };
export const $jscomp = {};

thheller 2025-06-14T14:29:12.728579Z

I did not add anything manually and you can clearly see the $jscomp export

thheller 2025-06-14T14:29:28.034829Z

so, what do the first few lines of your shared.js look like?

thheller 2025-06-14T14:32:17.616699Z

I don't exactly have a lot of "usage data". so no clue if it just happens to work for me and you are doing something "wrong". or shadow-cljs is doing something wrong and I just happen to not run into it

tianshu 2025-06-14T15:58:03.664699Z

These are some lines from the beginning. It's from release build, because it's only broken in release.

var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayFromIterator=function(a){for(var c,b=[];!(c=a.next()).done;)b.push(c.value);return b};$jscomp.arrayIteratorImpl=function(a){var c=0;return function(){return c<a.length?{done:!1,value:a[c++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};
$jscomp.makeIterator=function(a){var c=typeof Symbol!="undefined"&&Symbol.iterator&&a[Symbol.iterator];if(c)return c.call(a);if(typeof a.length=="number")return $jscomp.arrayIterator(a);throw Error(String(a)+" is not an iterable or ArrayLike");};$jscomp.arrayFromIterable=function(a){return a instanceof Array?a:$jscomp.arrayFromIterator($jscomp.makeIterator(a))};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_ES6=!1;$jscomp.ASSUME_ES2020=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;
$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.INSTRUMENT_ASYNC_CONTEXT=!0;$jscomp.IS_SYMBOL_NATIVE=typeof Symbol==="function"&&typeof Symbol("x")==="symbol";$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;
And this is the build configuration,
{:target           :esm
 :output-dir       "build/js"
 :release          {:output-dir "dist/race/js"}
 :modules
 {:solana    {:entries    [racepoker.shared.transport.solana racepoker.shared.wallet.solana]
              :depends-on #{:shared}}
  :sui       {:entries    [racepoker.shared.transport.sui]
              :depends-on #{:shared}}
  :facade    {:entries    [racepoker.shared.transport.facade racepoker.shared.wallet.facade]
              :depends-on #{:shared}}
  :shared    {:entries []
              :append  "export {$jscomp};"}
  ;; Web Worker
  :worker    {:init-fn    racepoker.worker/-main
              :depends-on #{:shared}}
  ;; Lobby page
  :lobby     {:init-fn    racepoker.lobby/-main
              :depends-on #{:shared}}
  ;; Table page
  :table     {:init-fn    racepoker.table/-main
              :depends-on #{:shared}}
  ;; Dashboard page
  :dashboard {:init-fn    racepoker.dashboard/-main
              :depends-on #{:shared}}
  ;; PWA page
  :pwa       {:init-fn    racepoker.pwa/-main
              :depends-on #{:shared}}}
 :css-modules
 {:lobby {:entries [racepoker.lobby.core]}
  :table {:entries [racepoker.table.core]}}
 :css-configs      ["styles/lobby.edn" "styles/table.edn"]
 :build-hooks      [(build/css-hook)]
 :compiler-options {:output-feature-set :es-next}}
:css-module and :css-configs are something I did for shadow.css, I think they are relevant. I have this :output-feature-se :es-next .

tianshu 2025-06-14T15:59:34.722679Z

Just like me know what information would be helpful. I'll also try to create a repro for the issue.

thheller 2025-06-14T16:01:02.024069Z

wouldn't really recommend mixing in the shadow.css stuff, but besides that looks ok

tianshu 2025-06-14T16:09:47.080599Z

I'm trying to create a repository to reproduce this

thheller 2025-06-14T16:10:39.968559Z

that would help

tianshu 2025-06-14T16:22:59.287169Z

I didn't success, but I found something.

tianshu 2025-06-14T16:23:54.661919Z

I made a new project, and it works. The output of shared.js is

export const $APP = {};
export const shadow$provide = {};
const shadow_esm_import = function(x) { return import(x) };
export const $jscomp = {};
var ....
But in my original project, there's no these exports

thheller 2025-06-14T16:24:38.421919Z

are you sure you are looking at the right output files? maybe changed dirs and looking at old files?

thheller 2025-06-14T16:24:52.470909Z

there must be least export const $APP = {};

tianshu 2025-06-14T16:29:30.964409Z

No, I just deleted the dist and recompiled. There's no those lines. This is the very beginning of that file.

tianshu 2025-06-14T16:32:05.464899Z

Yeah, I'm 100% sure.

tianshu 2025-06-14T16:32:33.285969Z

Other files contain this line import { $APP, shadow$provide, $jscomp } from "./shared.js";.

thheller 2025-06-14T16:51:06.157859Z

no export const $APP = {}; anywhere at all in any file?

tianshu 2025-06-14T16:56:20.810549Z

It has

export const $APP = {};
export const shadow$provide = {};
These two lines are the only two export const in shared.js.

tianshu 2025-06-14T16:56:33.542199Z

Not at the very beginning, at line 17

tianshu 2025-06-14T16:58:10.822759Z

Does the access to my repository or the compiled result helps? I can share

tianshu 2025-06-14T16:58:32.550269Z

I just don't know which information is helpful.

thheller 2025-06-14T16:59:21.911869Z

I think I found it

tianshu 2025-06-14T16:59:44.381479Z

I can patch shadow-cljs locally to test?

thheller 2025-06-14T17:01:47.467979Z

it actually is fairly obvious bug when you look at it 😛

thheller 2025-06-14T17:02:45.521479Z

the logic is all wrong

thheller 2025-06-14T17:03:05.917479Z

if there is polyfill-js it injects it, but not the $jscomp thing. which is only required if there are polyfills

thheller 2025-06-14T17:03:13.038679Z

so, kinda dumb 😛

tianshu 2025-06-14T17:04:47.683769Z

So it should be

(when (seq polyfill-js) (str polyfill-js "\n" "export const $jscomp = {};\n")))?

thheller 2025-06-14T17:05:04.839339Z

nah

thheller 2025-06-14T17:05:13.226069Z

easier to just always have it

thheller 2025-06-14T17:05:24.237619Z

otherwise need to adjust the import as well

tianshu 2025-06-14T17:05:41.692279Z

What is this polyfill-js?

tianshu 2025-06-14T17:06:41.305119Z

I guess the reason I have it non-empty is I have @solana/webcrypto-ed25519-polyfill dependency.

thheller 2025-06-14T17:07:21.865939Z

polyfills the closure compiler generates, so not coming from any npm packages directly.

tianshu 2025-06-14T17:08:49.719209Z

I see. so it's not that polyfill. 😂

tianshu 2025-06-14T17:09:09.785249Z

Will you fix it now?

thheller 2025-06-14T17:11:47.981459Z

try 3.1.7

tianshu 2025-06-14T17:20:33.755029Z

It works!

👍 1
tianshu 2025-06-14T17:20:37.304729Z

Thank you so much!

tianshu 2025-06-14T17:25:34.087169Z

hmmm, I met this

Uncaught SyntaxError: redeclaration of var $jscompshared.js:17:14note: Previously declared at line 1, column 5

thheller 2025-06-14T17:25:55.084819Z

the polyfill fun never ends 😛

thheller 2025-06-14T17:26:16.533579Z

maybe figure out why there are polyfills in the first place. there shouldn't be any if you set :output-feature-set :es-next

thheller 2025-06-14T17:26:28.871809Z

or maybe try :output-feature-set :no-transpile

tianshu 2025-06-14T17:27:34.216939Z

So in theory, if I use es-next, there shouldn't be polyfill.

thheller 2025-06-14T17:28:06.169109Z

can't say for sure. not quite sure when the closure compiler decides that it needs polyfills and for what

tianshu 2025-06-14T17:29:00.635699Z

Interesting, when I have the code running locally, it works. But after I upload it to cloudflare, it gives me this error.

thheller 2025-06-14T17:29:05.891019Z

but :es-next is the highest level it supports. so maybe the JS libs use some super last minute JS additions that the compiler wants to polyfill regardless

tianshu 2025-06-14T17:29:07.584599Z

It's the same build.

tianshu 2025-06-14T17:29:59.106119Z

oh, my mistake

tianshu 2025-06-14T17:30:26.443999Z

I forgot to delete that :append.

1
tianshu 2025-06-14T17:36:00.359389Z

Nope, the error occurs because the first line is

var $jscomp=$jscomp||{};
Then later there's a export const $jscomp. That's why it's redeclared

thheller 2025-06-14T17:37:56.060929Z

I guess you could just override that before sending to cloudflare 😛

tianshu 2025-06-14T17:38:24.901839Z

I could patch shadow.esm too...

thheller 2025-06-14T17:38:25.026889Z

if you replace that with something of equal length the source maps should still work

thheller 2025-06-14T17:38:33.480639Z

this isn't from shadow.esm

thheller 2025-06-14T17:38:37.931339Z

that from the closure library

tianshu 2025-06-14T17:39:05.066279Z

No, I mean, if it's export {$jscomp}, then there will be no issue.

thheller 2025-06-14T17:39:59.430529Z

hmm I guess

tianshu 2025-06-14T17:41:21.982939Z

The equavalent of export const $jscomp = ... is globalThis.$jscomp = ...?

thheller 2025-06-14T17:41:39.944539Z

don't understand that question, but no it is not

tianshu 2025-06-14T17:42:01.920899Z

Because I saw rest generation are still use var, I assume they are es3.

thheller 2025-06-14T17:42:42.424279Z

I don't know what that has to do with anything

thheller 2025-06-14T17:43:15.232059Z

closure library and cljs use var, for the most part

thheller 2025-06-14T17:44:54.838399Z

var $jscomp=$jscomp||{}; this construct is coming from the closure library, goog/base.js to be exact

thheller 2025-06-14T17:45:16.110229Z

has never been an issue before with :esm, so nothing tries to change it

tianshu 2025-06-14T17:46:38.930369Z

I see. So the problem is closure library generates this, but it's not exported. And in some cases, closure library doesn't generate this when it thinks it's not necessary. Do I get it right?

thheller 2025-06-14T17:46:53.002559Z

no

thheller 2025-06-14T17:47:20.607429Z

the closure compiler is not aware that I'm turning its output into ESM. in fact I have to actively hide that.

thheller 2025-06-14T17:47:32.333469Z

so it just generates the same usual code it would generate for :browser

thheller 2025-06-14T17:47:44.273139Z

I then patch it all over the place to inject the import/export stuff

thheller 2025-06-14T17:48:09.106019Z

literally just do a search and replace with the problematic bit before sending it to cloudflare when it works in the browser

thheller 2025-06-14T17:48:38.829119Z

or make an issue. I'm done for the day, so not trying to figure that out now

tianshu 2025-06-14T17:48:48.476469Z

Sure, no problem!

tianshu 2025-07-07T15:13:15.190949Z

I checked this issue again, and I think I found something. But I want to confirm my thinking. I played with esm.clj , I found the polyfill-js is actually applied twice that it causes the polyfill section appears in output twice. It's like

var $jscomp=$jscomp|{}             |<- polyfill code injection, from somewhere idk
....
export const $APP = {};            |<- 3 lines shim
export const shadow$provide = {};
export const $jscomp = {};         |<- polyfill code again, from esm.clj#L549
var $jscomp=$jscomp|{} (
...
shadow$provide[0]=...              
The second injection comes from the (inject-polyfill-js) at esm.clj#L549 . The first injection comes somewhere I don't know. If I comment esm.clj#L549 , there will be only one left.

tianshu 2025-07-07T15:14:47.042519Z

This duplicated injections won't hurt, but the export const $jscomp = {}; after existing declaration (the first injection) seems to be the problem. I have to change it to export { $jscomp }; to make it work.

tianshu 2025-07-07T15:16:01.081889Z

Oh you said this var $jscomp = $jscomp|{}; is from closure library.