Home

Awesome

Bazel rules for JavaScript

This ruleset is a high-performance Bazel integration for JavaScript, based on the pnpm package manager.

Many companies are successfully building with rules_js. If you're getting value from the project, please let us know! Just comment on our Adoption Discussion.

rules_js is just a part Aspect's monorepo developer platform:

Bazel compatibility

The ruleset is known to work with:

[!NOTE] Remote Execution (RBE) requires at least Bazel 6.0.

Known issues

Installation

Follow instructions from the release you wish to use: https://github.com/aspect-build/rules_js/releases.

To use a commit rather than a release, you can point at any SHA of the repo.

For example, to use commit abc123 with WORKSPACE:

  1. Replace url = "https://github.com/aspect-build/rules_js/releases/download/v0.1.0/rules_js-v0.1.0.tar.gz" with a GitHub-provided source archive like url = "https://github.com/aspect-build/rules_js/archive/abc123.tar.gz"
  2. Replace strip_prefix = "rules_js-0.1.0" with strip_prefix = "rules_js-abc123"
  3. Update the sha256. The easiest way to do this is to comment out the line, then Bazel will print a message with the correct value.

Note that GitHub source archives don't have a strong guarantee on the sha256 stability, see https://github.blog/2023-02-21-update-on-the-future-stability-of-source-code-archives-and-hashes

Usage

See the documentation in the docs folder.

Examples

Basic usage examples can be found under the examples folder.

Note that the examples also rely on code in the /WORKSPACE file in the root of this repo.

The e2e folder also has a few useful examples such as js_image_layer for containerizing a js_binary and js_run_devserver, a generic rule for running a devserver in watch mode with ibazel.

Larger examples can be found in our bazel-examples repository including:

Relationship to rules_nodejs

rules_js is an alternative to the build_bazel_rules_nodejs Bazel module and accompanying npm packages hosted in https://github.com/bazelbuild/rules_nodejs, which is now unmaintained. All users are recommended to use rules_js instead.

rules_js replaces some parts of bazelbuild/rules_nodejs and re-uses other parts:

LayerLegacyModern
Custom rulesnpm:@bazel/typescript, etc.aspect_rules_ts, etc.
Package manager and Basic rulesbuild_bazel_rules_nodejsaspect_rules_js
Toolchain and core providersrules_nodejsrules_nodejs

The common layer here is the rules_nodejs Bazel module, documented as the "core" in https://bazel-contrib.github.io/rules_nodejs/:

It is currently useful for Bazel Rules developers who want to make their own JavaScript support.

That's what rules_js does! It's a completely different approach to making JS tooling work under Bazel.

First, there's dependency management.

Then, there's how a Node.js tool can be executed:

There are trade-offs involved here, but we think the rules_js approach is superior for all users, especially those at large scale. Read below for more in-depth discussion of the design differences and trade-offs you should be aware of. Also see the slides for our Bazel eXchange talk

Design

The authors of rules_js spent four years writing and re-writing build_bazel_rules_nodejs. We learned a lot from that project, as well as from discussions with Rush maintainer @octogonz.

There are two core problems:

And there's a fundamental trade-off: make it fast and deterministic, or support 100% of existing use cases.

Over the years we tried a number of solutions and each end of the trade-off spectrum.

Installing third-party libraries

Downloading packages should be Bazel's job. It has a full featured remote downloader, with a content-address-cached (confusingly called the "repository cache"). We now mirror pnpm's lock file into starlark code, then use only Bazel repository rules to perform fetches and translate the dependency graph into Bazel's representation.

For historical context, we started thinking about this in February 2021 in a (now outdated) design doc and have been working through the details since then.

Running Node.js programs

Fundamentally, Bazel operates out of a different filesystem layout than Node. Bazel keeps outputs in a distinct tree outside of the sources.

Our first attempt was based on what Yarn PnP and Google-internal Node.js rules do: monkey-patch the implementation of require in NodeJS itself, so that every resolution can be aware of the source/output tree difference. The main downside to this is compatibility: many packages on npm make their own assumptions about how to resolve dependencies without asking the require implementation, and you can't patch them all. Unlike Google, most of us don't want to re-write all the npm packages we use to be compatible.

Our second attempt was essentially to run npm link before running a program, using a runtime linker. This was largely successful at papering over the filesystem layout differences without disrupting execution of programs. However, it required a lot of workarounds anytime a JS tool wanted to be aware of the input and output locations on disk. For example, many tools like react-scripts (the build system used by Create React App aka. CRA) insist on writing their outputs relative to the working directory. Such programs were forced to be run with Bazel's output folder as the working directory, and their sources copied to that location.

rules_js takes a better approach, where we follow that react-scripts-prompted workaround to the extreme. We always run JS tools with the working directory in Bazel's output tree. We can use a pnpm-style layout tool to create a node_modules under bazel-out, and all resolutions naturally work.

This third approach has trade-offs.