The TypeScript compiler
Type-checking is typically slow, and is really only possible with TypeScript, not with alternative tools.
Transpilation is mostly "erase the type syntax" and can be done well by a variety of tools.
ts_project allows us to split the work, with the following design goals:
- The user should only need a single BUILD.bazel declaration: "these are my TypeScript sources and their dependencies".
- Most developers have a working TypeScript Language Service in their editor, so they got type hinting before they ran
- Development activities which rely only on runtime code, like running tests or manually verifying behavior in a devserver, should not need to wait on type-checking.
- Type-checking still needs to be verified before checking in the code, but only needs to be as fast as a typical test.
Read more: https://blog.aspect.dev/typescript-speedup
transpiler attribute of
Starting in rules_ts 2.0, we require you to select one of these, as there is no good default for all users.
SWC is a fast transpiler, and the authors of rules_ts recommend using it.
This option results in the fastest development round-trip time, however it may have subtle
See https://github.com/aspect-build/rules_ts/discussions/398 for known issues.
To switch to SWC, follow these steps:
Install a recent release of rules_swc
swc. You can automate this by running:
npx @bazel/buildozer 'fix movePackageToTop' //...:__pkg__
npx @bazel/buildozer 'new_load @aspect_rules_swc//swc:defs.bzl swc' //...:__pkg__
In the simplest case you can skip passing attributes to swc (such as an
.swcrcfile). You can update your
ts_projectrules with this command:
npx @bazel/buildozer 'set transpiler swc' //...:%ts_project
However, most codebases do rely on configuration options for SWC. First, Synchronize settings with tsconfig.json to get an
.swcrcfile, then use a pattern like the following to pass this option to
transpiler = partial.make(swc, swcrc = "//:.swcrc"),
Cleanup unused load statements:
npx @bazel/buildozer 'fix unusedLoads' //...:__pkg__
tsc can do transpiling along with type-checking.
This is the simplest configuration without additional dependencies. However, it's also the slowest.
Note that rules_ts used to recommend a "Persistent Worker" mode to keep the
tscprocess running as a background daemon, however this introduces correctness issues in the build and is no longer recommended. As of rules_ts 2.0, the "Persistent Worker" mode is no longer enabled by default.
To choose this option for a single
transpiler = "tsc".
You can run
npx @bazel/buildozer 'set transpiler "tsc"' //...:%ts_project to set the attribute
If you use the default value
transpiler = None, rules_ts will print an error.
You can simply disable this error for all targets in the build, behaving the same as rules_ts 1.x.
Just add this to `/.bazelrc``:
# Use "tsc" as the transpiler when ts_project has no `transpiler` set.
transpiler attribute accepts any rule or macro with this signature:
(name, srcs, **kwargs)
**kwargs attribute propagates the tags, visibility, and testonly attributes from
See the examples/transpiler directory for a simple example using Babel, or https://github.com/aspect-build/bazel-examples/tree/main/ts_project_transpiler for a more complete example that also shows usage of SWC.
If you need to pass additional attributes to the transpiler rule such as
out_dir, you can use a
to bind those arguments at the "make site", then pass that partial to this attribute where it will be called with the remaining arguments.
The transpiler rule or macro is responsible for predicting and declaring outputs.
If you want to pre-declare the outputs (so that they can be addressed by a Bazel label, like
then you should use a macro which calculates the predicted outputs and supplies them to a
on the rule.
You may want to create a
ts_project macro within your repository where your choice is setup,
load() from your own macro rather than from
transpiler is used, then the
ts_project macro expands to these targets:
[name]- the default target which can be included in the
depsof downstream rules. Note that it will successfully build even if there are typecheck failures because invoking
tscis not needed to produce the default outputs. This is considered a feature, as it allows you to have a faster development mode where type-checking is not on the critical path.
[name]_typecheck- provides typings (
.d.tsfiles) as the default output. Building this target always causes the typechecker to run.
build_testtarget which simply depends on the
[name]_typechecktarget. This ensures that typechecking will be run under
[name]_typings- internal target which runs the binary from the
- Any additional target(s) the custom transpiler rule/macro produces. (For example, some rules produce one target per TypeScript input file.)
Avoid eager type-checks via
A common pattern to link monorepo packages is to use the
When typings files (
*.d.ts) are included in the
npm_package, this can cause the type-checker to run, even for a development round-trip that shouldn't need it.
ts_project(name = a) --foo.d.ts--> npm_package ---> npm_link_package ---> ts_project(name = b) ---> js_test
In this diagram, we'd like to be able to change the TypeScript sources of
a and then re-run the test target, without waiting for type-checking.
foo.d.ts is declared as an input to the
npm_package rule, Bazel needs to produce it.
To solve this, you can add the flag
--@aspect_rules_js//npm:exclude_declarations_from_npm_packages to your
Use this flag only for local development! You can add a line to your
.bazelrc to make this easier to type, for example:
# Run bazel --config=dev to choose these options: