rules_typescript¶
TypeScript programs are source-native by default: ts_compile and ts_test keep tsgo validation and emit no application JavaScript or declarations. Opt in with emit = True where a JavaScript-only runtime or declaration consumer requires built files; Gazelle follows those consumer requirements.
An opinionated Bazel ruleset for TypeScript, built around Oxc and tsgo. It builds TypeScript packages and provides a source-built dev server. For a different build model, see aspect-build/rules_ts (comparison).
Rust and Go do the work: Oxc compiles an ES-module program and tsgo a CommonJS-shaped one; tsgo type-checks. The default dev server is oj 0.2.16. Vite remains an explicit option. Gazelle writes the BUILD files. Write .ts, run Gazelle, bazel build //.... The build reads no node_modules/. No system Node. Just Bazelisk.
Coming from an existing TypeScript monorepo, the
Quick Start is the whole path: four root files,
then bazel run //:gazelle. Install and
Quick Example below are the short version.
Key Ideas¶
- Oxc compiles an ES-module program — Rust-based TypeScript/JSX transformer:
.js+.js.mapper file, and.d.tstoo under--//ts:declarations=oxc. A program whosemoduleis CommonJS-shaped is tsgo's emit — see The Module Format. - tsgo validates every program — The Go port of TypeScript checks source-mode and emitted programs. With
emit = True, it is also the default declaration emitter. Unmodified TypeScript compiles: no export annotations required, and the.d.tsare whattscwould produce. tsgo runs as a build action, not a separatetsc --noEmitjob, so type errors failbazel build; the declarations are real outputs, and a package type-checks against what its dependency emits. - The dev server is swappable —
ts_dev_server(server = ...)takes any target providingDevServerInfo. oj 0.2.16 is the default; select@rules_typescript//vite:dev_serverfor Vite. Each server declares the config fields it does not read, so a target depending on one fails at analysis time naming the field and the server.ts_dev_serverhands the source tree to the server; HMR is the server's, not a rebuild. See Bringing your own server. - Isolated declarations — annotate the exports, build under
--//ts:declarations=oxc, and Oxc emits the.d.tssyntactically. A dependent waits for a per-file transform rather than for tsgo's declaration emit, which shortens a deep dependency chain substantially. Opt-in, per build. See Cost of each mode. - Gazelle generates the BUILD files — one package per
tsconfig.json, its sources and deps read off tsgo's own listing of the program. The tsconfig, lockfile and manifest own program membership; protobuf product identities are explicit. - Direct dependencies — a source may import only what a direct dep provides. A declaration arriving through another dep's own deps does not satisfy an import; the build names the file, the specifier and the label to add, and Gazelle writes it.
- How npm packages are fetched — one Bazel repository per package, fetched on demand, behind a
@npmalias hub. A target's npm cost is its own closure, not the whole lockfile, and build actions use Bazel’s npm store. Gazelle and editor tools can read the checkout’s installednode_modules/. The store is pnpm's: one cached tree per resolution (name, version and peer set), each importer'snode_moduleslinks into it, and a target resolves through its importers. - The editor reads a generated
tsconfig.json—ts_refresh_tsconfigwrites a checked-intsconfig.jsonout of the build graph, so tsserver, a plaintscrun and a coding agent's language server resolve what Bazel resolves. See IDE Setup. - Only Bazelisk required — Bazel fetches Node.js, the Rust and Go toolchains, and pnpm. It builds Oxc and the four Go tools from source. pnpm installs the checkout that Gazelle lists. Prebuilt Go tools require explicit configuration.
Install¶
rules_typescript is not on the Bazel Central Registry yet, so pin it from git
with git_override. Add to MODULE.bazel:
bazel_dep(name = "rules_typescript", version = "0.2.0")
git_override(
module_name = "rules_typescript",
remote = "https://github.com/mikn/rules_typescript.git",
commit = "REPLACE_WITH_A_COMMIT_SHA_FROM_MAIN",
)
register_toolchains("@rules_typescript//ts/toolchain:all")
bazel_dep(name = "gazelle", version = "0.47.0")
Pre-1.0, any commit may break the API with no deprecation window. Every break is listed in the changelog with the edit it requires; the versioning policy has the rest.
bazel_dep keeps its version attribute: bzlmod requires it and ignores the
value while an override is in place. The quickstart covers the
archive_override and local_path_override alternatives;
the plain bazel_dep line resolves on its own once a version reaches the BCR.
Add to .bazelrc:
build --incompatible_strict_action_env
build --nolegacy_external_runfiles
build --output_groups=+_validation
No @rules_rust flag belongs here. rules_typescript does not expose that repository, so Bazel cannot resolve the label from your repository and
fails the invocation. See
Troubleshooting.
Quick Example¶
Write TypeScript. Export annotations are optional under tsgo:
Generate BUILD files and build:
Gazelle produces src/BUILD.bazel for the tsconfig.json in src/: one
ts_compile per program, named after the directory, and a ts_config over
the file. See What Gazelle writes.
ts_compile(
name = "src",
srcs = ["math.ts"],
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
visibility = ["//visibility:public"],
)
Supported Platforms¶
| Platform | Status |
|---|---|
| Linux x86_64 | Supported |
| Linux ARM64 | Supported |
| macOS x86_64 | Supported |
| macOS ARM64 | Supported |
| Windows x86_64 | Not supported |
Windows is not supported right now; it may be considered in the future. What runs there today: Compatibility.
Documentation¶
- Quick Start — new project or migrating an existing one
- Isolated Declarations — the opt-in throughput mode
- IDE Setup — the generated
tsconfig.json, the tsserver plugin, and what a coding agent's language server needs - npm Dependencies — pnpm lockfile integration
- Testing with vitest —
ts_test, the vitest config layers, coverage - Bundling —
ts_binarywith aBundlerInfobundler - Dev Server — oj by default, with optional Vite
- Tailwind v4 — through
vite_config, under the dev server - Monorepo Layout — one package per tsconfig.json, cross-package deps
- Troubleshooting — the error messages, by message text
- Benchmark — Matching work, cache states and invocation evidence
- Gazelle Reference — what a run reads and writes,
# keep - Rules Reference — all rule attributes and providers
- Migrating from rules_ts — where the other ruleset is the better choice
- Compatibility — Bazel and platform support, the Vite/vitest versions the tests exercise, and the pre-1.0 policy
License¶
MIT