shadow-cljs

thheller 2025-04-24T07:33:14.211049Z

I just released shadow-cljs version 3.0.2. the major version bump is due to the breaking change of the closure compiler requiring java version 21+. pretty much the only change is getting the newer closure compiler to work again, nothing else feature wise. please test and report back. this includes the first closure compiler update in over a year, so no idea what might be broken now. needs broader testing. shouldn't require any cljs related changes, so only maybe bumping your java version if too old.

👍 1
👀 1
Roman Liutikov 2025-04-24T09:15:14.060999Z

Now that public class fields syntax support has landed in Closure Compiler and private fields are still in development (as mentioned on GitHub they might work on it in Q3 this years but who knows), but the syntax is already parsed by Closure, would it make sense to add a custom compiler pass in shadow that rewrites private fields into prefixed public fields?

thheller 2025-04-24T09:16:19.158249Z

I have not checked the state of this at all yet, so no clue what even happens.

thheller 2025-04-24T09:16:40.476849Z

but yes I want this to work. ideally just by leaving things as they are without rewriting

thheller 2025-04-24T09:16:48.864509Z

dunno what JS tools actually do?

Roman Liutikov 2025-04-24T09:18:54.341319Z

> ideally just by leaving things as they are without rewriting that's an option as well, browser support seems to be quite good https://caniuse.com/mdn-javascript_classes_private_class_fields

Roman Liutikov 2025-04-24T09:19:18.703659Z

> dunno what JS tools actually do? depending on language version settings they will either rewrite or leave it as is

Roman Liutikov 2025-04-24T09:21:16.524629Z

for rewrite, here's what Babel is doing

class Hello {
  #yo = "asd"
  #pp = "888"
}
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
var _yo = /*#__PURE__*/new WeakMap();
var _pp = /*#__PURE__*/new WeakMap();
class Hello {
  constructor() {
    _classPrivateFieldInitSpec(this, _yo, "asd");
    _classPrivateFieldInitSpec(this, _pp, "888");
  }
}

thheller 2025-04-24T09:23:16.435829Z

meh

thheller 2025-04-24T09:23:59.136419Z

given that a private field can only be accessed in the context of that class definition that should be easier to just do

class Hello {
 __some_prefix__yo = "asd";
  __some_prefix__pp = "888";
}

thheller 2025-04-24T09:25:34.233379Z

and then just replace all uses of #yo in that class context with __some_prefix__yo where __some_prefix is something unique, maybe based on filename

👍 1
thheller 2025-04-24T09:26:12.234319Z

I don't care about enforcing private field semantics, like checking redeclaration. assuming the code we get is already "correct"

thheller 2025-04-24T09:30:46.562639Z

but likely I will just bump the default :output-feature-set to something very recent

thheller 2025-04-24T09:31:04.981829Z

really doesn't make sense anymore to be targeting 5 year old browsers by default

thheller 2025-04-24T09:32:01.800869Z

there should be fallback if needed, but not used by default

Roman Liutikov 2025-04-24T09:44:07.075999Z

not sure if expected but :output-feature-set :es-unsupported and :language-in/ou :unsupported didn't work

thheller 2025-04-24T09:46:20.653579Z

:language-out/in you never need

thheller 2025-04-24T09:46:49.371359Z

:language-in :ecmascript-next-in is the default. doesn't go any higher than that

thheller 2025-04-24T09:47:20.755809Z

what does "didn't work" mean? some closure error or what is happening?

Roman Liutikov 2025-04-24T09:52:10.319909Z

> what does "didn't work" mean? some closure error or what is happening? compiler still spits out the same error This language feature is not currently supported by the compiler: Private class properties.

Roman Liutikov 2025-04-24T09:53:49.105729Z

I wonder if it's an error though shadow prints this

[:examples] Compiling ...
Closure compilation failed with 2 errors
--- uix/hello.js:3
This language feature is not currently supported by the compiler: Private class properties.

thheller 2025-04-24T09:53:57.202769Z

:output-feature-set :no-transpile?

👀 1
Roman Liutikov 2025-04-24T09:54:02.777999Z

