Published on

What's 2024 brought to Melange so far?


Last year we saw the first major release of Melange. Over the course of 2023, what started as disjoint sets of tooling has gradually evolved into a cohesive development experience within the OCaml Platform.

But we're not done yet. In the next few paragraphs, I'll tell you everything we've shipped so far in the first 3 months of 2024.

Releasing Melange 3

We released Melange 3.0 in February. This release mostly focused on addressing long-standing deprecations, crashes, error messages and making the Melange distribution leaner.

One highlight of the Melange 3 release is the support for more versions of OCaml. Melange 3 supports OCaml 4.14 and 5.1; the next major release will additionally feature support for OCaml 5.2.

reason-react and the Melange libraries in melange-community were also updated and released with support for this new Melange major version.

Melange 3 has been running in production at Ahrefs since its release. This is the largest Melange codebase that we are aware of (on the scale of tens of libraries with support for (modes melange) and melange.emit stanzas, across dozens of apps).

Emitting ES6

As the great majority of browsers supports ECMAScript 2015 (ES6), we decided to bump the version that Melange targets. In melange#1019 and melange#1059 we changed the emission of var to let (and const, where possible). let's lexical scope makes some closure allocations in for loops unnecessary, which we promptly removed in melange#1020. This change also results in a slight reduction of bundle size as Melange emits a bit less code.

Starting to emit ES6 also unblocks working on melange#342, which requests support for tagged template literals. We'll consider adding support for more features in ES6 and later on a case-per-case basis, if the added expressivity justifies the complexity of the implementation. Feel free to open an issue if you feel that Melange should emit ES6 features that your project requires.

Identifying Melange exceptions in JavaScript

Until Melange 3, exceptions originating from OCaml code compiled with Melange are roughly thrown as such:

