package ppx_platform
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=be1ae7c2cacdef9f21b0822b728d854bc77fa6181276e1b47db06b49a80531e5
sha512=bd428092b3ec062ff294a02619ed4441d451d1965021f3f7074244b2448687f03fa03c0eb320b112a8660cac8bee552bfb711a0bb458deacccccfd3979fda860
Description
A ppx rewriter that keeps one OCaml codebase for native and Melange builds: let%melange_only, [@melange_only], [@native_only] and match%platform pick the code that each target compiles.
Added to opam-repository:
README
ppx_platform
A ppx rewriter that keeps one OCaml codebase for native and Melange builds. It picks the code that each target compiles and replaces Melange-only code with a runtime failure on native.
Extracted from server-reason-react.
Install
opam install ppx_platformSetup
Native library or executable:
(library
(name my_lib)
(preprocess
(pps ppx_platform)))Melange library: pass the -js flag to the ppx.
(library
(name my_lib_js)
(modes melange)
(preprocess
(pps ppx_platform -- -js)))The ppx adds ppx_platform.runtime as a dependency of both builds.
Syntax
match%platform
match%platform picks one of two expressions at compile time. The cases are Native and Melange. In Reason, write switch%platform.
let api path =
match%platform () with
| Native -> Sys.getenv "API_URL" ^ path
| Melange -> path
let now () =
match%platform () with
| Native -> Unix.gettimeofday () *. 1000.
| Melange -> Js.Date.now ()The ppx keeps only the branch for the current target, so each build compiles different code:
(* native *)
let api path = Sys.getenv "API_URL" ^ path
let now () = Unix.gettimeofday () *. 1000.(* Melange *)
let api path = path
let now () = Js.Date.now ()Both branches must have the same type, because the type checker sees only the one that remains for each target.
This lets one codebase:
- Behave differently on each target where it must. In
api, the server needs an absolute URL, and the browser can use a relative one. - Use modules that exist on one target only. In
now,Unixdoes not exist in Melange andJs.Datedoes not exist on native. Each build compiles only its own branch, so neither needs the other's module.
[@melange_only] / [@native_only]
For modules and other top-level items. The item stays on one target only:
include [@melange_only] struct
type t = Js.Json.t
end
include [@native_only] struct
type t = Yojson.Basic.t
endTwo items with the same name are fine. Each build keeps only one of them, so the module exists on both targets:
module X = struct
type t = Js.Json.t
end [@@melange_only]
module X = struct
type t = Yojson.Basic.t
end [@@native_only]melange_only
For functions and let bindings that only get compiled by Melange and are meant for the browser. On native, the body becomes an expression that raises Ppx_platform.Melange_only, and the binding gets a melange_only alert. So native code that calls it outside another let%melange_only gets a compiler alert (an error with -alert @melange_only):
let%melange_only focus_input element = Webapi.Dom.HtmlElement.focus elementIf native code calls it anyway, the exception is not caught and the program crashes. To give a fallback value instead, catch the exception:
let%melange_only window_width () = Webapi.Dom.(window |> Window.innerWidth)
let width () =
try window_width () with Ppx_platform.Melange_only _ -> 0With OCAMLRUNPARAM=b (or Printexc.record_backtrace true), the runtime prints a hint and the call stack to stderr before it raises, so a caught call still leaves a trace.
[%melange_only fun ...] does the same for an expression:
let on_click = [%melange_only fun event -> Js.log event][@melange_only] removes an item or a record field on native:
let window_size = (fun () -> (0, 0)) [@@melange_only]Licence
MIT