BuckleScript is a JavaScript backend for the OCaml compiler.
You can try BuckleScript in the brower, edit the code on the left panel and see the generated JS on the right panel instantly.
Users of BuckleScript can write type-safe, high performance OCaml code, and deploy the generated JavaScript in any platform with a JavaScript execution engine.
Each OCaml module is mapped to a corresponding JavaScript module, and names are preserved so that:
- The stacktrace is preserved, the generated code is debuggable with or without sourcemap.
- Modules generated by BuckleScript can be used directly from other JavaScript code. For example, you can call the
List.length
function from the OCaml standard libraryList
module from JavaScript.
let sum n =
let v = ref 0 in
for i = 0 to n do
v := !v + i
done;
!v
BuckleScript generates code similar to this:
function sum(n) {
var v = 0;
for(var i = 0; i<= n; ++i){
v += i;
}
return v;
}
As you can see, there is no name mangling in the generated code, so if this module is called M
,
M.sum()
is directly callable from other JavaScript code.
This project has been released to exchange ideas and collect feedback from the OCaml and JavaScript communities. It is in an very early stage and not production ready for your own projects yet.
Note that you have to clone this project with --recursive
option, as the core OCaml compiler is brought into your clone as a Git submodule
.
- Build the patched OCaml Compiler
Checkout the master
branch in the OCaml Repo
For exmaple:
git clone https://github.com/bloomberg/ocaml
cd ./ocaml
git checkout master
./configure -prefix `pwd`
make world.opt
make install
The patched compiler is installed locally into your $(pwd)/bin
directory, check if ocamlc.opt
and ocamlopt.opt
are there, add
them into your $(PATH)
.
- Build BuckleScript Compiler
Assume that you have ocamlopt.opt
in the PATH
cd ./jscomp
ocamlopt.opt -g -inline 100 -linkall -I +compiler-libs -I bin ocamlcommon.cmxa ocamlbytecomp.cmxa bin/compiler.mli bin/compiler.ml -o bin/osc
Now you have a binary called osc
under jscomp/bin
directory,
put it in your PATH
.
Our compiler is released as a single file so that for release-builds it does not need any build system(easier to be supported on Windows Platform).
- Test
Create a file called hello.ml
:
mkdir tmp # create tmp directory inside the stdlib
cd tmp
echo 'print_endline "hello world";;' >hello.ml
Then compile it with osc
osc -I . -I ../ -c hello.ml
It should generate a file called hello.js
, which can be executed with any JavaScript engine. In this example, we use Node.js
node hello.js
If everything goes well, you will see hello world
on your screen.
Note that the following steps are optional, it is used to build the runtime and standard library to verify if it works, you don't need run these steps.
- Build the runtime with
osc
cd runtime
make all
- Build the standard library with
osc
cd ../stdlib
make all
We plan to provide a Windows installer in the near future.
The OCaml directory is the official OCaml compiler (version 4.02.3). Refer to its copyright and license notices for information about its licensing.
This project reused and adapted parts of js_of_ocaml:
- Some small printing utilities in pretty printer.
- Part of the Javascript runtime support
It adapted two modules Lam_pass_exits and Lam_pass_lets_dce from OCaml's Simplif module, the main reasons are those optimizations are not optimal for Javascript backend.
Js_main is adapted from driver/main, it is not actually used, since currently we make this JS backend as a plugin instead, but it shows that it is easy to assemble a whole compler using OCaml compiler libraries and based upon that we can add more compilation flags for JS backend.
stdlib is copied from ocaml's stdlib to have it compiled with the new JS compiler.
Since our work is derivative work of js_of_ocaml, the license of the BuckleScript components is GPLv2, the same as js_of_ocaml.
-
Readability
-
No name mangling.
-
Support JavaScript module system.
-
Integrate with existing JavaScript ecosystem, for example, npm, webpack.
-
Straight-forward FFI, generate tds file to target Typescript for better tooling.
-
Separate and extremely fast compilation.
-
Better performance than hand-written Javascript: Thanks to the solid type system in OCaml it is possible to optimize the generated JavaScript code.
-
Smaller code than hand written JavaScript, aggressive dead code elimination.
-
Support Node.js, web browsers, and various Javascript target platforms.
-
Compatible with OCaml semantics modulo c-bindings and Obj, Marshal modules.
Below is a contrived example to demonstrate our motivation, it tries to insert 1,000,000 keys into an immutable map, then query it.
module IntMap = Map.Make(struct
type t = int
let compare (x : int) y = compare x y
end)
let test () =
let m = ref IntMap.empty in
let count = 1000000 in
for i = 0 to count do
m := IntMap.add i i !m
done;
for i = 0 to count do
ignore (IntMap.find i !m )
done
let () = test()
The code generated by BuckleScript is a drop-in replacement for the Facebook immutable library.
'use strict';
var Immutable = require('immutable');
var Map = Immutable.Map;
var m = new Map();
var test = function(){
var count = 1000000
for(var i = 0; i < count; ++i){
m = m.set(i, i );
}
for(var j = 0; j < count ; ++j){
m.get(j)
}
}
test ()
Runtime performance:
- BuckleScript Immutable Map: 1186ms
- Facebook Immutable Map: 3415ms
Code Size:
- BuckleScript (Prod mode): 899 Bytes
- Facebook Immutable : 55.3K Bytes
While most of the OCaml language is covered, because this project is still young there is plenty of work left to be done.
Some known issues are listed as below:
-
Language features:
Int32 operations, currently, Int use Float operations, this should be fixed in the near future.
-
Standard libraries distributed with OCaml:
IO support, we have very limited support for
Pervasives.print_endline
andPervasives.prerr_endline
, it's non-trivial to preserve the same semantics of IO between OCaml and Node.js, one solution is to functorize all IO operations. Functors are then inlined so there will no be performance cost or code size penalty.Bigarray, Unix, Num, Int64
-
String is immutable, user is expected to compile with flags
-safe-string
for all modules:Note that this flag should be applied to all your modules.
If you have questions, comments, suggestions for improvement or any other inquiries regarding this project, feel free to open an issue in the issue tracker.