but in closure code it's a warning

static String languageFeatureWarningMessage(Feature feature) {
    LanguageMode forFeature = LanguageMode.minimumRequiredFor(feature);

    if (forFeature == LanguageMode.UNSUPPORTED) {
      return "This language feature is not currently supported by the compiler: " + feature;
    } else {
      return "This language feature is only supported for "
          + LanguageMode.minimumRequiredFor(feature)
          + " mode or better: "
          + feature;
    }
  }

void maybeWarnForFeature(ParseTree node, Feature feature) {
    features = features.with(feature);
    if (withinClosureUnawareCodeRange(node.location.start.line, node.location.start.column)) {
      return;
    }
    if (!isSupportedForInputLanguageMode(feature)) {
      errorReporter.warning(
          languageFeatureWarningMessage(feature), sourceName, lineno(node), charno(node));
    }
  }

thheller 2025-04-24T09:55:10.367009Z

or was that :language-out :no-transpile? can't remember

Roman Liutikov 2025-04-24T09:55:21.904399Z

note a check for LanguageMode.UNSUPPORTED above

thheller 2025-04-24T09:56:21.867299Z

from what context is that warning snippet? maybe just a compiler pass than can be disabled?

thheller 2025-04-24T09:58:06.189029Z

that doesn't seem relevant? we do not want to create IR, only use existing one?

thheller 2025-04-24T09:58:49.312829Z

or does it fail because of the initial parse? that means its still not parseable, so nothing shadow-cljs can do?

Roman Liutikov 2025-04-24T10:01:28.043489Z

that's the only place where the error message appears in the codebase also https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java#L270-L272

// ES_UNSUPPORTED: Features that we can parse, but not yet supported in all checks
    // Part of ES2022. Support will improve as implementation progresses.
    PRIVATE_CLASS_PROPERTIES("Private class properties", LangVersion.ES_UNSUPPORTED),

Roman Liutikov 2025-04-24T10:03:00.587149Z

ha and there's already a placeholder class for rewriting private props https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/RewritePrivateClassProperties.java

Roman Liutikov 2025-04-24T10:11:12.972649Z

oh I see, so the PRIVATE_CLASS_PROPERTIES feature set is a part of ES_UNSUPPORTED language mode which is a part of UNSUPPORTED language mode, but UNSUPPORTED lang mode is not present in shadow's config

thheller 2025-04-24T10:13:06.711739Z

this lang version stuff is confusing. IIRC :output-feature-set is the relevant part. lang version being a subset of that

thheller 2025-04-24T10:14:20.444579Z

but regardless. I just tested :language-out :unsupported after adding that. same error.

👍 1
thheller 2025-04-24T10:15:05.714589Z

:output-feature-set :es-unsupported
    :language-out :unsupported

thheller 2025-04-24T10:15:16.953179Z

shouldn't go any higher than this right?

Roman Liutikov 2025-04-24T10:16:06.616079Z

should be :language-in

Roman Liutikov 2025-04-24T10:16:20.397179Z

since the check is called isSupportedForInputLanguageMode

thheller 2025-04-24T10:17:06.297549Z

set that to :unsupported to, no change

👍 1
Roman Liutikov 2025-04-24T10:25:35.423129Z

it did work for me

Roman Liutikov 2025-04-24T10:26:16.264739Z

js input

