Description
context
JSOO has long been the sole, defacto provider mapping OCaml input to JavaScript output. This project has existed for well over a decade, and has the primary function of taking users' ML and converting it to immediately invocable or effectful JavaScript. This continues to be a great accomplishment.
Over the passed few years, the ECMAScript spec has evolved and developed a proper module system.
This module system development is of interest, because its stabilization enables a greater opportunity for ML to participate in the JavaScript ecosystem.
proposal
Provide a first class compile target to ESM.
justification
- JS bundle peformance
- status-quo: the current compiler (bundler) costs >75kb of javascript, just for hello world (with --profile=release). ref project
- 75kb is a large baseline
- the full runtime is compiled, even if only a subset is used
- future: like
elm
's compiler, the moreocaml
features that user code consumes, the more of the runtime we can incrementally include in the output
- status-quo: the current compiler (bundler) costs >75kb of javascript, just for hello world (with --profile=release). ref project
- Improved portability
- ESM output enables OCaml modules to be participate in JavaScript norms. Such norms include participation with common javascript tooling (browsers (e.g.
<script src="..." module></script>
), bundlers, analyzers, dead-code analyzers, minifiers, pretty printers--the works!)
- ESM output enables OCaml modules to be participate in JavaScript norms. Such norms include participation with common javascript tooling (browsers (e.g.
hypothetical
The following hypothetical cases may be completely bogus. Consider these my temporary, envisioned target state, even if they are not necessarily achievable.
case - empty
Input:
(* main.ml *)
(* no content *)
Output:
// main.js
// no content
No source, no dist! 75k worth of savings, vs status quo :)
case - hello world
Input:
(* main.ml *)
let () = print_endline "Hello, world!"
Output (a):
// main.js
console.log("Hello, world!")
Output (b):
// main.js
import { print_endline } from "@jsoo/std";
print_endline("Hello, world!")
case - reduce
Input:
(* main.ml *)
let rec sum = function
| [] -> 0
| head::tail -> head + (sum tail)
Output:
// main.js
import { fn, match_case, match } from "@jsoo/fn";
import { pattern_length } from "@jsoo/lists";
export const sum = fn(
match_case(match(pattern_length(0, true)), () => 0),
match_case(match(pattern_length(1, false), [head, ...tail] => head + (sum tail)))
);
// ^or whatever the equivalent output would need to be,
// as this is just pseudo-code
Where the runtime is partitioned into ESM modules as well. E.g.:
// @jsoo/caml_runtime
export const caml_lists_iter_whatever(a,b,c) => { /* */ };
// ... all the caml_ stuff!
// @jsoo/lists
export const pattern_length = (len, exact) => x => exact
? x.length === len
: x.length >= len;
// @jsoo/fn
export const match = x => pattern => pattern(x);
// @jsoo/operators
export const caml_neg_float = x => (-x);
// ...
If may be the case that the emitted modules is more along the lines of:
// main.js
import { caml_apply, caml_match_case, caml_match, caml_pat } from "@jsoo/runtime";
// ^ psuedocode, clearly. not an expert in the caml_ bindings, or the
// feasibility of such a mapping :)
export const sum = caml_apply(
caml_match_case(caml_match(caml_pat(0, true)), () => 0),
caml_match_case(caml_match(caml_pat(1, false), [head, ...tail] => head + (sum tail)))
);
And the fully runtime
is implemented by a plain, super ES module.
What's nice about this, is that only the bare minimum import graph gets used, versus a full runtime!
omissions
Omitted from this discussion are
- how the FFI would play a role
- how local imports would link into module outputs
- how 3rd party ML code would participate
We could crack into all of these as interested!
user experience
- Want to use esbuild? Parcel? webpack?
With an ESModule target, browser-friendly ML code could be achievable anywhere ESM is used!npm install @jsoo/runtime
, then setup a ML loader OR precompile.ml
to modules! - reason/rescript toolchains compile ml-like files with similar output, albeit to commonjs. (esm -> commonjs 😄 , commonjs -> esm 😵💫 )
I didn't see such conversations on this topic in github, but may have missed it. Sorry if this is duplicated! If this is duped conversation, please feel free to eagerly close!