Although I like the simplicity of the result (I was never a fan of bs-webapi’s ergonomics myself), I feel like this refactor has some shortcomings if it is to demonstrate how to approach problems such as interaction with DOM APIs. Hoping that it is useful feedback for the team, I’m going to take this refactor at face value and give my two cents.
Take this commit for example: https://github.com/chenglou/domgraphs/commit/0727144626b7a6013d297b6aab74d056cb07f889#diff-6b86dad133536de8149349ea05fd8585R51
I think this only works when you’re refactoring working software to make the code simpler. It benefits from:
- Having some code that was correct and performed the task well in the first place
- Being able to look at and compare the generated JS output of some existing code
- Having a good command of DOM API’s already before attempting to write this code from scratch
It is indeed easier to read and follow in places (due to bs-webapi’s API design and difficulty of making generic DOM bindings), but I’m not sure if it is easier to write, let alone easier to write correctly. Concerns such as false sense of safety or inaccuracies regarding nullability are important things to address of course. But knowing how often I get my hand-written bindings wrong, how hard they are to maintain when the underlying JS library keeps changing, and how many head-scratching bugs they caused me in the past, I don’t want to be in the mindset of “always interacting with JS” like when writing code such as context["moveTo"](~x=0.0, ~y=centerY)
. Thankfully DOM API is more stable than rest of the JS ecosystem and very well documented, but I would also be afraid to have this sort of arbitrary JS access all around a big codebase only to have the underlying library change its API making it a really error-prone upgrade process (instead of updating the bindings and letting the type system help with the rest of the work).
When building something such as domgraphs I heavily rely on tooling. With JavaScript and TypeScript you get a lot of help from editors such as VS Code. It allows you to discover APIs and get your code right (although it is sometimes unsound). libraries such as bs-webapi or Fable.Browser also provide a similar boost when interacting APIs as complex as DOM. Of course it is not a replacement for having a good working knowledge of the underlying API, but I would always want more assistance when writing code than having to have MDN or some JS libraries README always open on the side.
Another way of looking at it; Old browsers or different execution environments not having some features doesn’t invalidate the entirety of https://github.com/microsoft/TypeScript/blob/master/lib/lib.dom.d.ts, and I’m glad there’s a peer reviewed collection of types for DOM used by many people and written by people who have much more experience than I do.
For example, with let width = Belt.Float.fromInt(element["width"])
I definitely see myself omitting Belt.Float.fromInt
if I don’t know the return type by heart. Or whether it can be null or not. In JS there’s already a culture of overly defensive code such as typeof element.width = "number" ? ... : ...
. People don’t start out writing code this way, but after one too many easily avoidable errors on Sentry, they adopt this pattern to interact with any and every function someone else wrote.
Re-creating all of this ecosystem of bindings with a small community and gargantuan API’s such as DOM that don’t always align with ReScript’s type system is an uphill battle indeed. Maybe ReScript tooling (not necessarily the compiler, but definitely the editor) can help out with this if it can seamlessly utilize TypeScript types to display hints when interacting with JS without bindings. Some editor features that would help with the following line
context["fillRect"](~x=0.0, ~y=0.0, ~w=width, ~h=height)
:
-
I would like to have
fillRect
suggested as I type
Personally, it is always a pain if I need to stop and google this sort of stuff. I might not know what this method is called, or how to spell it, or whether it exists at all. Autocomplete is a massive productivity boost. -
I would like to see these labelled arguments suggested as I pass arguments.
I really have no idea what order I need to pass them. Using labelled arguments help as documentation. But I first need to figure out what each of these positional arguments in one specific overload of the original JS function stand for and the accepted types.I also don’t know if I’m passing an unused argument to this function, or whether I need to (or can) pass more arguments.
Slightly off topic; using labelled arguments in this case makes me think that I can pass them in a different order or I can insert another argument in the future at an arbitrary position, which is not the case.
-
I would like to be able to jump to definition and read signatures, docs. The destination could be lib.dom.d.ts#L3397
Of course this doesn’t help with poorly documented JS libraries much. There’s too many popular libraries with a single README.md acting as documentation containing things like “drawUnicorn: takes a canvas and draws a unicorn according to the options passed as the second argument” and having no other information. At least with bindings someone figures this stuff out once which helps everyone else. Unfortunately I also often don’t have the luxury of being able to say “I won’t use this poorly written library” or “All of the code we interact with in our project is bespoke apart from platform APIs”. I can’t count the number of times I was able to find someones hand-written Flow or TypeScript documentation for a JS library on Github without which I was unable to figure out how to use the library correctly in JS. I don’t think “with ReScript you don’t need bindings” tagline really clicks with me.