package ppx_platform

  1. Overview
  2. Docs
Platform-specific code for native and Melange builds

Install

dune-project
 Dependency

Authors

Maintainers

Sources

ppx_platform-0.3.0.tbz
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_platform

Setup

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:

  1. Behave differently on each target where it must. In api, the server needs an absolute URL, and the browser can use a relative one.
  2. Use modules that exist on one target only. In now, Unix does not exist in Melange and Js.Date does 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
end

Two 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 element

If 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 _ -> 0

With 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

Dependencies (4)

  1. melange >= "3.0.0"
  2. ppxlib >= "0.36.0"
  3. ocaml >= "4.14.1"
  4. dune >= "3.9"

Dev Dependencies (3)

  1. odoc with-doc
  2. ocamlformat = "0.28.1" & with-test
  3. reason with-test

Used by

None

Conflicts

None