BuckleScript is a backend for the OCaml compiler which emits
JavaScript. It works with both vanilla OCaml and Reason, the
whole compiler is compiled into JS (and ASM) so that you can play with it
in the browser.

If you find errors or omissions in this document, please don’t
hesitate to submit an issue, sources are here.

Why BuckleScript

Benefits of JavaScript platform

JavaScript is not just the browser language, it’s also the only
existing cross platform language. It is truly everywhere: users don’t
need to install binaries or use package managers to access software,
just a link will work.

Another important factor is that the JavaScript VM is quite fast and
keeps getting faster. The JavaScript platform is therefore
increasingly capable of supporting large applications.

Problems of JavaScript && how BuckleScript solves them

BuckleScript is mainly designed to solve the problems of large scale JavaScript programming:

Type-safety

OCaml offers an industrial-strength
state-of-the-art type system and provides very strong type inference (i.e. No
verbose type annotation required compared with TypeScript), which proves
invaluable
in managing large projects. OCaml’s type system is not just for tooling,
it is a sound type system which means it is guaranteed that there will
be no runtime type errors after type checking.

High quality dead code elimination

A large amount of web-development relies on inclusion of
code dependencies by copying or referencing CDNs (the very thing
that makes JavaScript highly accessible), but this also introduces
a lot of dead code. This
impacts performance adversely when the JavaScript VM has to
interpret code that will never be invoked. BuckleScript provides
powerful dead-code elimination at all levels:

Function and module level elimination is facilitated by the
sophistication of the type-system of OCaml and purity analysis.

At the global level BuckleScript generates code ready for
dead-code elimination done by bundling tools such as the
Google closure-compiler.

Offline optimizations

JavaScript is a dynamic language, it
takes a performance-hit for the VM to optimize code at runtime.
While some JS engines circumvent the problem to some extent by
caching,
this is not available to all environments, and lack of a strong
type system also limits the level of optimizations possible. Again,
BuckleScript, using features of the OCaml type-system and compiler
implementation is able to provide many optimizations during offline
compilation, allowing the runtime code to be extremely fast.

JS platform and Native platform

Run your programs on all platforms, but run your system faster
under specific platforms. JavaScript is everywhere but it does not
mean we have to run all apps in JS, under several platforms, for
example, server side or iOS/Android native apps, when programs are
written in OCaml, it can also be compiled to native code for better
and reliable performance.

While a strong type-system helps in countering these problems, at the
same time we hope to avoid some of the problems faced in using other
offline transpilation systems:

Slow compilation

OCaml byte-code compilation is known to be fast
(one or two orders of magnitude faster than other similar languages:
Scala or
Haskell),
BuckleScript shares the same property and compiles even faster
since it saves the link time. See the speeds at work in the
playground, the native backend is one
order faster than the JS backend.

Un-readable JS Code and hard to integrate with existing JS libraries

When compiling to JavaScript, many systems
generate code, that while syntactically and semantically correct is
not human-readable and very difficult to debug and profile.
Our BuckleScript implementation and the multi-pass compilation strategy of OCaml,
allows us to avoid name-mangling,
and produce JavaScript code that is human-readable and easier to debug and
maintain. More importantly, this makes integration with existing JS
libraries much easier.

Large JS output even for a simple program

In BuckleScript, a Hello world program generates 20 bytes JS code
instead of 50K bytes. This is due to BuckleScript having an excellent
integration with JS libs in that unlike most JS compilers,
all BuckleScript’s runtime is written in OCaml itself so that these
runtime libraries are only needed when user actually calls it.

Loss of code-structure

Many systems generate JavaScript code that is essentially a
big ball of mud. We try
to keep the original structure of the code by mapping one OCaml module
to one JS module.

Installation

Below is a list of different ways to install BuckleScript:

Windows Installation

npm install bs-platform

*nix installation

Prerequisites

Standard C toolchain

npm (should be installed with Node)

The standard npm package management tool can be used to install
BuckleScript. If you don’t already have npm installed, follow the
directions listed
here. Once npm
is installed, run the following command:

