BuckleScript considers performance at install time, build time and run time as a serious feature. Here are some more info, and tips on keeping the build fast. Feel free to skip this section if you're just starting out.
Under the Hood
Bsb itself uses a build system under the hood, called Ninja. Ninja is like Make, but cross-platform, minimal, focuses in perf and destined to be more of a low-level building block than a full-blown build system. In this regard, Ninja's a great implementation detail for bsb.
Bsb reads into
bsconfig.json and generates the Ninja build file in
lib/bs. The file contains the low-level
bsc-related commands, namespacing rules, intermediate artifacts generation & others. It then runs
ninja for the actual build.
The JS Wrapper
bsb itself is a Node.js wrapper which takes care of some miscellaneous tasks, plus the watcher. The lower-level, watcher-less, true
bsb is called
bsb.exe. It can be located in the same directory as where
bsb is found:
> bsb -where /usr/local/lib/node_modules/bs-platform/lib
The path varies across systems.
If you don't need the watcher, you can run said
/usr/local/lib/node_modules/bs-platform/lib/bsb.exe. This side-steps the node.js startup time, which can be big (in the order of
bsb.exe build on a small project should be around
70ms. This doubles when you use the more common
bsb wrapper which comes with a watcher, which is practically faster since you don't manually run the build at every change (though you should opt for the raw
bsb.exe for programmatic usage, e.g. inserting bsb into your existing JS build pipeline).
No-op build (when no file's changed) should be around
15ms. Incremental rebuild (described soon) of a single file in a project is around
Cleaning the artifacts should be instantaneous.
We've stress-tested bsb on a big project of 10,000 files (2 directories, 5000 files each, first 5000 no dependencies, last 5000 10 dependencies on files from the former directory) using https://github.com/ocaml-omake/omake/blob/perf-test/performance/generate.ml, on Retina Macbook Pro Early 2015 (3.1 GHz Intel Core i7).
- No-op build of 10k files:
800ms(the minimum amount of time required to check the mtimes of 10k files).
- Clean build: <3 minutes.
- Incremental build: depends on the number of the dependents of the file. No dependent means
Note that bsb is a file-based build system. We don't do in-memory build, even if that speeds up the build a lot. In-memory builds risk memory leaks, out-of-memory errors and others. The bsb watcher, on the other hand, can stay open for days.
Bsb doesn't take whole seconds to run every time. The bulk of the build performance comes from incremental build, aka re-building a previously built project when a few files changed.
In short, thanks to OCaml, BuckleScript and bsb's architecture, we're able to only build what's needed. E.g. if
Speed Up Incremental Build
OCaml/BuckleScript/Reason uses the concept of interface files (
.rei) (or, equivalently, module signatures). Exposing only what you need naturally speeds up incremental builds. E.g. if you change a
.re file whose corresponding
.rei file doesn't expose the changed part, then you've reduced the amount of dependent files you have to rebuild.
Unfortunately, JS build systems are usually the bottleneck for building a JS project nowadays. Having part of the build finishes blazingly fast doesn't matter much if the rest of the build takes seconds or literally minutes. Here are a few suggestions:
- Convert more files into BuckleScript/Reason =). Fewer files going through fewer parts of the JS pipeline helps a ton.
- Careful with bringing in more dependencies: libraries, syntax transforms, build step loaders, etc. The bulk of these dragging down the editing & building experience might out-weight the API benefits they provide.
- Wait for us to create our own super fast linker (aka bundler).
Hot reloading refers to maintaining a dev server and listening to file changes in a way that allows the server to pipe some delta changes right into the currently running browser page. This provides a relatively fast iteration workflow while working in specific frameworks.
However, hot reloading is fragile by nature, and counts on the occasional inconsistencies (bad state, bad eval, etc.) and the heavy devserver setup/config being less of a hassle than the benefits it provides. We err on the side of caution and stability in general, and decided not to provide a built-in hot reloading yet. Note: you can still use the hot reloading facility provided by your JS build pipeline.