Fork me on GitHub
#shadow-cljs
<
2024-02-07
>
agorgl14:02:17

Hello! I'm trying to use this npm package with clojurescript / shadow-cljs: https://vercel.com/font The relevant js import seems to be: import { GeistSans } from 'geist/font/sans'; that afaik should translate to ["geist/font/sans" :refer [GeistSans]] in my namespace's require in cljs I've tried all the different combinations that the docs say (with $default and whatsoever) I get this error on my js console:

shadow-cljs - failed to load module$node_modules$geist$dist$sans
shadow.js.jsRequire @ js.js:89
js.js:90 TypeError: (0 , require.esmDefault(...).default) is not a function
    at shadow$provide.module$node_modules$geist$dist$sans (sans.js:7:19)
    at shadow.js.jsRequire (js.js:81:18)
    at shadow.js.require (js.js:137:20)
    at eval (example.reframe_playground.views.js:8:51)
    at eval (<anonymous>)
    at goog.globalEval (app.js:434:11)
    at env.evalLoad (app.js:1405:12)
    at app.js:1691:12
shadow.js.jsRequire @ js.js:90
Any ideas what I'm doing wrong?

thheller16:02:55

solution at the end might be the same

thheller16:02:14

but I suspect that the code will end up relying on features shadow-cljs does not support, such as bundling fonts or css

thheller16:02:56

I suspect that they expect you to be using the vercel platform which would take care of this

thheller16:02:35

you could just take the actual font files and use them in your page via css

thheller16:02:16

yep, thats not something shadow-cljs does

agorgl16:02:42

The font page seems to have instructions for non-nextjs projects (plain react with tailwind), thats why I thought it could work

agorgl16:02:00

Let me try the above and I'll check back

thheller16:02:52

just "Get the .zip"

thheller16:02:43

take the zip, put it into a http reachable folder (e.g. "public/css") and add a css file to include the fonts. you can use this css file as a reference

thheller16:02:04

just need to swap those file name references with the geist font names

thheller16:02:29

works exactly like any other font

thheller16:02:33

without any JS

agorgl17:02:40

Yep I know that in the end this is the proper solution

agorgl17:02:57

I'm just experimenting on how far the js interop goes

thheller17:02:38

the font itself has no code, only what I guess are "markers" for the next platform to do its thing

agorgl17:02:34

Ok I just tried

:target-defaults
 {:browser
  {:js-options {:entry-keys ["module" "browser" "main"]
                :export-conditions ["import" "module" "browser" "require" "default"]}}}
behavior seems the same

agorgl17:02:02

I tried with

:target-defaults
 {:browser
  {:js-options {:ignore-exports true}}}
I get a build error now: The required JS dependency "geist/font/sans" is not available

agorgl17:02:01

(Just to be clear, I'm not really interested in adding this font to my playground project, I'm just trying to find out why this specific npm package does not work, in order to learn how to debug this kind of stuff)

thheller17:02:17

well you are debugging a package that has no actual JS in it

thheller17:02:12

this file uses "exports", so by setting :ignore-exports you are ignoring them

thheller17:02:25

and the fallback then cannot find font/sans in that package

thheller17:02:56

this is the JS file you get for geist/font/sans https://unpkg.com/[email protected]/dist/sans.js

thheller17:02:57

the problem itself is in this package "next/font/local"

thheller17:02:10

and if you follow that along you end up with this file

thheller17:02:27

this is empty

thheller17:02:51

and as I suspect serves only as a marker for the next/vercel platform tools

thheller17:02:02

it at the very least does not have a localFont default export

thheller17:02:09

which is why you are getting the error

agorgl17:02:26

Very insightful deep dive

thheller17:02:28

shadow-cljs is following this all along, there is just code missing. everything else worked as its supposed to

agorgl17:02:57

I understand now why it breaks

agorgl17:02:09

But my question now is why it works in js in the first place

agorgl17:02:18

How is the localFont fn defined?

thheller17:02:23

it doesn't work

thheller17:02:51

like I said. I suspect that vercel is providing special support for this during compilation

agorgl17:02:10

Ah yeah that makes sense

thheller17:02:11

which the article I linked seems to suggest

thheller17:02:22

the compiler then replaces this call with something else I guess

agorgl17:02:46

Thank you VERY much for the explanation

agorgl17:02:04

It was a nice dive

agorgl17:02:13

Keep up the fantastic work with shadow-cljs!

thheller17:02:53