npm install --save bs-platform

or install it globally

npm install -g bs-platform

Recommended installation with OPAM

When working with OCaml we also recommend using opam
package manager to install OCaml toolchains, available
here. You will benefit from the
existing OCaml ecosystem.

Once you have opam installed, ask opam to switch to using our
version of the compiler:

The patched compiler is installed locally into your $(pwd)/bin
directory. To start using it temporarily, check if ocamlc.opt and
ocamlopt.opt exist in $(pwd)/bin, and temporarily add the location
to your $(PATH) (e.g. PATH=$(pwd)/bin:$PATH).

Building BuckleScript

The following directions assume you already have the correct version of
ocamlopt.opt in your $PATH, having followed the process described in
the previous section.

cd ../../jscomp
make world

At the end, you should have a binary called bsc.exe under jscomp/bin
directory, which you can add to your $PATH.
You could also set an environment variable
pointing to the stdlib, e.g. BSC_LIB=/path/to/jscomp/stdlib for ease
of use.

Warning

The built compiler is not relocatable out of box, please don’t move it around unless you know what you are doing

Get Started

Using existing templates (@since 1.7.4 )

BuckleScript provide some project templates, to help people get started on board as fast as possible.

To enter watch mode, you can run npm run watch,
then you can edit src/demo.ml and see it compiled on the fly, and changes are updated on lib/js/src/demo.js

If you happen to use VSCode as editor, we provide .vscode/tasks.json as well, type Tasks>build, it will enter watch mode, the great thing is that VSCode comes with
a Problems panel which has a much nicer UI.

Currently there are not too many project templates, you can contribute more project templates here here,

First example by hand without samples

Create a directory called hello and create bsconfig.json and package.json
as below:

By passing this flag, bsc.exe will store your package_name and
relative path to package.json in .cmj files. It will also generate
JS files in the directory you specified. You can, and are encouraged
to, store JavaScript files in a hierarchical directory.

For the binary artifacts (Note that this is not necessary if you only
want your libraries to be consumed by JS developers, and it has
benefit since end users don’t need these binary data any more), the
convention is to store all *.cm data in a single directory
package.json/lib/ocaml
and Javascript files in a hierachical directory like package.json/lib/js

To use OCaml library as a npm package

If you follow the layout convention above, using an OCaml package is
pretty straightforward:

Examples

JS Calling OCaml

Since BuckleScript guarantees that all OCaml functions are exported as
is, no extra work is required to expose OCaml functions to JavaScript.

Caution

external exports are not exported as JS functions, if you really
want to export those external functions, please write val instead

operators are escaped, since Javascript does not support user
defined operators. For example, instead of calling Pervasives.(^),
you have to call Pervasives.$caret from your JavaScript functions

If users want to consume some OCaml features only available in OCaml but not in JS,
we recommend users to export it as functions.

For example, data constructors are not available in JS

typet=|Consofint*t|Nil

Currently, we recommend the user expose the constructor as a function
so that it can be constructed from the JS side.

letconsxy=Cons(x,y)letnil=Nil

Note

In the future, we will derive these functions to
automate this process.

BuckleScript annotations for Unicode and JS FFI support

To make OCaml work smoothly with JavaScript, we introduced several
extensions to the OCaml language. These BuckleScript extensions
facilitate the integration of native JavaScript code and
improve the generated code.

Unicode support (@since 1.5.1)

Warning

In this section, we assume the source is encoded using UTF8.

In OCaml, string is an immutable byte sequence (like GoLang), so if the user types some Unicode

FFI

Like TypeScript, when building type-safe bindings from JS to OCaml,
users have to write type declarations.
In OCaml, unlike TypeScript, users do not need to create a separate
.d.ts file,
since the type declarations are an integral part of OCaml.

The FFI is divided into several components:

Binding to simple functions and values

Binding to high-order functions

Binding to object literals

Binding to classes

Extensions to the language for debugger, regex, and embedding arbitrary JS
code

Binding to global value: bs.val

