Developer diary: Day 1 — Bazel build system with monorepo and typescript

Fredrik Bonander
Red Flag developer blog
4 min readOct 30, 2018

As we grow as a company with an increasing number of developers the pain of maintaining multiple shared libraries grows. In an effort to try to mitigate this I figured I take a stab at trying to combine our shared typescript libraries in a monorepo. This will allow a simpler development flow when working with dependencies between shared libraries by having all code in the same place and stamp new releases of all libraries at the same time.

In addition to merging all libraries, that makes sense, into a single repository or monorepo I also want to add the Bazel build system in to the development flow.

Why Bazel? I like the idea with a language agnostic build system and it’s what is cool now.

First thing is to create a project structure, I ended up with something like this:

packages/
server/
src/
BUILD.bazel
package.tpl.json
event/
src/
BUILD.bazel
package.tpl.json
test/
server/
BUILD.bazel
event/
BUILD.bazel
tools/.bazelrc
BUILD.bazel
package.json
WORKSPACE

In packages we have two shared libraries server and event . Both of theses will be release as separate library. The unit test for each of these resides in test . In tools we will add some helper methods/rules Bazel.

When starting with with Bazel it can be really really confusing. Should I use BUILD.bazel or BUILD ? How do I update versions? How do I debug Bazel rules that fail? And so on… To be honest there’s still some magic happening that I still don’t fully understand.

I’ve take a lot of inspiration from the Angular project and the work of Alex Eagle.

So let’s dive in.

A good place to start is to have a look at the Building JavaScript Outputs guide on the Bazel site. It outlines some of the basics. When Bazel (and ibazel if you want a watcher) is installed we need to add a couple of things in the root folder:

In /.bazelrc we can setup a few default options for some commands that should be pushed to the repo so it’s shared with everyone that uses it:

In /BUILD.bazel we expose our tsconfg.json to Bazel. All files that Bazel needs in it’s rules needs to be exposed.

In WORKSPACE we defined our root workspace, which rules that should be loaded for all rules:

We need to add our first build tool script. This is used each time Bazel is used with the build command, in tools/bazel_stamp_vars.sh we add a environment variable that we can later use to stamp our package with with a release tag:

Now that we have our basic setup in place, let’s add some code in packages/server/src/index.ts and packages/server/src/calculator/calculator.ts :

In order to be able to build our package we add the following to packages/server/BUILD.bazel :

The important thing here is module_name which is the name it will be publish with on NPM or in your private npm-repository. Note: This property is undocumented for some reason.

Now we can build our code by calling the Bazel package with the path to BUILD.bazel and the package name, in our case shared-red-server-library:

bazel build //packages/server:shared-red-server-library

Before we publish our packages we needs some tests, in test/ we mirror our structure from packages ie:

test/
server/
src/
calculator/
calculator.spec.ts
BUILD.bazel
BUILD.bazel

We add an empty BUILD.bazel in the root of test to let Bazel know that there’s packages inside. Then we need add some rules for our test package test/server/BUILD.bazel :

Note: We use mocha for unit testing. Unfortunately `mocha_node_test` isn’t part of the official package. I’ve created my own based on a PR in github. I will add the code for this in the test repo for this article.

Now we write a unit test for our calculator in test/server/src/calculator/calculator.spec.ts . In our test we can import our Calculator form the package (the module_name we added before) instead from a relative path.

To run the tests we do:

bazel test //test/server:shared-red-server-library_test

Now to the fun part. Releasing the package to the world. In order to do this we add two new rules to our packages/server/BUILD.bazel so it looks like this:

The new npm_package creates the npm package. We can create a package as a gzipped file or publish it to npm. We also have a genrule that uses some jq magic to set our new version. It picks up the version from a file called volatile-status.txt that is created with our tools/bazel_stamp_vars.sh script. This solution is kind of hacky, but it’s what I managed to figure out.

We also need to a packages/server/package.tpl.json file that we use as template for our npm pakage:

To create a gzipped file with our new package we can do:

bazel run //packages/server:shared-red-server-library_package.pack

And then to publish a new version we create a new tag and then publish the package:

bazel test //test/server:shared-red-server-library_testnpm version patch -m "release: Throwing out v%s to the wolfs"bazel run //packages/server:shared-red-server-library_package.publishgit push --follow-tags

Now we can just redo the same steps for packages/event as for packages/server and publish at the same time. Like so:

bazel test //test/server:shared-red-server-library_test
bazel test //test/event:shared-red-event-library_test
npm version patch -m "release: Throwing out v%s to the wolfs"bazel run //packages/server:shared-red-server-library_package.publish
bazel run //packages/event:shared-red-event-library_package.publish
git push --follow-tags

I published all code for this here: https://github.com/RedFlagTeam/developer_diary_day1

..fredrik

--

--