export class Hello {
    static kuku() { return 11111; }
    #name = 'Roman'
    sayHi() { console.log(this.#name, Hello.kuku()); }
}
advanced compiled output
class b{g="Roman";sayHi(){console.log(this.g,11111)}}a={};a.h=b;(new a.h).sayHi();

Roman Liutikov 2025-04-24T10:26:33.998489Z

seems like the private field #name was rewritten into a public field

Roman Liutikov 2025-04-24T10:27:20.692689Z

I just patched shadow's config fn

(alter-var-root #'shadow.build.closure/lang-key->lang-mode
                (fn [f]
                  (fn [key] CompilerOptions$LanguageMode/UNSUPPORTED)))

thheller 2025-04-24T10:28:32.972729Z

ah nvm ..

thheller 2025-04-24T10:28:38.572279Z

I adjusted the setting in the npm code path, using non npm code to test 😛

👍 1
Roman Liutikov 2025-04-24T10:29:16.090539Z

ah, that's because npm stuff is processed with a diff set of settings?

thheller 2025-04-24T10:31:21.768239Z

I adjusted things here directly, so not from config

thheller 2025-04-24T10:31:37.702629Z

but that is the npm path, so pointless to do there 😛

thheller 2025-04-24T10:34:14.568749Z

well, regardless. it switching this to just public fields doesn't seem safe

👍 1
2025-04-25T12:47:42.421839Z

just FYI, I had to rm -r .shadow-cljs/ when updating, to fix these errors:

The required namespace "goog" is not available, it was required by "shadow/test/env.cljs"
...
The required namespace "goog" is not available, it was required by "shadow/cljs/devtools/client/console.cljs"

thheller 2025-04-25T13:17:23.157299Z

weird. all caches invalidate themselves on version change, so not sure why wiping the caches manually would do anything

2025-04-25T13:18:34.861969Z

idk, I was using different versions for clojurescript and google closure (like you recommend not to...) and so I also had to switch to just let shadow-cljs provide them maybe something leftover

cormacc 2025-04-25T14:17:11.026299Z

I'm getting a stack trace after updating my dep to 3.0.2 -- using openjdk 21.0.5. I have no need to upgrade, but just reporting back per the request in initial post -- see below. If you want more info I can raise an issue on github:

❯ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+1-nixos)
OpenJDK 64-Bit Server VM (build 21.0.5+1-nixos, mixed mode, sharing)

> shadow-cljs -A:dev watch example test portfolio --config-merge ./env-local.edn --config-merge ./ui-themes.edn
shadow-cljs - config: /home/cormacc/nmd/products/connect/portal/shadow-cljs.edn
shadow-cljs - starting via "clojure"
/*! 🌼 daisyUI 5.0.28 */
≈ tailwindcss v4.1.4

Done in 311ms
Downloading: thheller/shadow-cljs/3.0.2/shadow-cljs-3.0.2.pom from clojars
<.... more jar downloads ....>

Exception in thread "main" Syntax error macroexpanding at (shadow/build.clj:1:1).
	at clojure.lang.Compiler.load(Compiler.java:8177)
	at clojure.lang.RT.loadResourceScript(RT.java:401)
	at clojure.lang.RT.loadResourceScript(RT.java:392)
	at clojure.lang.RT.load(RT.java:479)
	at clojure.lang.RT.load(RT.java:444)
	at clojure.core$load$fn__6931.invoke(core.clj:6189)
	at clojure.core$load.invokeStatic(core.clj:6188)
	at clojure.core$load.doInvoke(core.clj:6172)
	at clojure.lang.RestFn.invoke(RestFn.java:411)
	at clojure.core$load_one.invokeStatic(core.clj:5961)
	at clojure.core$load_one.invoke(core.clj:5956)
	at clojure.core$load_lib$fn__6873.invoke(core.clj:6003)
	at clojure.core$load_lib.invokeStatic(core.clj:6002)
	at clojure.core$load_lib.doInvoke(core.clj:5981)
	at clojure.lang.RestFn.applyTo(RestFn.java:145)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6044)
	at clojure.core$load_libs.doInvoke(core.clj:6028)
	at clojure.lang.RestFn.applyTo(RestFn.java:140)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6066)
	at clojure.core$require.doInvoke(core.clj:6066)
	at clojure.lang.RestFn.invoke(RestFn.java:3662)
	at shadow.cljs.devtools.api$eval142$loading__6812__auto____143.invoke(api.clj:1)
	at shadow.cljs.devtools.api$eval142.invokeStatic(api.clj:1)
	at shadow.cljs.devtools.api$eval142.invoke(api.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7700)
	at clojure.lang.Compiler.eval(Compiler.java:7689)
	at clojure.lang.Compiler.load(Compiler.java:8165)
	at clojure.lang.RT.loadResourceScript(RT.java:401)
	at clojure.lang.RT.loadResourceScript(RT.java:392)
	at clojure.lang.RT.load(RT.java:479)
	at clojure.lang.RT.load(RT.java:444)
	at clojure.core$load$fn__6931.invoke(core.clj:6189)
	at clojure.core$load.invokeStatic(core.clj:6188)
	at clojure.core$load.doInvoke(core.clj:6172)
	at clojure.lang.RestFn.invoke(RestFn.java:411)
	at clojure.core$load_one.invokeStatic(core.clj:5961)
	at clojure.core$load_one.invoke(core.clj:5956)
	at clojure.core$load_lib$fn__6873.invoke(core.clj:6003)
	at clojure.core$load_lib.invokeStatic(core.clj:6002)
	at clojure.core$load_lib.doInvoke(core.clj:5981)
	at clojure.lang.RestFn.applyTo(RestFn.java:145)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6044)
	at clojure.core$load_libs.doInvoke(core.clj:6028)
	at clojure.lang.RestFn.applyTo(RestFn.java:140)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6066)
	at clojure.core$require.doInvoke(core.clj:6066)
	at clojure.lang.RestFn.invoke(RestFn.java:424)
	at user$eval136$loading__6812__auto____137.invoke(user.clj:2)
	at user$eval136.invokeStatic(user.clj:2)
	at user$eval136.invoke(user.clj:2)
	at clojure.lang.Compiler.eval(Compiler.java:7700)
	at clojure.lang.Compiler.eval(Compiler.java:7689)
	at clojure.lang.Compiler.load(Compiler.java:8165)
	at clojure.lang.RT.loadResourceScript(RT.java:401)
	at clojure.lang.RT.loadResourceScript(RT.java:388)
	at clojure.lang.RT.maybeLoadResourceScript(RT.java:384)
	at clojure.lang.RT.doInit(RT.java:506)
	at clojure.lang.RT.init(RT.java:487)
	at clojure.main.main(main.java:38)
