When writing client/server applications, it's convenient to share selected code between the two. You may be able to achieve this by extracting the common code in a shared library. But for exploratory programming, it can be useful to define the client and server interleaved in the same files.
This ppx rewriter provides %client and %server ppx annotations:
let%client c = "client only"
let%server s = "server only"
let shared_by_default x = xFrom which you can extract two distinct versions:
(pps ppx_shared.client) |
(pps ppx_shared.server) |
|---|---|
let c = "client only"
let shared_by_default x = x |
let s = "server only"
let shared_by_default x = x |
Since the code is duplicated, this allows the client and server to be compiled with different libraries or even compilers (for example js_of_ocaml).
See example.ml for annotating expressions, modules, types, or even blocks of code:
let%client x = ...,val%client x : ...,module%client M = ...,type%client t = ...for definitions available only on the client[%%client let x = 1 let y = 2]for multiple definitionsinstr ;%client restif the instructioninstrmust only evaluate on the client(expr [@client other])to computeexpron the server andotheron the client(typ [@client: other])to specify the typetypon the server andotheron the clientopen M [@client Alt]to open the moduleMon the server andAlton the client
Finally, you can replace %client by %server to exchange the semantic!
Integration with dune requires a bit of care, as it doesn't like to reuse the same module in two different contexts. A simple solution is to create directories for the client and/or server, with a dune file responsible for importing the modules from a shared src/ directory:
(executable
(name example)
(preprocess (pps ppx_shared.client)))
; replicate the sources!
(copy_files ../src/*.ml)
(copy_files ../src/*.mli)
; or one by one with:
(rule (copy ../src/origin.ml dest.ml))This ppx is inspired by the Eliom syntax extension for Ocsigen, with a few differences:
-
Code is assumed to be shared by the client and server by default: there is no explicit
%sharedannotations -
No runtime mechanism is provided for exchanging values between the client and the server, as this depends on your protocol. You can explicitly annotate expressions to smooth this communication, assuming some suitable definition of
sendandreceive:let msg = send "hello from the server" [@client receive ()] in ...
This ppx is also similar to ppx_inline_test as you could use the %client annotation to specify unit tests (that will not be present in the final "server" version of the code). See ppx_shared_client.ml for how you can use ppx_shared as a library to create your own ppx with custom aliases:
let () =
Ppx_shared.keep "release" ; (* to keep all code annotated by [%release] *)
Ppx_shared.remove "debug" (* to remove all code annotated by [%debug] *)let%debug check name cond = (* a custom assert only available in debug mode *)
if not cond then failwith name
let%debug () = check "root" (sqrt 4.0 = 2.0)