bs.val attribute is used to bind to a JavaScript value,
it can be a function or plain value.

Note

If external-declaration is the same as value-name, the user can leave external-declaration empty.
For example:

externaldocument:dom=""[@@bs.val]

If you want to make a single FFI for both C functions and
JavaScript functions, you can
give the JavaScript foreign function a different name:

externalimul:int->int->int="c_imul"[@@bs.val"Math.imul"]

Scoped values: bs.scope (@since 1.7.2)

In JS library, it is quite common to use a name as namespace,
for example, if the user want to write a binding to
vscode.commands.executeCommand, assume vscode is a module name,
the user needs to type commands properly before typing executeCommand, and in practice, it is rarely useful to call vscode.commands alone, for this reason, we introduce a convient sugar: bs.scope

Using polymorphic variant to model enums and string types

There are several patterns heavily used in existing JavaScript codebases, for example,
the string type is used a lot. BuckleScript FFI allows the user to model string type in a safe
way by using annotated polymorphic variant.

These [@bs.string], [@bs.int], and [@bs.unwrap] annotations will only have effect in external declarations.

The runtime encoding of using polymorphic variant is internal to the compiler.

With these annotations mentioned above, BuckleScript will automatically
transform the internal encoding to the designated encoding for FFI.
BuckleScript will try to do such conversion at compile time if it can, otherwise, it
will do such conversion in the runtime, but it should be always correct.

Phantom Arguments and ad-hoc polymorphism

bs.ignore allows arguments to be erased after passing to JS functional call, the side effect will
still be recorded.

Using a GADT as a [@bs.ignore] argument as described to achieve a single polymorphic argument can now also be accomplished with [@bs.unwrap] above. The GADT + [@bs.ignore] approach is slightly more flexible and is always guaranteed to have no runtime overhead, where [@bs.unwrap] could incur slight runtime overhead in some cases but could be a more intuitive API for end users.

Fixed Arguments

Contrary to the Phantom arguments, _ [@bs.as] is introduced to attach constant
data.

This is less convenient for end users, so we introduce another implicit annotation [@bs.uncurry] so that the compiler will automatically wrap the curried callback (from OCaml side) to JS uncurried callback. In this way, the [@bs.uncurry] annotation is defined
only once.

In general, bs.uncurry is recommended, and compiler will do lots of optimizations to resolve the curry to uncurry calling convention at compile time. However, there are some cases the compiler optimizer could not do it, in that case, it will be converted runtime.

This means [@bs] are completely static behavior (no any runtime cost), while [@bs.uncurry] is more convenient for end users but in some very rare cases it might be slower than [@bs]

Uncurried calling convention as an optimization

Background:

As we discussed before, we can compile any OCaml function as arity 1
to
support OCaml’s curried calling convention.

This model is simple and easy to implement, but
the native compilation is very slow and expensive for all functions.

BuckleScript does this optimization in the cross module level and tries
to infer the arity as much as it can.

Callback optimization

However, such optimization will not work with high-order functions,
i.e, callbacks.

For example,

letappfx=fx

Since the arity of f is unknown, the compiler can not do any optimization
(unless app gets inlined), so we
have to generate code as below:

functionapp(f,x){returnCurry._1(f,x);}

Curry._1 is a function to dynamically support the curried calling
convention.

Since we support the uncurried calling convention, you can write app
as below

letappfx=fx[@bs]

Now the type system will infer app as type
('a →'b [@bs]) → 'a and compile app as

functionapp(f,x){returnf(x)}

Note

In OCaml the compiler internally uncurries every function
declared as external and guarantees that it is always fully applied.
Therefore, for external first-order FFI, its outermost function does
not need the [@bs] annotation.

Bindings to this based callbacks: bs.this

Many JS libraries have callbacks which rely on this (the source), for
example:

x.onload=function(v){console.log(this.response+v)}

Here, this would be the same as x (actually depends on how onload
is called). It is clear that
it is not correct to declare x.onload of type unit → unit [@bs].
Instead, we introduced a special attribute
bs.this allowing us to type x as below:

