The BuckleScript compilation model is similar to OCaml native compiler.
If b.ml
depends on a.ml
, you have to compile a.ml
and a.mli
first.
Note
|
The technical reason is that BuckleScript will generate intermediate
files with the extension |
BuckleScript provides a native build tool on top of Google’s ninja-build. It is designed for a fast feedback loop (typically 100ms feedback loop) and works cross platform.
bsb
is a schema based build tool. The schema is
{schema}[available]. It is strongly recommended to check out the {schema}[schema]
after you finish reading the tutorials below.
If your editor supports JSON schema validation and auto-completion like VSCode, below is a configuration to help you get auto-completion and validation for free:
{
"json.schemas": [
{
"fileMatch": [
"bsconfig.json"
],
"url" : "file:///path/to/bucklescript/docs/docson/build-schema.json" // (1)
}
],
// ....
}
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:
{
"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
.
The directory layout (after building) would be
. ├── lib │ ├── bs │ │ ├── src │ │ └── test │ ├── js │ │ ├── src │ │ └── test │ ├── amdjs // (1) │ │ ├── src │ │ └── test │ ├── es6 // (2) │ │ ├── src │ │ └── test │ └── ocaml ├── scripts ├── src └── test
-
Will generate AMDJS modules if flags are turned on
-
Will generate ES6 modules if flags are turned on
We wrap bsb.exe
as bsb
so that it will work across different platforms.
bsb // (1)
bsb -w // (2)
-
Do the plain build
-
Do the plain build and watch
List your dependency in bs-dependencies
and install it via npm install
as below:
{
"name": "bs-string",
"version": "0.1.3",
"bs-dependencies": [
"bs-mocha" // (1)
],
"sources": [
.. .
],
"generate-merlin" : false // (2)
}
-
Yet another BuckleScript dependency
-
If true (default) bsb will generate merlin file for you
{
"dependencies": {
"bs-mocha": "0.1.5"
},
...
}
After your npm install
,
bsb -clean-world // (1)
bsb -make-world // (2)
bsb -w // (3)
-
Clean the binary artifact of current build and your dependency
-
Build dependencies and lib itself
-
Start watching the project and whenever your changes are made,
bsb
will rebuild incrementally
You can also streamline the three commands as below:
bsb -clean-world -make-world -w
Note sometimes, you have directories which are just tests that you don’t need your dependency to build. In that case you can mark it as dev only:
{
"sources" : {
"dir" : "test",
"type" : "dev" // (1)
}
}
-
directory
test
is in dev mode, it will not be built when used as a dependency
Below is a json configuration for the bucklescript-tea: the Elm artchitecture in BuckleScript
{
"name": "bucklescript-tea",
"version": "0.1.3",
"sources": [
"src", // (1)
{
"dir": "test",
"type": "dev" // (2)
}
]
}
-
Source directory, by default it will export all units of this directory to users.
-
Dev directory, which will only be useful for developers of this project.
{
"name": "bucklescript-tea",
"version": "0.1.3",
"description": "TEA for Bucklescript",
"scripts": {
"build": "bsb",
"watch": "bsb -w",
"test": "echo \"Error: no test specified\" && exit 1"
},
"peerDependencies": {
"bs-platform": "^1.7.0" // (1)
}
}
-
Here we list
bs-platform
as a peer dependency so that different repos shares the same compiler.
Now, if we have a repo bucklescript-have-tea which depends on bucklescript-tea
, its configurations are as below:
{
"name" : "bucklescript-have-tea",
"sources" : "src",
"bs-dependencies": [
"bucklescript-tea"
]
}
{
"name" : "bucklescript-have-tea",
"version" : "0.1.0",
"dependencies" : { "bucklescript-tea" : "^0.1.2" }, // (1)
"peerDependencies" : { "bs-platform" : "^1.7.0" } //(2)
}
-
List
bucklescript-tea
as dependency -
List
bs-platform
as peer dependency
Suppose you are in bucklescript-have-tea
top directory,
npm install // (1)
npm install bs-platform (2)
./node_modules/.bin/bsb -clean-world -make-world -w (3)
-
Install the dependencies
-
Install peer dependencies
-
On Windows, it would be
.\node_modules\.bin\bsb -clean-world -make-world -w
You can also change the package-specs
to have another module format, for example, tweak your bsconfig.json
:
{
... ,
"package-specs" : ["amdjs", "commonjs"],
...
}
Rerun the command
bsb -clean-world -make-world
You will get both commonjs
and amdjs
support. In the end, we suggest you check out the schema and enjoy the build!
OCaml treats file names as modules, so that users can only have unique file names in a project, BuckleScript solves the problem by scoping all modules by package.
Below is the bsconfig.json
for liba
, libb
(they share the same configuration in this example)
{
"name": "liba",
"version": "0.1.0",
"sources": "src",
"namespace": true
}
Now you have a library to use them
{
"name": "namespace",
"version": "0.1.0",
"sources": "src",
"bs-dependencies": [
"liba",
"libb"
]
}
Since liba
and libb
are configured using namespace, to use them in source code, it would be like
let v =
(Liba.Demo.v + Libb.Demo.v)
Note
|
In the same package, everything works the same whether it uses namespace or not, it only affects people who use your library |
When user has an existing JS project, it makes sense to output the JS file in the same directory as vanilla JS,
so the schema added a key called in-source
so that the JS file is generated next to ML file.
Example:
{
...
"package-specs" : [{
"module": "commonjs",
"in-source": true
}]
}
Warning
|
|
BuckleScript distribution has bsdep.exe
which has the same interface as ocamldep
Here is a simple Makefile to get started:
OCAMLC=bsc.exe # (1)
OCAMLDEP=bsdep.exe # (2)
SOURCE_LIST := src_a src_b
SOURCE_MLI = $(addsuffix .mli, $(SOURCE_LIST))
SOURCE_ML = $(addsuffix .ml, $(SOURCE_LIST))
TARGETS := $(addsuffix .cmj, $(SOURCE_LIST))
INCLUDES=
all: $(TARGETS)
.mli:.cmi
$(OCAMLC) $(INCLUDES) $(COMPFLAGS) -c $<
.ml:.cmj:
$(OCAMLC) $(INCLUDES) $(COMPFLAGS) -c $<
-include .depend
depend:
$(OCAMLDEP) $(INCLUDES) $(SOURCE_ML) $(SOURCE_MLI) > .depend
-
bsc.exe is the BuckleScript compiler
-
ocamldep executable is part of the OCaml compiler installation
In theory, people need run make depend && make all
. make depend
will calculate dependencies
while make all
will do the job.
However in practice, people used to a file watch service, such as watchman for example, will need the JSON configuration:
[
"trigger", ".", {
"name": "build",
"expression": ["pcre", "(\\.(ml|mll|mly|mli|sh|sh)$|Makefile)"], // (1)
"command": ["./build.sh"],
"append_files" : true
}
]
-
whenever such files are changed, it will trigger the
command
field to be run
make -r -j8 all (1)
make depend // (2)
-
build
-
update the dependency
Now in your working directory, type watchman -j < build.json
and enjoy the lightning build speed.
It is quite common for programmers to use some preprocessors to generate some boilerplate code during developement.
Note preprocessors can be classified in two categories: one is system-dependent which must be delayed until running on the target machines and the other is system-independent (such as lex, yacc, m4, re2c, etc.) which could be executed anytime.
BuckleScript has built in support for conditional compilation for the second category. Since it is system-independent, we ask users to always generate such code and check it in before shipping, which would cut the dependencies for end users.
A typical example would be like this
{
"generators" : [
{ "name" : "ocamlyacc" ,
"command" : "ocamlyacc $in" }
],
...,
"sources" : {
"dir" : "src",
"generators" : [
{
"name" : "ocamlyacc",
"edge" : ["test.ml", "test.mli", ":", "test.mly"]
}
]
}
}
Note ocamlyacc
will generate test.ml
and test.mli
in the same directory with test.mly
. The developer should check in the generated file since then users would not need run ocamlyacc
again. This would apply to menhir
as well.
When a developer is working on their current project, bsb
will track the dependencies between test.ml
and test.mly
properly. When released
as a package, bsb
will cut such a dependency, so that users will
only need the generated test.ml
. To help test such behavior, developers can set it manually:
{
...,
"cut-generators" : true
}
With this setting bsb
will not regenerate test.ml
whenever test.mly
changes.