I wish I understood why JS people have to add stuff like this 😛

agorgl17:02:26

I know, the ecosystem is already really badly stitched with lots of awful stuff

agorgl17:02:37

Then you have this on top of it

HomerClojure16:02:26

Hello, I'm having problems with CSP (Content Security Policy) that the problem is coming from compiled .js code, reported as script-src and rule eval. The block of code is below.

oa=function(a,b,e){oa=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?caa:daa;return oa.apply(null,arguments)};ra=function(a,b){var e=Array.prototype.slice.call(arguments,1);return function(){var g=e.slice();g.push.apply(g,arguments);return a.apply(this,g)}};ta=function(a){(0,eval <----- )(a)}; 
The eval shold I consider as a shadow-cljs source or closure compile source? I can't permit using the rule unsafe-eval in this case.

thheller16:02:11

run the build with npx shadow-cljs release <your-build-id> --pseudo-names to identify where this eval is from

thheller16:02:22

kinda hard to tell otherwise

thheller16:02:44

shadow-cljs or closure do not put eval in your code, so it is coming from other code you are using

HomerClojure16:02:43

it seems nothing changed

oa=function(a,b,e){oa=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?caa:daa;return oa.apply(null,arguments)};ra=function(a,b){var e=Array.prototype.slice.call(arguments,1);return function(){var g=e.slice();g.push.apply(g,arguments);return a.apply(this,g)}};ta=function(a){(0,eval)(a)};
I have a compiler from Clojure Function:
from package.json:
{..."build:release-static": "npx shadow-cljs run dev.build/build-release-static --pseudo-names",...}

(defn build-release-static []
  (clean-compiled-resources)
  (export-image-assets-and-replace-paths)
  (export-deeplinking-assets)
  (shadow/release environment {:verbose true})
  (compile-sass)
  (export-assets)
  (index-csp))
Should I put it into shadow/release with :verbose?

thheller16:02:13

well if you are building like that the option won't apply

thheller16:02:45

just run the regular release command, you don't need all this extra stuff just for debugging this

thheller16:02:16

or change to (shadow/release environment {:verbose true :pseudo-names true})

👍 1
HomerClojure16:02:31

$goog$globalEval$$ = function($script$jscomp$1$$) {
  (0,eval)($script$jscomp$1$$);
};

thheller17:02:00

ok, do another search for where $goog$globalEval$$ is used

HomerClojure17:02:28

if ($JSCompiler_StaticMethods_evaluateCode_$self$$.$sourceUrlInjection_$ || $JSCompiler_StaticMethods_evaluateCode_$self$$.$debugMode_$ && ($goog$userAgent$product$CHROME$$ || $goog$labs$userAgent$browser$matchFirefox_$$() && 0 <= $goog$string$internal$compareVersions$$($goog$labs$userAgent$browser$getVersion$$(), "36"))) {
      for (var $i$jscomp$165$$ = 0; $i$jscomp$165$$ < $uris$jscomp$4$$.length; $i$jscomp$165$$++) {
        $goog$globalEval$$($texts$$[$i$jscomp$165$$] + " //# sourceURL\x3d" + $uris$jscomp$4$$[$i$jscomp$165$$]);
      }
    } else {
      $goog$globalEval$$($texts$$.join("\n"));
    }

thheller17:02:06

as in this example report you'll find a group for org.clojure/google-closure-library https://code.thheller.com/demos/build-report/ui-report.html

thheller17:02:00

expand that and look for goog/labs/userAgent I guess

thheller17:02:29

if you mouseover that it'll tell you what end up including that

thheller17:02:00

I have a suspicion

thheller17:02:13

let me check the code. I was not aware there is eval in it

thheller17:02:53

trouble is that its kinda hard to identify what this code actually was before optimizations

thheller17:02:33

you can try finding //# sourceURL in the source code prior to optimization

thheller17:02:47

so for example from a dev build in its cljs-runtime folder

thheller17:02:59

shouldn't get too many hits

thheller17:02:37

do you use multiple :modules?

thheller17:02:12

hmm yeah that might be it then. the loader dynamically loads modules and evals them potentially

HomerClojure17:02:21

so, what's the ways?

thheller17:02:28

good question. I don't think there is one

thheller17:02:57

I mean I guess you could switch to using :target :esm and use esm instead

thheller17:02:10

how are you loading the modules in the code?

HomerClojure17:02:28

I thougth it is internal from shadow, rigth?

thheller17:02:18