There is no way to consume a function of type
'obj → 'a0 .. → 'aN → 'b0 [@bs.this] on the OCaml side.
This is an intentional design choice, we don’t encourage people to write code in this style.

This was introduced mainly to be consumed by existing JS libraries.
User can also type x as a JS class too (see later)

Binding to JS objects

Convention:

All JS objects of type 'a are lifted to type 'a Js.t to avoid
conflict with OCaml’s native object system (we support both OCaml’s
native object system and FFI to JS’s objects), ## is used in JS’s
object method dispatch and field access, while # is used in OCaml’s
object method dispatch.

Typing JavaScript objects:

OCaml supports object oriented style natively and provides structural type system.
OCaml’s object system has different runtime semantics from JS object, but they
share the same type system, all JS objects of type 'a are typed as 'a Js.t

OCaml provides two kinds of syntaxes to model structural typing: < p1 : t1 > style and
class type style. They are mostly the same except that the latter is more feature rich
(supporting inheritance) but more verbose.

Simple object type

Suppose we have a JS file demo.js
which exports two properties: height and width:

demo.js

exports.height=3exports.width=3

There are different ways to writing binding to module demo,
here we use OCaml objects to model module demo

In OCaml, the order of label does not matter, and the evaluation order
of arguments is undefined. Since the order does not matter, to make sure the compiler realize all the arguments
are fulfilled (including optional arguments), it is common to have a unit type before the result.

Method chaining

Object label translation convention

There are two cases, where we might want to do name mangling for a JS object method name.

First, in OCaml, some names are keywords, so we want to add an underscore to avoid a syntax
error.

Key-word method:

f##_openf##_MAX_LENGTH

OUTPUT:

f.openf.MAX_LENGTH

Second, it is common to have several types for a single method. To model
this ad-hoc polymorphism, we introduced a small convention when translating
object labels, which is occasionally useful as below

Ad-hoc polymorphism

f##draw__cat(x,y)f##draw__dog(x,y)

OUTPUT:

f.draw(x,y)// f.draw in JS can accept different typesf.draw(x,y)

Note

Rules

If __[rest] appears in the label, index from the right to left.

If index = 0, nothing mangled

If index > 0, __[rest] is dropped

Else if _ is the first char

If the following char is not 'a' .. 'z',
drop the first '_'

Else if the rest happens to be a keyword,
drop the first '_'

Else, nothing mangled

Return value checking (@since 1.5.1)

In general, the FFI code is error prone, and potentially will leak in
undefined or null values.

So we introduced auto coercion for return values to gain two benefits:

Currently 4 directives are supported: null_to_opt, undefined_to_opt,
nullable(introduced in @1.9.0) and identity.
null_undefined_to_opt works the same as nullable,
but it is deprecated, nullable is encouraged

Note

null_to_opt, undefined_to_opt and nullable will semantically
convert a nullable value to option which is a boxed value, but the compiler will
do smart optimizations to remove such boxing overhead when the returned value is destructed
in the same routine.

The three directives above require users to write literally _ option. It is
in theory not necessary, but it is required to reduce user errors.

When the return type is unit: the compiler will append its return value
with an OCaml unit literal to make sure it does return unit. Its main purpose
is to make the user consume FFI in idiomatic OCaml code, the cost is very very small and
the compiler will do smart optimizations to remove it when the returned value is not used (mostly likely).

When the return type is bool, the compiler will coerce its return value from
JS boolean to OCaml boolean. The cost is also very small and compiler will remove
such coercion when it is not needed. Note even if your external FFI does return OCaml bool or unit,
such implicit coercion will cause no harm.

identity will make sure that compiler will do nothing about the returned value. It
is rarely used, but introduced here for debugging purpose.

Embedding untyped Javascript code

Warning

This is not encouraged. The user should minimize and
localize use cases
of embedding raw JavaScript code, however, sometimes it’s necessary to
get the job done.

Detect global variable existence bs.external (@since 1.5.1)

