Reducing the size of js_of_ocaml output

I finished compiling our app with js_of_ocaml, and now would like to reduce the size of its output significantly. The output is currently 3.2MB, and I managed to reduce it to 2.4MB, but now i’m stuck.

The main things I’ve already done:

  • removed a lot of the ppx_derivers, especially ppx_bin_prot, which saved about 800KB (derivers like show, eq, compare, sexp, etc, saved about 20kb each after I removed them).
  • --setenv FORCE_DROP_INLINE_TEST=true (saved about 170k)
  • removed any runtime js files I could

Here’s things I tried which didn’t make much difference:

  • using --opt 3 (maybe 10KB difference)
  • --disable debugger: no difference
  • (link_flags (-linkall)): made it a little bit bigger
  • tried running ocamlclean on the bytecode, but it didnt work (we’re using ocaml 4.07, and got the error “Error: invalid bytecode file (magic string does not match)”)
  • --no-inline: made it 300k bigger, unsurprisingly

Has anyone got advice on how to approach this?

PS: There are our dune files, using dune 1.1.1:

$ cat bin/jbuild # this is the entry point
  ((name darkjs)
    (public_name darkjs)
    (modules darkjs)
    (flags (-no-check-prims))
      ((flags (+nat.js
    (libraries (js_of_ocaml libfrontend))
    (preprocess (pps (js_of_ocaml-ppx)))
    (package dark)))

$ cat libcommon/jbuild
(jbuild_version 1)

  ((name libcommon)
    (flags (-warn-error (+A)))
    (libraries (core_kernel libtarget))))

$ cat /libfrontend/jbuild
(jbuild_version 1)

  ((name libfrontend)
    (flags (-warn-error (+A)))
    (preprocess (pps (
    (libraries (libtarget_js libexecution))))

$ cat server/libexecution/jbuild # this is where most of the source code is
(jbuild_version 1)

  ((name libexecution)
    (flags (-warn-error (+A) -opaque))
    (preprocess (pps (
    (libraries (
                ; note that libtarget.{ocaml,js} are not included here

$ cat libtarget/jbuild # empty lib
 ((name libtarget)
  (public_name libtarget)
  (wrapped false)
  (flags (:standard -no-keep-locs -opaque))
  (libraries (core_kernel))
  (modules_without_implementation (libtarget))))

$ cat libtarget.js/jbuild
(jbuild_version 1)

 ((name libtarget_js)
  (wrapped false)
  (libraries (core_kernel js_of_ocaml))
  (flags (-warn-error (+A) -no-keep-locs))
    ((flags (+nat.js
             --setenv FORCE_DROP_INLINE_TEST=true
    (javascript_files (libtarget.js))))))

(rule (copy# ${lib:libtarget:libtarget.mli} libtarget.mli))


Have you tried using Base instead of Core_kernel?


Haven’t tried switching to Base. I thought of it, but given we use quite a few things in Core_kernel, I’m reluctant to try that one right now.


There are some parts that can cause problems in your jbuild file:

(flags (-warn-error (+A) -opaque))

-opaque will prevent any cross-module optimization, so removing it is likely to improve the situation. Note that recent versions of dune will automatically add it in dev mode (so that the build is quicker) and omit it in release (so that the production binary is more optimized).

(libraries (… ppx_deriving_yojson ppx_deriving_yojson.runtime))))

That’s a likely culprit. This will pull ppx_deriving_yojson at runtime, including parts of the compiler (compiler-libs). You can safely remove ppx_deriving_yojson here and .runtime is supposed to be added automatically, but in the case of ppx_deriving_yojson I think that some parts are not released yet so you might have to keep that part.

Hope that helps!


I’m gonna ask a likely stupid question, but just in case, are you building with release profile?

This may be obvious, but for the avoidance of doubt: Base has the most important bits of Core_kernel included in it. So the key question is whether you’re using things that are in Core_kernel but not in Base.

1 Like

Yes, and I’m even using it in --profile release in dev because it never finishes compiling when I don’t.

Yes, this was a big win! It looks like the biggest thing was that I was compiling in ppx_deriving_yojson. Removing that dropped 2.5MB -> 800KB.

I think also this means when I removed derivers earlier, I was actually removing the deriver itself, and not the runtime.

I tried removing opaque and it didnt make a difference to jsoo, but my natively compiled binaries grew slightly (presumably they’ll be faster now, so I’ll leave it).


I gave it a quick shot. First thing was String.slice was missing, second thing was that my show derivers complained about Format being missing or deprecated.

I’ve got it down to 800k after the derivers got pulled out, so I’m gonna let gzip handle the rest for now. I’ll give switching to Base a go at some point again – thanks for the suggestion!