Fork me on GitHub
#shadow-cljs
<
2024-06-12
>
hifumi12306:06:25

In a :target :browser build, I am setting the following options.

:js-options {:js-provider :external
                :external-index "public/index.js"
                :external-index-format :esm}
I am using Vite as the bundler. When I make dev builds with Shadow CLJS, the index.html needs to look like this

    
or else I get the error ReferenceError: goog is not defined. However, when I make release builds, the index.html file needs to look like this

    
or else I get the error Uncaught SyntaxError: import declarations may only appear at top level of a module Am I doing something wrong?

1
thheller07:06:58

its a bit confusing to answer since I don't really know what vite is doing. :browser definitely does not output anything using import for :target :browser

thheller07:06:29

I'd say look at the actual files and make sure vite doesn't do anything to main.js?

hifumi12307:06:13

Well, unfortunately I am now simply unable to build anything, despite clearing build caches and deleting old build artifacts 😛 In the case of :target :esm, I was facing an issue stating shadow$bridge was undefined, even though the index.js file contains a line like

globalThis.shadow$bridge = ...
Both the index.js and main.js seemed to be actual ESM output. But for some reason shadow$bridge isnt being added to the global this object. May be some strange thing with ESM. Not sure yet. But Vite was successfully resolving dependencies declared in the index.js file. The problems occurred as soon as main.js was loaded and attempted to call shadow$bridge

thheller07:06:23

any reason you are using :target :browser at all? :target :esm might be a better fit for vite?

hifumi12307:06:41

Initially I was using :target :browser since my build was already using that. I decided to try switching to :target :esm in attempt to fix the (now unreproducible) issue I described at the start of this thread

thheller07:06:07

the thread does not mention :target :esm at all. it mentions :external-index-format which is something else entirely

hifumi12307:06:32

OK so I am testing with :target :esm and a single main module. Here is a snippet of the config

{:target :esm
   :output-dir "public/app/js"
   :asset-path "/app/js"
   :modules {:main {:entries []
                    :init-fn 
   :compiler-options {:output-feature-set :es-next}
   :js-options {:js-provider :external
                :external-index "public/app/js/index.js"
                :external-index-format :esm}}
the index.js file looks like this
import * as i0 from "react";
import * as i1 from "react-dom";
...
const ALL = {};
globalThis.shadow$bridge = function(name) {
  const ret = ALL[name];
  if (ret == undefined) {
    throw new Error("Dependency: " + name + " not provided by external JS!");
  } else {
    return ret;
  }
};
...
the main.js file looks like this
import "./cljs-runtime/shadow.module.main.prepend.js";
SHADOW_ENV.setLoaded("shadow.module.main.prepend.js");
...
try { my.app.init(); } catch (e) { console.error("An error occurred when calling ()"); console.error(e); }
In the browser, I see
Uncaught ReferenceError: shadow$bridge is not defined
from the file
shadow.js.shim.module$react.js:5:1

thheller07:06:05

:js-options {:js-provider :external is an option for :target :browser ONLY

thheller07:06:17

it does NOT work with :target :esm

thheller07:06:48

{:target :esm
 :output-dir "public/app/js"
 :modules {:main {:entries []
                  :init-fn 
 :compiler-options {:output-feature-set :es-next}
 :js-options {:js-provider :import}}

hifumi12307:06:04

I suspected. So I also tried setting :js-provider :import but with no success either IIRC. I’m trying it again, with the exact same suggestion provided just now

thheller07:06:05

then you just let vite process the public/app/js/main.js

thheller07:06:25

<script type="module" src="/public/index.js"></script> disappears and no longer exists

thheller07:06:05

is the only thing remaining (or however this works so that vite processes this file. I'm assuming you are using its http server

hifumi12307:06:35

Yeah I am using its HTTP server

thheller07:06:05

I never actually tried vite, so no clue what it does and if this works

thheller07:06:11

but this should be all there is to do

hifumi12307:06:23

Using your suggestions, I now have a single main.js file with import statements and shadow.env.SetLoaded(…) calls. Each import successfully runs (the Network tab shows vite fetching each module). Instead I get

Uncaught TypeError: Failed to resolve module specifier "react". Relative references must start with either "/", "./", or "../".
I assume this is not a shadow-cljs error anymore

thheller07:06:01

ok, this seems like vite then did NOT process this file as it should have rewritten that

thheller07:06:37

maybe you excluded it via config in the previous setup somehow?

hifumi12307:06:00

Your guess about processing seems to be correct. With your suggestions, I ran

npx shadow-cljs release app && npx vite build
and the CLJS app works fine. The only issue now is when using npx vite, which is the dev mode

hifumi12307:06:12

In other words, this is no longer a shadow-cljs issue

thheller07:06:37

it could be that vite just doesn't recognize the files shadow-cljs creates and it is definitely a bit of stretch calling this ESM for development builds

thheller08:06:20

if you want to setup a repo with this I can take a look

hifumi12308:06:44

Ah I think I found the problem. In the middle of creating a mini repro for you

hifumi12308:06:32

Looks like my bigger, more complex project, does not configure the index.html correctly. In short, Vite looks at the script tags in your HTML in order to determine what to bundle

hifumi12308:06:53

For anyone else looking at this thread in the future: double-check that the paths used in the