Before we dive into embedding arbitrary JS code, a quite common use case of embedding untyped JS code is detect a global variable (feature detection), Bucklescript provides a relatively type safe approach for such use case: bs.external (or external),
[%bs.external a_single_identifier] is a value of _ option type, see examples below

Exception handling between OCaml and JS (@since 1.7.0)

In the JS world, exception could be any data, while an OCaml exception is a structured data format and supports pattern matching. Catching an OCaml exception on JS side is therefore a no-op.

JS exceptions can be raised from OCaml by using the JS.Exn.raise* functions, and can be caught as an OCaml exception of the type Js.Exn.Error with the JS exception as it’s paylaod typed as Js.Exn.t. The JS Exception can then either be manipulated with the accessor functions in Js.Exn, or casted to a more appropriate type.

bs.open: Type safe external data-source handling (@@since 1.7.0)

There are some cases, the data-source could either come from JS land or OCaml land, it is very hard to give precise type information.
For example, for an external promise whose creation could come from JS API, its failed value caused by Promise.reject could be in any shape.

BuckleScript provides a solution to filter out OCaml structured exception data from the mixed data source,
it preserves the type safety while allow users to deal with mixed source.

It makes use of OCaml’s extensible variant, so that users can mix values of type exn with JS data-source

Js module

Js module is shipped with BuckleScript, both the namespace Js and Node are preserved.

Null and Undefined

