dune config for SSR with native code on the server #694
Replies: 5 comments 5 replies
-
I have done exact same steps under server-reason-react, exact same ppx for removing server code (browser_only) as well! but with a few details different:
Some things that we might need to consider
The decent result for now, has been copy_files into all levels of dependencies, so any library that needs to be universal will eventually have 2 separate versions. This is done since even when using browser_only the code needs to be compiled anyway, so stubs with the same interface are helpful. That can be a problem. For example, most of the code I'm migrating over use Js stuff which is currently implemented but some parts might be missing (currently crashing at runtime), for the opposite Belt works great. |
Beta Was this translation helpful? Give feedback.
-
I ran into this issue while trying vlibs when migrating Ahrefs monorepo to Melange. One solution is to make the type abstract, but this comes with its own set of downsides, and makes some things just impossible.
If the interfaces are exactly the same, it should be possible to create a virtual library out of it, no? Although worth trying, I believe that virtual libs would not be much cleaner than the current approach using two separate dependency trees: with virtual libraries, one would need to define three libraries: the virtual one, plus the two implementations, then choose the implementations from Turning the discussion around: what would be the "ideal" solution for this kind of setup? I imagine having |
Beta Was this translation helpful? Give feedback.
-
I was just about to ask a similar question, but this thread describes my current challenge with migrating to dune integrated melange more or less exactly 😄 Previously we had a shared 'Shared_util' module containing things like: (* src-shared-util/shared_util.ml, as part of a lib called Shared_util_lib *)
module String_map = struct
include CCMap.Make(CCString)
end
(* src-shared-util-bs/shared_util.ml *)
module String_map = struct
include Belt.Set.String
(* e.g. matching the `add` sig of CCMap where
the args are flipped so the two interfaces are the same *)
let add k v t = Belt.Map.String.set t k v
...
end This allowed us to define shared code modules using the We'd hooked this up with this in the
and adding only So it feels like we'd essentially created the two 'namespaces' using the two build systems as a bit of a hack. It sounds like the proposed One issue we had with this previous setup was editor tooling - each file had to be assigned to either the frontend realm or the backend realm for things like jumping to definition of these dual-implemention libs in a monorepo setup - I guess we would default shared code to jump to definition of functions in the 'native' impl? Not really a huge issue in practice but thought I would mention it. |
Beta Was this translation helpful? Give feedback.
-
@andreypopp have you tried this workaround? ocaml/dune#5997 (comment) |
Beta Was this translation helpful? Give feedback.
-
I have been thinking lately about ways to fix the issues @andreypopp brought up in this discussion, and @davesnx has been also experiencing. My goal was to fix them without the need to add new stanzas to Dune, which imo has some downsides as it makes Dune implementation more complex, and has some costs in documentation and maintenance that we'd have to assume. After some experiments, I realized that maybe leveraging Dune contexts through the This would allow using same library names across both platforms (wrapped), as well as avoid having to use The main downside is that users would have to add While testing the dual context idea, I ran into some limitations with Dune, where it currently forbids having two libs with the same name defined in different contexts. But with the help of @rgrinberg (thx!) we managed to figure things out in ocaml/dune#9839 (still unmerged). I have created a small demo using the version of Dune from that PR in https://github.com/jchavarri/melange-universal-demo. The src/world/dune file shows how it's possible to define the same library If we want to move forward with this approach, there are some remaining issues to be fixed, besides making sure that that Dune PR is approved and merged:
Another point that I haven't thought much about is how publication of packages that use this kind of approach would work. Please feel free to comment, ask, suggest or explore as you will. As this is all exploratory work, any kind of feedback is super appreciated. |
Beta Was this translation helpful? Give feedback.
-
So I'm working on a way to SSR React components on server but compiling them to native code instead of running inside Node.js (yes, this means need to reimplement some parts of React in native OCaml, partly done).
The code looks like this:
So this code compiles separately for browser (using melange) and separately for server (using ocamlopt). There are few interesting things here to note:
%browser_only
form allows to mark parts of the code (only functions, evals and lazy values) to be executed in browser only (no support from the type system here, at least yet)Such code might depend on libs which have different implementations for browser and for native. In this case I have
Remote
andApi
modules doing different things.Now to the "problems" I have with the build system config (quoting b/c I can workaround them but the workarounds are very verbose).
For the code above I have roughly (omiiting some details first) this scheme:
As you can see I'm using
(subdir)
stanza to:name
,modes
,preprocess
andlibraries
..ml
sources from the parent dirNow this doesn't look so bad... but given that the lib names are different for native vs melange libs, when the dependency graph grows it becomes harder to specify dependency and make the code compile.
The reason is not only that I need to specify different libs as dependencies for browser vs native libs but also need to expose different libs under the same name so the same code compiles for both cases. So the final build config looks like this.
First original lib for browser but notice I've added
example_deps
lib as dependency and then opened its root module inflags
:Now
example_deps
bundles allexample
deps and exposes them under "normalized" names in its root module:Now the same is done for native:
So to recap, the problems I see here:
preprocess
configs to those libs (I think it's ok to be able to do that as native vs melange libs might require completely different ppx but in my case the same ppx can work in two modes and I'd prefer if dune's melange mode had a separate ppx rules so it can communicate to ppx that it runs for melange mode, thus I don't have to pass flags to ppx)_deps
libs to expose dependencies under proper names.I wonder if there's a better way to solve this?
Why not dune's virtual libs? I've tried them but they have a limitation that the implementations of virtual libs cannot expose more than the virtual libs’ .mli define.
That means I cannot set different type equalities in a proper way for — for example if I have a vlib
promise
, thenpromise-server
impl to havetype 'a promise = 'a Lwt.t
andpromise-browser
to havetype 'a promise = 'a Js.Promise.t
. I cannot do that. Even more I cannot have converters from one promise type to another as I cannot expose more than vlib defines (and vlib cannot define more support by both native and melange).Link to the current WIP -
https://github.com/andreypopp/react_of_ocaml/blob/data/example/browser/dune
Beta Was this translation helpful? Give feedback.
All reactions