Caused by: java.lang.ExceptionInInitializerError
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:534)
	at java.base/java.lang.Class.forName(Class.java:513)
	at clojure.lang.RT.classForName(RT.java:2229)
	at clojure.lang.RT.classForName(RT.java:2238)
	at clojure.lang.RT.loadClassForName(RT.java:2257)
	at clojure.lang.RT.load(RT.java:469)
	at clojure.lang.RT.load(RT.java:444)
	at clojure.core$load$fn__6931.invoke(core.clj:6189)
	at clojure.core$load.invokeStatic(core.clj:6188)
	at clojure.core$load.doInvoke(core.clj:6172)
	at clojure.lang.RestFn.invoke(RestFn.java:411)
	at clojure.core$load_one.invokeStatic(core.clj:5961)
	at clojure.core$load_one.invoke(core.clj:5956)
	at clojure.core$load_lib$fn__6873.invoke(core.clj:6003)
	at clojure.core$load_lib.invokeStatic(core.clj:6002)
	at clojure.core$load_lib.doInvoke(core.clj:5981)
	at clojure.lang.RestFn.applyTo(RestFn.java:145)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6044)
	at clojure.core$load_libs.doInvoke(core.clj:6028)
	at clojure.lang.RestFn.applyTo(RestFn.java:140)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6066)
	at clojure.core$require.doInvoke(core.clj:6066)
	at clojure.lang.RestFn.invoke(RestFn.java:424)
	at cljs.env$loading__6706__auto____754.invoke(env.cljc:9)
	at cljs.env__init.load(Unknown Source)
	at cljs.env__init.<clinit>(Unknown Source)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:534)
	at java.base/java.lang.Class.forName(Class.java:513)
	at clojure.lang.RT.classForName(RT.java:2229)
	at clojure.lang.RT.classForName(RT.java:2238)
	at clojure.lang.RT.loadClassForName(RT.java:2257)
	at clojure.lang.RT.load(RT.java:469)
	at clojure.lang.RT.load(RT.java:444)
	at clojure.core$load$fn__6931.invoke(core.clj:6189)
	at clojure.core$load.invokeStatic(core.clj:6188)
	at clojure.core$load.doInvoke(core.clj:6172)
	at clojure.lang.RestFn.invoke(RestFn.java:411)
	at clojure.core$load_one.invokeStatic(core.clj:5961)
	at clojure.core$load_one.invoke(core.clj:5956)
	at clojure.core$load_lib$fn__6873.invoke(core.clj:6003)
	at clojure.core$load_lib.invokeStatic(core.clj:6002)
	at clojure.core$load_lib.doInvoke(core.clj:5981)
	at clojure.lang.RestFn.applyTo(RestFn.java:145)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6044)
	at clojure.core$load_libs.doInvoke(core.clj:6028)
	at clojure.lang.RestFn.applyTo(RestFn.java:140)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6066)
	at clojure.core$require.doInvoke(core.clj:6066)
	at clojure.lang.RestFn.invoke(RestFn.java:1526)
	at cljs.analyzer$loading__6706__auto____645.invoke(analyzer.cljc:9)
	at cljs.analyzer__init.load(Unknown Source)
	at cljs.analyzer__init.<clinit>(Unknown Source)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:534)
	at java.base/java.lang.Class.forName(Class.java:513)
	at clojure.lang.RT.classForName(RT.java:2229)
	at clojure.lang.RT.classForName(RT.java:2238)
	at clojure.lang.RT.loadClassForName(RT.java:2257)
	at clojure.lang.RT.load(RT.java:469)
	at clojure.lang.RT.load(RT.java:444)
	at clojure.core$load$fn__6931.invoke(core.clj:6189)
	at clojure.core$load.invokeStatic(core.clj:6188)
	at clojure.core$load.doInvoke(core.clj:6172)
	at clojure.lang.RestFn.invoke(RestFn.java:411)
	at clojure.core$load_one.invokeStatic(core.clj:5961)
	at clojure.core$load_one.invoke(core.clj:5956)
	at clojure.core$load_lib$fn__6873.invoke(core.clj:6003)
	at clojure.core$load_lib.invokeStatic(core.clj:6002)
	at clojure.core$load_lib.doInvoke(core.clj:5981)
	at clojure.lang.RestFn.applyTo(RestFn.java:145)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6044)
	at clojure.core$load_libs.doInvoke(core.clj:6028)
	at clojure.lang.RestFn.applyTo(RestFn.java:140)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6066)
	at clojure.core$require.doInvoke(core.clj:6066)
	at clojure.lang.RestFn.invoke(RestFn.java:3662)
	at shadow.build$eval9551$loading__6812__auto____9552.invoke(build.clj:1)
	at shadow.build$eval9551.invokeStatic(build.clj:1)
	at shadow.build$eval9551.invoke(build.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7700)
	at clojure.lang.Compiler.eval(Compiler.java:7689)
	at clojure.lang.Compiler.load(Compiler.java:8165)
	... 62 more
