Angular ❤️ Bazel leaving Angular Labs
Tags: angular
Published: 2020-05-29 03:00:48

With Angular 9.0 released, including the new Ivy compiler and runtime, it's a good time to ask "what's next for Angular?". You might even ask "is Bazel coming next?". The short answer is: we are spinning off the Bazel effort to be independent of Angular, and to work for ALL frontend frameworks or Node.js backends. However, Bazel will never be the default build tool in Angular CLI, and we expect that most applications will not switch.

# What we've learned

We've been working on Angular with Bazel for a few years. As a quick refresher, Bazel is Google's build tool which is incremental - a small change results in a small re-build/test. It also lets your build steps use a shared cache and execute remotely in parallel on a farm of machines. It's the key to Google's ability to write large applications with thousands of engineers in a massive monorepo. For Angular to be usable internally at Google, the team must maintain Angular+Bazel for Google engineers.

Bazel has been available in Angular Labs as an opt-in preview for over a year, which gave us a chance to put some miles on it and learn from users. We do have several companies relying on this toolchain, and I've heard from a couple of them who plan to write up a case study about the benefits they've gotten.

One thing we've learned is that most Angular applications don't have the problem Bazel solves. For these applications, we don't want to introduce another complex bit of build system machinery - no matter how well we encapsulate it in the Angular CLI, it's a leaky abstraction and you'll probably encounter Bazel as an end user. For this reason, we don't intend to ever make it the default for Angular CLI users.

Another thing we've learned is that Bazel migration should happen in tiny steps. Any breaking change is a major obstacle for enterprise apps. Bazel can run any toolchain: while Bazel is responsible for calculating which build steps need to re-run, it doesn't care what those steps do. This means we have the option of migrating to Bazel while keeping the toolchain the same. For Angular developers, this means that every application which works with the CLI should work with Bazel.

We have tried a few approaches for that migration. First, in Angular 4 we introduced support for Google's Closure Compiler. This produces the smallest bundles, but it's an expert tool that requires a lot of work to adopt. Then we introduced a hybrid toolchain, using Google's approach for compiling TypeScript, Angular, Sass, and so on, but with Rollup as the bundler. This is a lot more usable, but still not always a drop-in replacement; migrating to Google's tooling still has some cost.

# Generalizing Bazel

So essentially, we had hoped to export the Google-internal toolchain, but it has some incompatibilities and even the smallest incompatibility is unacceptable. So late last year, we released a 1.0 stable version of Bazel's JavaScript support (rules_nodejs) with a novel feature: run *any* JS ecosystem tool under Bazel without any custom plugin code (Bazel calls these "rules").

I wrote about this in [Layering in Bazel for Web](https://dev.to/bazel/layering-in-bazel-for-web-389h). The TL;DR of that article: if you install some JS tooling of your choice, say

```sh
$ npm install mocha domino @babel/core @babel/cli @babel/preset-env http-server
```

you can now configure Bazel to use that toolchain:

```python
load("@npm//@babel/cli:index.bzl", "babel")
load("@npm//mocha:index.bzl", "mocha_test")
load("@npm//http-server:index.bzl", "http_server")
babel(
    name = "compile",
    outs = ["app.es5.js"],
    ...
)
http_server(
    name = "server",
    data = [
        "index.html",
        "app.es5.js",
    ],
    ...
)
mocha_test(
    name = "unit_tests",
    args = ["*.spec.js"],
    ...
)
```

What does this mean for Angular developers? Well, since Bazel now runs any JS ecosystem tooling, it should be able to run exactly the tooling you're using today. To explain how we do that, we need to pick apart the Angular CLI a little.

One simple model of Angular CLI is:

`ng` command -> Builder -> webpack

The `ng` command reads your `angular.json` file to find which Builder should be used. The Builder layer is internally called "Architect", so look in your `angular.json` for a key "architect", and you'll see mappings for what builder to use. For example, say you run `ng build`; the default builder is `@angular-devkit/build-angular:browser`.

> Read more about Angular CLI builders in the [docs](https://angular.io/guide/cli-builder)

This is actually a standalone program you could run outside of Angular CLI. The `@angular-devkit/architect-cli` package provides a command-line tool called architect. So instead of `ng build`, it's totally equivalent to peel off one layer of abstraction and run `npx architect frontend:build`.

Now we can put the parts together. If Bazel runs arbitrary JS tooling, and we know how to run individual steps of your current Angular build using Architect, then we can have Bazel run the `architect` CLI to exactly reproduce the build you're doing today. We have an [example app demonstrating this](https://github.com/bazelbuild/rules_nodejs/blob/master/examples/angular_bazel_architect/) - if you look at the `BUILD.bazel` file in the example you see that we just call the architect command when Bazel wants to build or test the Angular app.

# What does this mean for me?

First of all, if your team is satisfied with Angular CLI (or with Nx) then there is nothing for you to do. Bazel doesn't affect you and won't in the future.

What if you do have a scaling problem with today's tooling? This is software engineering, so there are trade-offs. By making this build system 100% compatible with all existing Angular applications, we have lost some of the incrementality guarantees of Bazel. If we just run architect, the most granular our build can be is to have a bunch of Angular libraries, and an app that consumes them. Then only the affected libraries need to be re-built after a change. This is very similar to what Nx does.

We think it's now possible to get the best possible on-ramp: first use Bazel to orchestrate your existing build steps, then customize the build graph to improve incrementality, starting from the slowest, most frequently-executed steps.

There's another interesting consequence of this approach. Angular is not special, any frontend or Node.js backend code can be built by Bazel today without any work required from the team. For this reason, our plan is to migrate the Bazel-specific APIs (the `@angular/bazel` package) out of Angular itself, and allow the Bazel effort to proceed totally decoupled from Angular teams goals. This gives the Bazel effort more autonomy, and means it immediately applies to React, Vue, Next.js, or any other framework/technology that provides a CLI.

As for who supports what: I'm now working on rules_nodejs but no longer on the Angular team, so our layering is quite clear. Angular team supports the CLI builders, so any bugs you observe from using them can be reported to Angular. The orchestration of these builders is owned by rules_nodejs and we'll do our best to support you. Note that the latter is an all-volunteer OSS project.

Here's a short summary of changes happening now:
- Angular is deprecating the `@angular/bazel` package for v10, see [the Pull Request](https://github.com/angular/angular/pull/37190)
- The Angular CLI builder is now in the [`@bazel/angular` package](https://www.npmjs.com/package/@bazel/angular) which is published from rules_nodejs
- There is no automatic Bazel configuration for now. We expect users will opt-in to using Bazel, so you'll have to configure it with WORKSPACE/BUILD files. There are a number of community-contributed tools for maintaining the config, such as [Evertz/bzlgen](https://github.com/Evertz/bzlgen)
- You no longer need the `ng_module` Bazel rule which was in `@angular/bazel`. The migration path is to use `ts_library` with an Angular plugin. See [the canonical Angular example](https://github.com/bazelbuild/rules_nodejs/tree/master/examples/angular)

We'll keep updating [the docs](https://github.com/bazelbuild/rules_nodejs/blob/master/docs/examples.md#angular) and you can follow along with this effort in the #angular channel on https://slack.bazel.build.

I'm super excited to continue rolling out Bazel's unique capabilities to the frontend developer community! Thank you so much to all the contributors and users who have shaped this solution.