BuckleScript does not deal with null and undefined on the language level.
Instead they are defined on the library level, represented by Js.Null.empty
and Js.Undefined.empty (and Js.Null_undefined.empty) types. But due to this
being Ocaml and all, we can’t just pass these values off as if it was any type
we’d like it to be, so in order to actually use them we need to lift the type we
want to use them with. For this purpose BuckleScript defines the type 'a Js.null,
'a Js.undefined' and ’a Js.null_undefined (for values that can be both
null, undefined and 'a). As well as the corresponding modules Js.Null,
Js.Undefined and Js.Null_undefined for working with these types.

Here’s an example showing Js.Null used directly:

externalget_prop_name_maybe:unit->stringJs.null=""[@@bs.val]externalset_or_unset_prop:string->intJs.null->unit=""[@@bs.val](* unset property if fond *)lets=get_prop_name_maybe()let_=Js.Null.iters(functionname->set_or_unset_propnameJs.Null.empty)(* set some other property *)let_=set_or_unset_prop"some_other_property"(Js.Null.return"")

You might also want to map these types to option for a friendlier and more
idiomatic API, if the distinction between null and undefined isn’t important
to maintain:

Boolean

"Native" bool is for Bob-ish reasons not represented as true and false
literals in JavaScript. For interop you therefore need to use the Js.boolean
and its Js.true_ and Js.false_ values. There are also convenience functions
for coercing to and from bool: Js.to_bool and Js.Boolean.to_js_boolean.

Extended compiler options

This section is only for people who want roll their own build system instead of using the recommended build system: BuckleScript build system: bsb,
in general, it is safe to skip this section.
It also adds some tips for people who debug the compiler.

BuckleScript inherits the command line arguments of the
OCaml compiler. It
also adds several flags:

-bs-main (single directory build)

bsc.exe -bs-main Main

bsc.exe will build module Main and all its dependencies and when it
finishes, it will run node main.js.

bsc.exe -c -bs-main Main

The same as above, but will not run node.

-bs-files

So that you can do

bsc.exe -c -bs-files *.ml *.mli

the compiler will sort the order of input files before starting
compilation.

BuckleScript supports two compilation modes: script mode and package
mode. In package mode, you have to provide package.json on top and set the options
-bs-package-name, -bs-package-output. In script mode, such flags are not needed.

-bs-package-name

The project name of your project. The user is suggested to make it
consistent with the name field in package.json

-bs-packge-output

The format is module_system:oupt/path/relative/to/package.json
Currently supported module systesms are: commonjs, amdjs and
es6.

For example, when you want to use the es6 module system, you can do
things like this:

For this flag, it will not create any intermediate file, which is useful for
learning or troubleshooting.

-bs-no-builtin-ppx-ml, -bs-no-builtin-ppx-mli

If the user doesn’t use any bs specific annotations, the user can explicitly turn it off.
Another use case is that users can use -ppx explicitly as below:

bsc.exe-c-ppxbsppx.exe-bs-no-builtin-ppx-mlc.ml

-bs-no-version-header

Don’t print version header.

Semantic differences from other backends

This is particularly important when porting an existing OCaml
application to JavaScript.

Custom data type

In OCaml, the C FFI allows the user to define a custom data type and
customize caml_compare, caml_hash behavior, etc. This is not
available in our backend (since we have no C FFI).

Physical (in)equality

In general, users should only use physical equality as an optimization
technique, but not rely on its correctness, since it is tightly coupled
with the runtime.

String char range

Currently, BuckleScript assumes that the char range is 0-255. The user
should be careful when they pass a JavaScript string to the OCaml side. Note
that we are working on a solution for this problem.

Weak map

OCaml’s weak map is not available in BuckleScript. The weak pointer is
replaced by a strict pointer.

Integers

OCaml has int, int32, nativeint and int64 types.
- Both int32 and int64 in BuckleScript have the exact same semantics as OCaml.
- int in BuckleScript is the same as int32 while in OCaml it’s platform dependent.
- nativeint is treated as JavaScript float, except for division.
For example, Nativeint.div a b will be translated into a /b | 0.

Warning

Nativeint.shift_right_logical x 0 is different from
Int32.shift_right_local x 0. The former is literally translated into
x >>> 0 (translated into an unsigned int), while the latter is
x | 0.

Printf.printf

The Printf.print implementation in BuckleScript requires a newline
(\n) to trigger the printing. This behavior is not consistent with the
buffered behavior of native OCaml. The only potential problem we foresee
is that if the program terminates with no newline character, the text
will never be printed.

Obj module

We do our best to mimic the native compiler, but we have no guarantee
and there are differences.

Hashtbl hash algorithm

BuckleScript uses the same algorithm as native OCaml but the output is
different due to the runtime representation of int/int64/int32 and float.

Marshall

Marshall module is not supported yet.

Sys.argv, Sys.max_array_length, Sys.max_string_length

Command line arguments are always empty.
This might be fixed in the near future.
Sys.max_array_length and Sys.max_string_length will be the same as
max_int, but it might be respected.

Unsupported IO primitives

Because of the JavaScript environment limitation, Pervasives.stdin is
not supported but both Pervasives.stdout and Pervasives.stderr are.

Conditional compilation support - static if

It is quite common that people want to write code that works with different versions of compilers and libraries.

Instead of using a preprocessor, BuckleScript adds language level static if compilation to the language.
It is less powerful than other preprocessors since it only supports static if (no #define, #undefine, #include)
but there are several advantages.

It’s very small (only around 500 LOC) and highly efficient.
There is no added pass, allowing everything to be done in a single pass.
It is easy to rebuild the pre-processor in a stand alone file, with no dependencies on compiler
libs to back-port it to old OCaml compilers.

It’s purely functional and type safe, easy to work with IDEs like Merlin.

Typing rules

type of INT is int

type of STRING is string

type of FLOAT is float

value of UIDENT comes from either built-in values (with documented types) or an environment variable,
if it is literally true, false then it is bool, else if it is parsable by int_of_string
then it is of type int, else if it is parsable by float_of_string then it is float, otherwise
it would be string

In lhs operator rhs, lhs and rhs are always the same type and return boolean.
=~ is a semantic version operator which requires both sides to be string.

Built in variables and custom variables

Changes to command line options

For BuckleScript users, nothing needs to be done (it is baked in the language level).
For non BuckleScript users, we provide an external pre-processor, so it will work with other OCaml
compilers too.
Note that the bspp.ml is a stand alone file,
so that it even works without compilation.

The build system is installed as bsb.exe in bs-platform/bin/bsb.exe. Due to a known issue in npm,
we create a JS wrapper (which is accessible in .bin too) so the user can call
either bsb (slightly higher latency but symlinked into .bin) or bsb.exe

Here is a minimal configuration:

bsconfig.json

{"name":"test",// package name, required (1)"sources":"src"(2)}

It is an extension to JSON with comment support

Here we did not list files, so all .ml, .mli, .re, .rei will be considered as source files

The entry point is bsb.
It will check if there is already build.ninja in the build directory,
and if not or it needs to be regenerated it will generate the file build.ninja
and delegate the hard work to ninja.

Now in your working directory, type watchman -j < build.json and enjoy the lightning build speed.

Customize rules (generators support, @since 1.7.4)

It is quite common that programmers use some pre-processors to generate some bolierpolate code during developement.

Note pre-processors can be classified as two categories, one is system-dependent which should be delayed until running on user machines, the other is system-indepdent , lex, yacc, m4, re2c, etc, which could be executed anytime.

BuckleScript has built in support for conditional compilation, this section is about the second part, since it is system-indepdent, we ask users to always generate such code and check in before shipping, this would help cut the dependencies for end users.

Note ocamlyacc will generate in test.ml and test.mli in the same directory with test.mly, user should check in generated file since then users would not need run ocamlyacc again, this would apply to menhir as well.

When users are developing current project, bsb will track the dependencies between test.ml and test.mly properly, when released
as a package, bsb will cut such dependency, so that users will
only need the generated test.ml, to help test such behavior in development mode, users could set it manually

{...,"cut-generators":true}

Then bsb will not re-generate test.ml whenever test.mly changes.

FAQ

How does IO work in the browser?

In general, it is very hard to simulate IO in the browser, we recommend users write bindings to NodeJS directly for server side, or use Js.log in client side, see discussions in #748

The compiler does not build?

In production mode, the compiler is a single file in
jscomp/bin/compiler.ml. If it is not compiling, make sure you have the
right OCaml compiler version. Currently the OCaml compiler is a
submodule of BuckleScript. Make sure the exact commit hash matches (we
only update the compiler occasionally).

Note there is a upstream fix in Merlin, make sure your merlin is updated.

What polyfills does BuckleScript need?

Math.imul:
This polyfill is needed for int32 multiplication.
BuckleScript provides this by default (when feature detection returns false), no action is
required from the user.

TypedArray:
The TypedArray polyfill is not provided by BuckleScript and it’s the
responsibility of the user to bundle the desired polyfill implementation
with the BuckleScript generated code.

The following functions from OCaml stdlib
require the TypedArray polyfill:

Int64.float_of_bits

Int64.bits_of_float

Int32.float_of_bits

Int32.bits_of_float

Warning

For the current BuckleScript version, if the user does not bundle the
TypedArray polyfill, the JavaScript engine does not support it and user used
functions mentioned above, the code will fail at runtime.

Uncurried functions cannot be polymorphic?

E.g. if you try to do this at toplevel:

let id : ('a -> 'a [@bs]) = ((fun v -> v) [@bs])

You’ll get this dreaded error message

Error: The type of this expression, ([ `Arity_1 of '_a ], '_a) Js.fn,
contains type variables that cannot be generalized

The issue here isn’t that the function is polymorphic. You can use (and probably have used) polymorphic uncurried functions without any problem as inline callbacks. But you can’t export them. The issue here is the combination of using the uncurried calling convention, polymorphism and exporting (by default). It’s an unfortunate limitation partly due to how OCaml’s type system incorporates side-effects, and partly due to how BuckleScript handles uncurrying. The simplest solution is in most cases to just not export it, by adding an interface to the module. Alternatively, if you really, really need to export it, you can do so in its curried form, and then wrap it in an uncurried lambda at the call site. E.g.:

Design Principles

The current design of BuckleScript follows several high level
principles. While those principles might change in the future, they are
enforced today and can explain certain technical limitations
BuckleScript has.

Lambda Representation

As pictured in the diagram above, BuckleScript is primarily based on the
Lambda representation of the OCaml compiler. While this representation
is quite rich, some information is lost from the upstream
representation. The patch to the OCaml compiler tries to enrich this
representation in a non-intrusive way (see next section).

Minimal Patch to the OCaml compiler

BuckleScript requires patches to the OCaml compiler. One of the main
reasons is to enrich the Lambda representation so that the generated
code is as nice as possible. A design goal is to keep those patches
minimal and useful for the OCaml compiler in general so that they can
later be integrated.

Note

A common question is to wonder why BuckleScript transpiles an OCaml
record value to a JavaScript array while a more intuitive representation
would be a JavaScript object. This technical decision is a direct
consequence of the above 2 design principles: the Lambda layer assumes
in a lot of places that a record value is an array and such modification
would be too large of a change to OCaml compiler.

Soundness

BuckleScript preserves the soundness of the OCaml language. Assuming the
FFI is correctly implemented, the type safety is preserved.

Minimal new symbol creation

In order to make the JavaScript generated code as close as possible to
the original OCaml core we thrive to introduce as few new symbols as
possible.

Runtime representation

Below is a description of how OCaml values are encoded in JavaScript,
the internal description means users should not rely on its actual
encoding (and it is subject to change). We recommend that you write
setter/getter functions to manipulate safely OCaml values from JavaScript.

For example, users should not rely on how OCaml list is encoded in
JavaScript; instead, the OCaml stdlib provides three functions: List.cons, List.hd and
List.tl. JavaScript code should only rely on those three functions.

Contributions

First, thanks for your interest in contributing!
There are plenty of ways to make contributions, like blogging, sharing your experience,
open sourcing your libraries using BuckleScript. They are all deeply appreciated.

OS: Mac/Linux (Note BuckleScript works on Windows, but the dev mode is not tested)

Below assume that you are working in jscomp directory, and that you’ve followed instructions from Minimal dependencies to build OCaml.

Contributing to bsb.exe

The build target is

make -C bin bsb.exe

So whenever you change files relevant to the build tool bsb.exe, try it and do some
test. If it works, send a pull request!

Note that for most binaries in BuckleScript, we also have a release mode, which will pack
all relevant files into a single file. This is important, since it will cut all our dev-dependencies,
so the user does not need install those tools.

You can verify it by typing

make snapshotml # see the diffs in jscomp/bin

But please don’t commit those changes in PR, it will cause painful merge conflicts.

Contributing to bsc.exe

make -C bin bsc.exe # build the compiler in dev mode

make libs # build all libs using the dev compiler

There is also a snapshot mode,

make snapshotml

This will actually snapshot your changes into a single ml file and used in npm distribution.
But please don’t commit those changes in PR, it will cause painful merge conflicts.

Contributing to the runtime

BuckleScript runtime implementation is currently a mix of OCaml and
JavaScript. (jscomp/runtime directory). The JavaScript code is defined
in the .ml file using the bs.raw syntax extension.

The goal is to implement the runtime purely in OCaml and you can help
contribute.

Each new PR should include appropriate testing.

Currently all tests are in jscomp/test directory and you should either
add a new test file or modify an existing test which covers the part of
the compiler you modified.

Comparisons

Js_of_ocaml is a popular compiler which compiles OCaml’s bytecode into JavaScript. It is the
inspiration for this project, and has already been under development for
several years and is ready for production. In comparison, BuckleScript,
while moving fast, is still a very young project. BuckleScript’s
motivation, like js_of_ocaml, is to unify the ubiquity of the
JavaScript platform and the truly sophisticated type system of OCaml,
however, there are some areas where we view things differently from
js_of_ocaml. We describe below, some of these differences, and also
refer readers to some of the original informal
discussions.

Js_of_ocaml focuses more on existing OCaml ecosystem(opam) while
BuckleScript’s major goal is to target npm

Js_of_ocaml and BuckleScript have slightly different runtime encoding
in several places, for example, BuckleScript encodes OCaml Array as JS
Array while js_of_ocaml requires its index 0 to be of value 0.

Both projects are improving quickly, so this can change in the future!

Appendix A: Library API documentation

There is a small library that comes with bs-platform, its documentation is
here.