Caused by: java.lang.ClassNotFoundException: com.google.javascript.jscomp.JsAst
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:534)
	at java.base/java.lang.Class.forName(Class.java:513)
	at clojure.lang.RT.classForName(RT.java:2229)
	at clojure.lang.RT.classForNameNonLoading(RT.java:2242)
	at cljs.externs$loading__6706__auto____1358.invoke(externs.clj:9)
	at cljs.externs__init.load(Unknown Source)
	at cljs.externs__init.<clinit>(Unknown Source)
	... 152 more

thheller 2025-04-25T14:21:19.132809Z

thats a dependency conflict. still using the older closure compiler version.

cormacc 2025-04-25T14:21:43.848909Z

D'oh 🙂 Thank you

thheller 2025-04-25T14:22:18.111849Z

its fun right? closure people removing/renaming classes for no reason 😛

🙃 2
Roman Liutikov 2025-04-25T15:44:18.287789Z

@thheller re private fields, I was previously running tests against JS required into cljs from a local file which puts it through advanced optimisations, and that's were private fields were rewritten into public fields Just tried 3.0.2 with NPM library that uses private fields, and release build outputs NPM libs with private syntax unchanged

Roman Liutikov 2025-04-25T15:46:43.411899Z

presumably because npm stuff is bundled with :simple optimisations in shadow