if you are using shadow.lazy then yes

HomerClojure17:02:34

I only use this in config:

...:modules           {:base      {:output-to  "resources/public/js/compiled/base.js"
                                               :init-fn    core-client.core/init}
                                   :checkout  {:output-to  "resources/public/js/compiled/checkout.js"
                                               :entries    [checkout.core]
                                               :depends-on #{:base}}
                                   :site      {:output-to  "resources/public/js/compiled/site.js"
                                                :entries    [site.core]
                                                :depends-on #{:base}} ....

thheller17:02:11

those :output-to do absolutely nothing, so you can remove them

thheller17:02:20

do you also set :module-loader true?

thheller17:02:42

or use shadow.lazy, shadow.loader, cljs.loader in your code?

thheller17:02:55

ok thats is what is causing the problem

thheller17:02:16

thats why I'm asking how you are loading the code

thheller17:02:27

do you actually use any of the above namespaces in your code?

thheller17:02:38

or do you just load the files manually via HTML?

thheller17:02:50

is there any code loading checkout.js dynamically I mean?

HomerClojure17:02:25

I only load the base.<hash>.js and use the namespaces.

thheller17:02:51

so you are not dynamically loading any modules in your code?

thheller17:02:07

how/where is checkout.core used?

thheller17:02:21

if you are not loading the modules dynamically then you do not need :module-loader true

HomerClojure17:02:03

sorry, I use shadows.loader

thheller17:02:37

hmm yeah that is the problem then

HomerClojure17:02:18

shadows.loader call eval?

thheller17:02:00

indirectly yes I guess

HomerClojure17:02:36

so, what is the solution for that?

thheller17:02:46

you can try one thing

thheller17:02:18

copy this file into your source files

thheller17:02:26

so src/main/shadow/loader.js or so

thheller17:02:51

add ml.setUseScriptTags(true);

thheller17:02:12

maybe thats enough to get rid of the eval parts. I guess the defaults use eval

thheller17:02:28

goog.module.ModuleLoader is the actual thing doing the evals

thheller17:02:07

that being the problematic code

HomerClojure17:02:26

so, if you could help me to understand right: copy the loader.js to source code (where?), and include this ml.setUseScriptTags(true); in that line you told?

HomerClojure17:02:36

is there any change do I have to fix in compile process?

thheller17:02:35

where do you keep your sources? src or src/main?

thheller17:02:30

ok so create the src/cljs/shadow folder and put the linked loader.js in it

thheller17:02:43

that will make shadow-cljs use that file over the file it ships itself

thheller17:02:06

thus you can add the line above to it to test if this actually fixes the problem and ends up removing the eval

thheller17:02:28

no further changes required, just do another release build and see it eval is still in it

thheller17:02:49

the intention is that the goog.module.ModuleLoader appears to have two separate loading mechanisms, one using eval one using script tags

thheller17:02:56

script tags might not require unsafe-eval

thheller17:02:35

I'm debugging with you, I never had to deal with the CSP restriction and I suspect no one else has

thheller17:02:41

so just testing if this can work

thheller17:02:56

if it works I can add an option for this so you don't have to do this manual patching

HomerClojure17:02:06

that's amazing man, thank you very very much

thheller17:02:27

first need to find out if it actually works though

HomerClojure18:02:47

so the compiled code should remove the $goog$globalEval$$?

HomerClojure18:02:05

I included the source "loader.js" and included that line

thheller18:02:07

assuming thats in the src/cljs folder then it looks correct

thheller18:02:20

we are testing whether this removes the eval or not, I thought that it might. if the above doesn't work then I guess it does not.

HomerClojure18:02:47

so, I have to deploy to see if is it work?

thheller18:02:24

you should be able to tell by looking for eval in the new output?

thheller18:02:10

dunno if a full new deployment is necessary. if eval is still there it'll likely fail for the same reason

HomerClojure18:02:24

I'm still seeing the

$goog$globalEval$$ = function($script$jscomp$1$$) {
  (0,eval)($script$jscomp$1$$);
};

thheller18:02:01

I actually don't know if CSP actually already fails when it sees eval, or only when you actually try to eval?

thheller18:02:12

if its the latter it may actually work

thheller18:02:28

(don't forget to remove the :pseudo-names btw, that was only for debugging)

HomerClojure18:02:39

I'm running in CSP report-only mode, but if it catches it as eval , browser will not permit runs the eval

thheller18:02:10

as far as I can tell if won't try to eval when using script tags

thheller18:02:19

so might be worth trying I guess

thheller18:02:09

you can try adding console.log("actually set to use script tags") in the loader.js file, just to verify this hack actually worked

HomerClojure18:02:36

yes, the system have an event that calls shadlow.loader , that is the time it's been reported

HomerClojure18:02:13

console.log("actually set to use script tags") -> where?

thheller18:02:14

yes, prior to this change it would have definitely used eval, the question is if it still does

thheller18:02:24

anywhere in shadow/loader.js

thheller18:02:40

just want to verify that this file is actually getting used over the default

HomerClojure18:02:27

should I see this message where? In compile process?

thheller18:02:58

in the final output by searching for it or at runtime, not during compilation no

HomerClojure18:02:56

sorry, long process to run the system locally

HomerClojure18:02:25

worked -> actually set to use script tags

HomerClojure18:02:14

so, at this point , will it runs ok?

HomerClojure18:02:27

maybe it's depends the setUseScriptTags

thheller18:02:44

I do not know. I cannot answer this. you need to check if it runs

HomerClojure18:02:21

well, it seems that the new loader.js runs

thheller18:02:46

so no more CSP errors?

HomerClojure18:02:32

I have to deploy to qa environment that has all the csp rules for checking

HomerClojure18:02:53

I'll do it and tell ok? thank you

👍 1
HomerClojure19:02:43

I'm still using "shadow-cljs": "2.15.3", is that a problem?

HomerClojure19:02:26

I don't think so, because it's 3 years old file

thheller19:02:05

that shouldn't matter

HomerClojure21:02:46

Hello @U05224H0W , It does not work

HomerClojure21:02:27

it's complaining about the eval stuffs

HomerClojure21:02:33

{
    "csp-report": {
        "document-uri": "https://*******/****",
        "effective-directive": "script-src",
        "original-policy": "...",
        "blocked-uri": "eval",
        "line-number": 1903,
        "column-number": 322,
        "source-file": "https://*******/js/compiled/base.ABCF3918963DA671CD471DB45FB2D01D.js",
        "status-code": 200
    }
}

thheller06:02:51

hmm then I guess your only option is switching to :target :esm and then restructuring a bit to not use :module-loader true and shadow.loader

HomerClojure10:02:48

ok, I'll do it, do I have to use both or first try removing module-loader?

thheller10:02:50

well, removing module-loader but keeping the lazy loaded functionality requires going :esm

thheller10:02:13

you can remove the multiple modules, but I suspect you want to keep this

HomerClojure12:02:09

so the main change is for :taget :esm?

thheller12:02:47

I don't know what you mean by that question

thheller12:02:18

I haven't seen your full build config

thheller12:02:34

it could just be switching :target :browser to :target :esm

HomerClojure12:02:37

test removing :module-load true and :target :esm ?

thheller12:02:06

well, your code will then fail if its still using shadow.loader

thheller12:02:10

but yes, that is step one

HomerClojure12:02:19

the compiling failed with :target :esm

thheller13:02:05

that fails in your code, not something I can debug for you. dev.build is not part of shadow-cljs

HomerClojure13:02:21

humm, I'm using version 2.15.3 . Is there a problem with that version?

thheller13:02:19

no. I repeat this is failing in code that is not part of shadow-cljs, it has never existed in shadow-cljs. It is your code that is failing. I cannot say why it is failing.

thheller13:02:54

run npx shadow-cljs release app, if that fails I can help you. otherwise you are on your own

HomerClojure13:02:43

yep, something here

HomerClojure13:02:45

it said that the manifest.edn was not created

thheller13:02:58

ok. that is a :target :browser only feature, :target :esm does not create that

HomerClojure13:02:33

hummm, I use that files to read the main js with hash to inject into index.html

HomerClojure13:02:09

it seems that the :esm does not create the modules with hash in .js file names

thheller13:02:13

:module-hash-names is also a :target :browser only feature, that is correct

thheller13:02:37

I usually recommend people don't use that, even for :browser

HomerClojure13:02:20

well, it's a feature that is working 4 years

HomerClojure13:02:47

so, is the :esm the solution or a guess? Should I remove the shadow/loader too?

HomerClojure13:02:54

and use dynamic-import?

thheller13:02:06

the simplest solution to me is allowing unsafe-eval, but you seem to have reasons thats not possible

thheller13:02:22

you must remove shadow.loader yes, that is the source of your issue after all

HomerClojure13:02:50

yes, I'm implementing the CSP rules for security reasons

thheller13:02:14

well you could still allow if for your sources, and just disallow it for others

HomerClojure13:02:20

you mean include all the modules into script-src ?

thheller13:02:42

your domain

HomerClojure13:02:21

well, I'm using 'self' as the rule

HomerClojure13:02:48

but the eval is not allowed

thheller13:02:51

then :esm it is I guess

HomerClojure13:02:03

what is the propose of scriptTag? Do something like inject a <script> ..... Module .... </script> ?

thheller14:02:11

yes, regular old JS doesn't have a standard way of fetching additiontal JS dynamically. so the goog.module.ModuleLoader will either fetch the code via XHR and then eval it

thheller14:02:31

or add a script tag and load it that way. that way should not require eval

thheller14:02:25

with ESM you can just do a dynamic require to load the code, supported by the browser natively

thheller14:02:11

but you cannot use dynamic import to load a :browser module dynamically, since the rules for ESM are different and as such the output won't work

HomerClojure14:02:42

yes, that's I'm thinking

thheller14:02:06

so, only works if everything is ESM from the start, meaning :target :esm

HomerClojure16:02:40

I could solve the problem with eval loading, replacing the shadow.load

thheller16:02:41

which route did you go?

HomerClojure17:02:37

I uses inject script tag into header

HomerClojure19:02:26

so, one thing, how do you convert the keyword to js path? In load function

thheller19:02:01

I do not understand that question. what load function?

HomerClojure19:02:05

this function receives the keyword defined into shadow-cljs.edn modules

HomerClojure19:02:04

like here

:modules           {:base      {:output-to  "resources/public/js/compiled/base.js"
                                               :init-fn    client.core/init}

thheller19:02:43

the compiler injects this data into the build when :module-loader is true

thheller19:02:06

:output-to has no effect there, you can remove that

HomerClojure20:02:10

ok, but where is this mapping injected?

thheller20:02:23

what do you intend to do with it? I'm asking because this is all strictly for shadow.loader, if you are not using that you are not supposed to use that data

thheller20:02:43

so start with what you are trying to do instead, then I can make suggestions

HomerClojure20:02:16

for using the script tag aproach I have to know the filename of .js files. I have to use the hashes in the filenames because of caches. So, to this new module loader I wrote, how can I get the files if it will be dynamic because of hashes?

HomerClojure21:02:51

I'm thinking use the module-loader.edn to read all the references

pandeiro18:02:49

I have a macro defined with an non-passing assert clause which throws an error (correctly) when I compile in dev with shadow.cljs.devtools.api/watch but doesn't show any error when building for release with shadow.cljs.devtools.api/release. Would this be the expected behavior?

thheller18:02:53

if by assert you mean :pre when yes, those are removed by default for release builds

thheller18:02:17

you can set :compiler-options {:elide-asserts false} to keep them

pandeiro18:02:10

I don't see the :pre keyword but the macro has the form

(defmacro thing
  ([args]
    (assert ...)
    ...))

pandeiro18:02:18

Is that the same thing?

thheller18:02:30

hmm I guess I didn't understand the question

thheller18:02:40

that assert should still fire, given that its in the macro, not the emitted CLJS code

thheller18:02:08

hmm actually thats correct

🚀 1
pandeiro18:02:26

If I wanted to balance correctness checks and performance, would it possible elide CLJS asserts but maintain CLJ (macro) asserts?

pandeiro18:02:42

(in this case I have a fairly complex macro that can be easily misused)

thheller18:02:45

just don't use assert in the first place

valerauko18:02:49

You'd have to write assertions without "assert"s

thheller18:02:59

it makes absolutely horrific error messages that are basically impossible to debug

thheller18:02:08

just throw an exception instead

thheller18:02:05

"assert" by my definition are "this must not happen, if it does the world is allowed to explode"

thheller18:02:13

they are not for "hey, you provided an incorrect input"

thheller18:02:54

I'm technically not against changing how *assert* is bound, but the CLJS compiler will bind it like that too

valerauko18:02:13

since asserts work so differently and are so easy to toggle, sadly many people have many different interpretations

pandeiro18:02:20

I definitely want to break the build in the case that this assert fails on any code written, so I will follow your advice and dump the assert and just throw

pandeiro18:02:53

I agree the semantics are a bit vague now that I actually think about all of this

pandeiro18:02:15

Thanks very much, both of you!