Skip to main content
rbs builds Node.js and TypeScript projects without requiring Node, npm, or yarn on your machine. The workspace declares which Node.js version it uses, rbs downloads that exact toolchain, and every target builds and runs against it — the same way on every developer’s machine and in CI. Three rules cover most Node.js work: For browser apps (React, bundling, dev servers), see Web Apps. Component testing with vitest_test is covered at the end of this page.

Set up the toolchain

Declare the Node.js toolchain once in your workspace’s WORKSPACE.rbs:
rbs downloads the official Node.js release for your platform on first use and caches it. Supported versions include 22.15.1 (default), 22.11.0, 20.19.6, 20.11.0, and 18.19.0.

Declare npm dependencies

rbs resolves npm packages itself, directly from the npm registry — there is no package.json, no npm install, and no npm/yarn CLI involved. Each package your workspace uses is declared in WORKSPACE.rbs:
Transitive dependencies are resolved automatically, exactly like npm would: rbs walks the dependency tree, resolves semver ranges, and downloads everything in parallel into a shared cache. version is usually an exact pin, but semver ranges ("^4.18.0") and "latest" also work — exact pins are recommended for reproducibility. To declare several packages at once:

The lockfile

The first resolution writes rbs.lock at the workspace root, pinning every resolved package (including transitives). Later builds resolve instantly from the lockfile instead of hitting the registry.
Commit rbs.lock to version control. It makes dependency resolution reproducible and fast for everyone on the branch.

Referencing dependencies from targets

In a BUILD.rbs file, a target’s deps can reference npm packages two ways:
Both forms resolve to the same cached package. The @external://package:version form is explicit about the version at the point of use; the :name_repo form points back at the nodejs_repository declaration.
When a target runs, your code sees an ordinary node_modules directory and imports resolve exactly as in any Node.js project — rbs stages and shares the packages behind the scenes.

nodejs_binary

Builds a runnable Node.js program with a hermetic launcher: the target carries its own Node.js toolchain and dependencies, so rbs run works on any machine with no setup.
Run these commands from the directory containing the target’s BUILD.rbs, after adding the referenced source files and dependencies. Choose either run command; stop a running server with Ctrl+C before starting another on the same port.
  • rbs build :server — builds the server target; expect a successful build summary, without starting the server.
  • rbs run :server — builds and starts the server; expect the messages printed by your server.js. A running server keeps the terminal occupied until you stop it.
  • rbs run :server --watch — --watch rebuilds and restarts after relevant files change; expect an initial server start followed by rebuild/restart messages when you save a change. Stop watching with Ctrl+C.

Attributes

nodejs_library

Groups sources into a reusable unit. Libraries record their own npm dependencies, and any binary or test that depends on the library gets those packages automatically.
Code in a consuming target imports library files by the library’s target name followed by the file’s path within the library:

Attributes

TypeScript

TypeScript works out of the box in all three rules: when srcs contain .ts or .tsx files, rbs compiles them with tsc before staging. The only requirement is that the workspace declares the typescript package as a host dependency:
The default compiler options target ES2020 with CommonJS modules, strict mode, and esModuleInterop enabled. Override individual options per target:
Or point the target at an existing config with tsconfig = "tsconfig.json".
rbs also generates a TypeScript configuration for your editor (under .rbs/lsp/) so imports of npm packages and workspace libraries resolve in the IDE without any manual setup. This file is regenerated by builds — treat it as read-only. If you need different compiler behavior, set ts_compiler_options or tsconfig on the target rather than editing generated files.

Testing with nodejs_test

nodejs_test runs your tests with one of two runners:
  • jest (default) — real Jest: describe/it/expect suites, with Jest-collected coverage.
  • node — plain Node.js: the test file is a self-executing script (e.g. using assert) that exits non-zero on failure; coverage is collected with NYC (Istanbul).
First, install the test dependencies once in WORKSPACE.rbs:
Then declare test targets:
These are alternatives for choosing test scope or watching changes:
  • rbs test :calculator_test — runs the named test target; expect its test results and an overall pass/fail summary.
  • rbs test :all — runs test targets in the current package; expect results for those targets, rather than every package in the workspace.
  • rbs test //... — runs test targets across the workspace; expect a combined pass/fail summary. This may take longer than testing one package.
  • rbs test :calculator_test -w — -w is short for --watch; expect an initial test run and another run after relevant files change. The watcher stays open until Ctrl+C.
With test_runner = "node", name your test files *.test.js so the coverage tool excludes them from the coverage report.

Coverage gates

Set minimum coverage thresholds on the target and enforce them with rbs coverage — the run fails if any metric drops below its threshold:
  • rbs coverage :calculator_coverage_test — runs coverage for this target and checks its thresholds; expect coverage percentages and a pass/fail result. A passing test suite can still fail a coverage threshold.
  • rbs coverage //... — runs coverage across the workspace’s test targets; expect coverage results and threshold checks for the selected targets.
Coverage is real, measured lcov data — local library dependencies of the test are instrumented too, so the numbers reflect everything the tests actually exercise.

Attributes

Component testing with vitest_test

For React/DOM component tests, use vitest_test from the web-vite ruleset. It runs real Vitest with a jsdom environment by default, transforms TS/TSX with Vitest’s own pipeline (no separate compile step), and collects V8 lcov coverage. Install its dependencies once in WORKSPACE.rbs:
Then declare test targets — srcs must list both the test files (named *.test.* or *.spec.*) and the sources they exercise:
  • rbs test :counter_test — runs the component suite; expect Vitest test results and an overall pass/fail summary. This command does not enforce the coverage thresholds.
  • rbs coverage :counter_test — runs the suite with coverage and enforces its thresholds; expect coverage percentages and a pass/fail result for the configured gates.
@testing-library/react v16 requires @testing-library/dom as a peer dependency — list it explicitly in deps, as above.

Attributes

To test components that import a shared workspace library, declare the library under the same import scope the app rules use:
The tests can then import { Button } from '@acme/ui' just like the application code does. See Web Apps for how libraries and apps are wired together.