thheller 2025-04-25T18:18:44.816139Z

oh nice. I'll check it out over the weekend

dazld 2025-07-08T08:14:49.557669Z

it wasn’t super clear from the thread - but given I’m ok with translating a private field to public (it’s just a UI library, nothing particularly private) - should shadow-cljs 3.1.7 and closure compiler 20250407 be able to consume js that uses private fields? I’ve set the compiler options as so:

:compiler-options
 {:language-in        :unsupported
  :output-feature-set :es-unsupported}
but still seeing the following errors:
--- node_modules/@radix-ui/react-collection/dist/index.js:223
This language feature is not currently supported by the compiler: Private class properties.

Roman Liutikov 2025-07-08T08:32:18.401519Z

@dazld you need :js-options, that's for npm stuff

:js-options {:output-feature-set :es-unsupported
             :language-in :unsupported}

👍 1
👍🏿 1
dazld 2025-07-08T08:33:21.836299Z

[:frontend] Compiling ...
[:frontend] Build completed. (260 files, 162 compiled, 0 warnings, 8.94s)
thanks a ton @roman01la 🙌

thheller 2025-07-08T08:36:47.943699Z

I wonder if I should just make this the default? "downgrading" hasn't seemed necessary for a while since browsers are pretty fast at supporting new stuff

dazld 2025-07-08T08:37:23.244609Z

I think your instinct earlier about not making private fields magically public was right

thheller 2025-07-08T08:37:23.402209Z

the default of es2020 is rather conservative

dazld 2025-07-08T08:37:41.821369Z

in this case it’s ok (just some API aesthetics) but in other stituations, that would be a problem (eg api keys)

thheller 2025-07-08T08:38:04.785549Z

what do api keys have to do with this?

dazld 2025-07-08T08:38:22.368309Z

ie, if there was a private field with an api key, which suddenly became public

thheller 2025-07-08T08:38:52.185279Z

that isn't relevant at all. private is just a "human" helper to structure code. it isn't actually "hiding" anything

dazld 2025-07-08T08:39:20.448939Z

ah, but in plain JS - it is actually private, right?

thheller 2025-07-08T08:39:36.620839Z

depends on what you mean by actually private

thheller 2025-07-08T08:39:54.771149Z

as in able to protect secrets then no, absolutely not. its just plain text in the source code, anyone can see it

thheller 2025-07-08T08:41:09.737429Z

