{0 Targeting older browsers}

Js_of_ocaml's generated code and runtime target {b ECMAScript 2020 (ES2020)}.
Out of the box, the output runs on Node.js 18+, QuickJS-NG, and any evergreen
browser released since early 2020 (Chrome 80+, Firefox 74+, Safari 13.4+, Edge 80+).

If you need to support an older engine, you can post-process the
generated JavaScript with {{:https://babeljs.io}Babel} and ship
polyfills with {{:https://github.com/zloirock/core-js}core-js}.
Js_of_ocaml has no built-in downleveling.

{1:babel-recipe Transpiling with Babel}

Install Babel and core-js:

{v
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install core-js
v}

Create a [babel.config.json] next to your build:

{@json[
{
  "presets": [
    ["@babel/preset-env", {
      "targets": "> 0.5%, last 2 versions, not dead",
      "useBuiltIns": "usage",
      "corejs": 3
    }]
  ]
}
]}

Set [targets] to the
{{:https://github.com/browserslist/browserslist}browserslist query}
matching the engines you need to support.

Compile your program with js_of_ocaml, then run Babel on the output:

{v
js_of_ocaml program.byte -o program.js
npx babel program.js -o program.es5.js
v}

The resulting [program.es5.js] imports polyfills from [core-js]
with [require("core-js/modules/...")] calls. Before shipping to a
browser you need to bundle it so those imports are resolved and inlined.

{1:bundling Bundling}

Any JavaScript bundler will work; {{:https://esbuild.github.io}esbuild}
is a small, zero-config option:

{v
npm install --save-dev esbuild
npx esbuild program.es5.js --bundle --platform=browser \
  --external:node:* --target=es5 --outfile=program.bundle.js
v}

The runtime contains a few [require("node:*")] calls (filesystem,
tty, etc.) guarded by Node-detection branches that never run in a
browser. [--external:node:*] tells esbuild to leave those imports
in place instead of failing to resolve them at bundle time.

{{:https://esbuild.github.io/api/#target}[--target]} is needed even
when Babel has lowered the input: esbuild's own IIFE wrapper and CommonJS
helpers use arrow functions and other post-ES5 syntax, and [--target]
is what tells it to lower those too. Set it to the lowest syntax level
your browserslist query implies.

Ship [program.bundle.js] as a single script; no additional polyfills
are required at load time.

{1:dune With dune}

Assuming dune builds your program to [program.bc.js] (e.g. from an
[(executable (modes js) ...)] stanza), wire the Babel and esbuild
steps into the build with two rules:

{v
(rule
 (targets program.es5.js)
 (deps program.bc.js babel.config.json)
 (action
  (run npx babel %{dep:program.bc.js} -o %{targets})))

(rule
 (targets program.bundle.js)
 (deps program.es5.js)
 (action
  (run npx esbuild %{dep:program.es5.js}
       --bundle --platform=browser --external:node:*
       --target=es5 --outfile=%{targets})))
v}

{1:see-also See also}

- {{!page-"options"}Command line options} — compiler flags
- {{!page-"install"}Installation} — supported engines