throw {
  MEL_EXN_ID: "Assert_failure",
  _1: ["", 42, 8],
  Error: new Error()

As stated in an old ReScript issue, the encoding above is at odds with user exception monitoring in popular vendors.

We set out to fix this for Melange 4. The next release of Melange includes melange#1036 and melange#1043, where we changed the encoding to throw a dedicated MelangeError instance:

-throw {
+throw new Caml_js_exceptions.MelangeError("Assert_failure", {
  MEL_EXN_ID: "Assert_failure",
  _1: ["", 42, 8],
- Error: new Error()

Besides fixing the immediate issue – vendor SDKs for error monitoring now understand Melange runtime errors – this change brings a few additional benefits to users of Melange:

  • Detecting an exception originating from Melange-compiled code is now as easy as using the JS instanceof operator to check if the exception is an instance of Caml_js_exceptions.MelangeError.
  • MelangeError adds support for – and polyfills, if necessary – the cause property in instances of Error, which lets us squeeze out some extra browser support.
    • Additionally, this enables even better integrations with 3rd party monitoring tools, which will look for JavaScript error details in Error.prototype.cause.

melange.js keeps getting better

The Melange 3 announcement touched a bit on the improvements we did in the Js.* modules recently. We're always trying to improve the number of zero-cost bindings to functions in the JS runtime, and their quality. In next release, we're adding more functions to cover Map, Set, WeakMap, WeakSet, BigInt and Iterator.

We're also taking advantage of Dune's obscure extension that models libraries like the OCaml Stdlib ((using experimental_building_ocaml_compiler_with_dune 0.1) in dune-project, the stdlib field in library stanzas). Here's the difference between a "stdlib library" and a regular library: a stdlib library's main module depends on just a subset of the internal modules in the libraries, while others depend on this main module. Dune doesn't allow regular library modules to depend on their main module name.

In the next release of Melange, we treat the Js.* modules like a "stdlib" library (melange#1091): modules are only accessible through the Js.* namespace; as a fortunate side-effect, we stop exposing Js__Js_internal, which could leak into some error messages, causing unnecessary confusion.

OCaml 5.2

With OCaml 5.2 around the corner (the first beta was released just a couple weeks ago), we made Melange ready for the upcoming release. In melange#1074 and melange#1078 we upgraded both the Melange core and the Stdlib to the changes in OCaml 5.2. As mentioned above, we're happy that the next release of Melange will support an even wider range of OCaml compiler versions, making 5.2 the latest addition to the supported compiler versions.

Leveraging Dune's potential

Making Dune faster

Back in January, Javi found a performance regression in Dune (dune#9738) after upgrading to Dune 3.13. The whole fact-finding process of profiling Dune's performance and working closely with the team to patch this regression (dune#9769) ended up being quite the learning experience.

Once the dust settled, Javi took the time to write a blog post outlining some of the tools he used and the steps he used to gather information about Dune's runtime behavior.

Improving dune describe pp

The command dune describe pp prints a given source file after preprocessing. This is useful to quickly inspect the code generate by a (set of) ppx.

dune describe pp didn't, however, support Dune dialects. I found out about this limitation when trying to get the preprocessed output of a ReasonML file.

We recently set out to fix this problem. In dune#10321 we made Reason files and dialects generally work within dune describe pp, and we followed up with the ability to print back the preprocessed output in the same dialect as the given file (dune#10322, dune#10339 and dune#10340).

Virtual libraries in Melange

From Dune's own documentation:

Virtual libraries correspond to Dune’s ability to compile parameterised libraries and delay the selection of concrete implementations until linking an executable.

In the Melange case there's no executable linking going on, but we can still delay the selection of concrete implementations until JavaScript emission – in practice, this means programming against the interface of "virtual modules" in libraries and deferring the dependency on the concrete implementation until the melange.emit stanza.

Or rather, this is now possible after landing melange#1067 and dune#10051: in particular, while Dune support for Melange has shipped with virtual libraries since day one, it didn't support one of the most useful features that they provide: programming against the interface of a virtual module.

Melange rules work within the Dune sandbox

Within the last month, we also fixed a bug where Dune didn't track all Melange dependencies precisely during the JavaScript emission phase. While the originally reported issue saw this bug manifest when moving modules across directories when (include_subdirs ..) is enabled, the fix we applied in dune#10286 and dune#10297 brings with it the fortunate side-effect of making Melange rules work in the Dune sandbox. We're glad this issue is fixed since it could result in the Dune Cache being poisoned, leading to very confusing results.

To make sure that sandboxing keeps working with Melange, we enabled it by default in dune#10312 for the Melange tests in Dune.

Towards Universal React in OCaml

One of our goals for 2024 is to ship a good developer experience around "universal libraries" in OCaml, the ability to write a mixed OCaml / Melange codebase that shares most libraries and modules pertaining to DOM rendering logic.

Dave wrote server-reason-react for this purpose. He also wrote a post on his blog detailing the motivation behind this approach and what he wants to achieve with server-reason-react.

While React component hydration in native OCaml is a challenge specific to Melange and React codebases, there are reusable primitives that we needed to implement in Dune to make it possible. They also unlock a host of new use cases for Dune that we expect will start getting adoption over time.

Universal libraries are already deployed for a small subset of apps at Ahrefs. Javi wrote about how Ahrefs is sharing component code for those apps.

This past quarter, we took it a step further. We added support in Dune for libraries that share the same name, as long as they're defined in different build contexts (dune#10222). Support for libraries with the same name in multiple contexts landed in dune#10307. We'll be diving deeper into what led us to this solution and what it enables in a separate blog post. For now, the remaining work relates to selecting which build context to use for editor integration when using multi-context builds.

Community at large

Bootstrapping Melange projects with create-melange-app

In January, Dillon released v1.0 of create-melange-app, and we're now recommending it for bootstrapping new Melange projects

create-melange-app is a friendly and familiar way to get started with OCaml, ReasonML, and Melange, geared towards JavaScript and TypeScript developers.

create-melange-app quickly became a community favorite after its release, attracting a number of contributors that are helping make it better.

Melange Book

Feihong keeps making really good progress on our book Melange for React Devs, a project-based introduction to Melange for React developers. The newest chapter on Cram testing guides you through the necessary steps for writing tests and snapshotting their output.

There are more chapters in the pipeline that we'll be releasing incrementally as they're ready for consumption. We're also gathering feedback on the book; we invite you to go through it and open issues in the GitHub repository for any material that deserves rewording.

Looking forward

As we look forward, towards the next phase of Melange development, I'd like to take a moment to thank Ahrefs, the OCaml Software Foundation and my GitHub sponsors for the funding and support, without which developing Melange wouldn't have been possible over this sustained period of time.

On a personal note, it fills me with joy to have the opportunity to share the amazing work that Melange contributors have been putting in. It represents a stark contrast from not long ago and the Melange project and community are better for it. Thank you everyone!

As a final note, we thank you for reading through our updates for the first three months of 2024! As we finish planning our work for the next period, we'll share updates on what's to come for Melange in the not-so-distant future.

Until then, stay tuned and happy hacking!