class { #key = "literal" } I mean anyone can read this, there is nothing private about it

thheller 2025-07-08T08:41:55.766929Z

imho its a nonsense computer language thing. telling users of certain classes "you are not supposed to use this, its internal". I mean the convention is of course good. modifying the language to get it is the nonsense part

thheller 2025-07-08T08:45:23.451889Z

at runtime I'm not actually sure if you can get it. seems beside the point if anyone can just look at the source 😛

dazld 2025-07-08T08:48:33.195819Z

private fields are only accessible to the class - so it’s not programatically accessible outside. In theory, we all have strict CSP disabling arbitrary scripts, right? so it’s not a problem there.. but in reality.. not everyone is doing that.

thheller 2025-07-08T08:49:32.551149Z

yeah dunno. I guess if you have a private field and pass an instance of that class to some third party thing then it does protect it sort of

thheller 2025-07-08T08:50:00.138209Z

but who'd run code that tries to steal secrets in the first place

thheller 2025-07-08T08:50:19.071229Z

otherwise I can just open the sources panel in the devtools and that lets me find any secret I want

dazld 2025-07-08T08:50:42.651429Z

automated script injection attacks, looking for globals, recurse down..

thheller 2025-07-08T08:51:04.185839Z

again ... why would anything do that at runtime? just download the damn .js file 😛

dazld 2025-07-08T08:51:13.407999Z

heh

dazld 2025-07-08T08:51:47.142269Z

JWTs are a fun example - many people use third party auth, which does stuff in an iframe.

dazld 2025-07-08T08:52:15.984699Z

your bearer token may live on a private field

thheller 2025-07-08T08:52:43.885959Z

again .. if the thing tries to steal your secrets you probably shouldn't rely on private fields, you should not use it 😛

thheller 2025-07-08T08:53:18.104979Z

JWTs are also commonly cookies, so trivial to read for anyone

dazld 2025-07-08T08:53:40.582749Z

not always, and cookies aren’t always accessible from JS

dazld 2025-07-08T08:54:11.845899Z

but think we’re talking past each other - I’m thinking very specifically about keeping code “safe by default”

dazld 2025-07-08T08:54:36.047129Z

ie, I’m not intentionally hammering on private fields as the app dev, but making sure that an accidental backdoor isn’t exposed

dazld 2025-07-08T08:54:54.496219Z

for bad actors

thheller 2025-07-08T08:55:10.089459Z

I think there is no "safe by default" when talking web stuff. in any way ever. all the stupid stuff we invented still doesn't prevent shit if you ask me

thheller 2025-07-08T08:55:34.246099Z

if you are doing stuff that needs to be safe then there should be no third party scripts ever

dazld 2025-07-08T08:55:41.166399Z

there are no absolutes in security

thheller 2025-07-08T08:55:54.365869Z

heck it probably shouldn't even enter the client/browser

dazld 2025-07-08T08:56:04.825749Z

there are only a huge range of stuff you do to try and keep as much as possible away

dazld 2025-07-08T08:56:14.337499Z

if someone is determined to get in, they will

dazld 2025-07-08T08:57:38.677869Z

I don’t leave my keys in my car when I go to the shops, but if someone really wants to steal it, they will.. stuff like this

thheller 2025-07-08T08:58:25.809199Z

I think most of the stuff gives a false sense of security and since its all so complicated may make you think its actually safe when it never is

thheller 2025-07-08T08:58:52.089859Z

so best to assume nothing is safe and act accordingly and forget about the other stuff

dazld 2025-07-08T08:58:52.755519Z

that’s also very true. TS private fields for example, iirc, were also public. not sure if they fixed that.

thheller 2025-07-08T08:59:21.550949Z

but oh well third party scripts are so common that this is all a fantasy to begin with 😛

dazld 2025-07-08T08:59:44.542149Z

I wonder if there were CVEs that were raised on this.. let me take a look edit there’s a couple about prototype pollution, but seems this is mostly app level security problems, so wouldn’t be a CVE..

dazld 2025-07-08T09:11:22.559329Z

however - here’s a better example. if you iterate over the object, then the previously private field would then pop up in the iteration.

> class Foo { bla="abc"; #key="sekrit"; bar() {return this.#key}}
> f = new Foo
< Foo {#key: "sekrit", bla: "abc"}
> for (let v in f) { console.log(v, f[v]) }
[Log] bla – "abc"
< undefined
see how key doesn’t appear. I wouldn’t write code like this that blindly literates, but some people do.

Dustin Getz (Hyperfiddle) 2025-04-24T15:31:35.102489Z

When using :esm is there a way to pass through a css import? e.g., import "./ts_page.css"; or in our case (:require ["dustingetz/root.css"] - which compiles to import "./shadow.esm.esm_import$dustingetz$root_css.js"; (unsurprisingly)

thheller 2025-04-24T15:34:07.003459Z

incorrect. it compiles down to import * from "dustingetz/root.css";

🤔 1
thheller 2025-04-24T15:34:29.314659Z

the shim is just a JS file doing the actual import

Dustin Getz (Hyperfiddle) 2025-04-24T15:35:29.831589Z

ooo

Dustin Getz (Hyperfiddle) 2025-04-24T15:36:57.770689Z

good work

Dustin Getz (Hyperfiddle) 2025-04-24T15:37:16.816349Z

i had no idea

thheller 2025-04-24T15:38:13.218299Z

for :import any string require is just passed through. shadow doesn't attempt to locate or parse anything.

👍 1