This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-04-11
Channels
- # announcements (2)
- # babashka (31)
- # beginners (31)
- # calva (30)
- # cider (23)
- # clerk (1)
- # clojure (46)
- # clojure-austin (5)
- # clojure-brasil (1)
- # clojure-europe (47)
- # clojure-nl (1)
- # clojure-norway (72)
- # clojure-uk (2)
- # clojurescript (39)
- # conjure (1)
- # cursive (16)
- # data-science (1)
- # datomic (35)
- # dev-tooling (4)
- # events (5)
- # introduce-yourself (2)
- # jobs-discuss (5)
- # missionary (3)
- # polylith (11)
- # releases (4)
- # scittle (4)
- # shadow-cljs (18)
- # spacemacs (16)
- # specter (2)
- # squint (27)
- # xtdb (6)
I would like to make a ClojureScript library consommable by a TypeScript client. Any advice and tips?
There's a bit of info here: https://shadow-cljs.github.io/docs/UsersGuide.html#target-npm-module
I have an additional concern: My Cljs lib expects keyword values inside some field maps e.g.
{:name "Joe"
:role :admin}
How this map could be passed in JS/TS?I am talking about the field value :admin
.
You can create and export vars that JS can use, so it might end up something like this:
the.app.namespace.create_the_map("Joe", the.app.namespace.admin_role)
Interesting. It can simply be:
{name: "Joe",
role: the.app.namespace.admin_role}
Or even:
{name: "Joe",
role: cljs.core.keyword("admin")}
What do you guys think?
cljs.core.keyword
is not exported, it will not be available after advanced optimizations.
Also, that's not a CLJS map, so it'd have to be converted to one by something.
My plan is to use js->clj
in the exported function.
Right. But I am looking for a generic way to deal with it
Requiring your users to call keyword
on their data is shifting the responsibility from your code to theirs. It does make your code more generic, but makes their code less generic.
I realise it now. What can I do then?
> What can I do then? I would make the API convenient to users and do all the necessary conversions on the CLJS side. You can do it manually, you can do it via some spec/schema library's coercion.
In something like typescript, people are going to expect static types for things like roles. Be it an enum or an object exported as const
, I'd suggest writing a typescript shim for your api that does the conversions and calls into the cljs code. I think that's the safest way of doing these kind of conversions.
IMO a shim that does additional work and calls into CLJS is more work than just the result of CLJS compilation + TS type definitions.
Thank you guys!
Another question: how to define the types for TypeScript for the functions my CLJS library exposes?
The same exact way you'd do it for a JS library that doesn't define any types in specially formatted comments - by manually writing a .d.ts
file with all the relevant types in it.
From the example in the docs thheller has linked above - you have a /js/demo.js
file that exports { hello }
.
Then you'd add /js/demo.d.ts
that has export function hello (): void;
.
In the docs, /js/demo.js
is a generated file. Can shadow-cljs bundle ts source files?
It can be done via a build hook. You'd have the TS file in your sources and the hook would copy it to the output dir.
@U05224H0W Is that the only way?
shadow-cljs cannot bundle ts files no, but you can run tsc
manually and have that produce esm or commonjs files. shadow-cljs can bundle those.
Do you think type information is preserved when producing esm/commonjs files?
I want a TypeScript consumer to see the types that I declare.
Just tested. TL;DR: you should just stick to creating .d.ts
files manually. It's better in almost every way.
Docstrings are turned into comments, but those are removed with any level of optimization, including :whitespace
.
So you have to use :none
, but that doesn't work for release builds. So you have to use a development build and then somehow stitch together all the separate build artifacts.
def
s in CLJS become members of a global object in JS, so all you can document is the type of an object member. If that member is a function, you have to document it as such using JSDoc in its docstring - "this is a function that returns this and accepts this", which can be cumbersome (although I might be wrong here - maybe TS understands when a member is documented as a function). It won't work at all for functions with multiple arities because those become separate members with some generated entry point.
Oh! And docstrings are only at the impl level. The interface file that has the exports doesn't have the comments at all. No clue whether TS will follow any references during compilation. I'd assume that it wouldn't.
I'm slightly confused about arrays. Why is this not an array? (array? (type (js->clj #js["a"]))) => false
derp, never mind. it's a vector.
it was a long day 🙂
❯ clj -A:cljs -M -m cljs.main -re node -r
ClojureScript 1.10.773
cljs.user=> (type (js->clj #js["a"]))
cljs.core/PersistentVector
cljs.user=> (type *1)
#object[Function]
cljs.user=> (array? (type (js->clj #js["a"])))
false
cljs.user=> (array? #js["a"])
true
cljs.user=> (array? (js->clj #js["a"]))
false
cljs.user=>