Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: improve readability: extract transpiler/tsconfig to standalone #215

Merged
merged 1 commit into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
docs/*.md linguist-generated=true
docs/rules.md linguist-generated=true
docs/repositories.md linguist-generated=true
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ This is a high-performance alternative to the `@bazel/typescript` npm package fr
The `ts_project` rule here is identical to the one in rules_nodejs, making it easy to migrate.
Since rules_js always runs tools from the bazel-out tree, rules_ts naturally fixes most usability bugs with rules_nodejs:

- Freely mix generated `*.ts` and `tsconfig.json` files in the bazel-out tree with source files
- Fixes the need for any `rootDirs` settings in `tsconfig.json` as reported in https://github.com/microsoft/TypeScript/issues/37378
- "worker mode" for `ts_project` now shares workers across all targets, rather than requiring one worker pool per target
- Freely mix generated `*.ts` and `tsconfig.json` files in the bazel-out tree with source files
- Fixes the need for any `rootDirs` settings in `tsconfig.json` as reported in https://github.com/microsoft/TypeScript/issues/37378
- "worker mode" for `ts_project` now shares workers across all targets, rather than requiring one worker pool per target

rules_ts is just a part of what Aspect provides:

- _Need help?_ This ruleset has support provided by https://aspect.dev.
- See our other Bazel rules, especially those built for rules_js, linked from <https://github.com/aspect-build>

Known issues:
- Does not work with `--worker_sandboxing`.

- Does not work with `--worker_sandboxing`.

## Installation

Expand All @@ -33,6 +34,8 @@ If you'd like an example added, you can file a Feature Request.

## Usage

See the API documentation in [the docs/ folder](./docs/).

### From a BUILD file

The most common use is with the [`ts_project` macro](./docs/rules.md#ts_project) which invokes the
Expand Down
17 changes: 9 additions & 8 deletions docs/rules.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions docs/transpiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Transpiling TypeScript to JavaScript

The TypeScript compiler `tsc` can perform type-checking, transpilation to JavaScript, or both.
Type-checking can be slow, and is really only possible with TypeScript, not with alternative tools, because the type system is so rich that writing a correct checker is a massive undertaking.

Transpilation is mostly "erase the type syntax" and can be done well by a variety of tools.
`tsc` is regarded as the slowest option for transpilation, so it makes sense to divide the work between two tools.

`ts_project` supports this with the following design goals:

- The user should only need a single BUILD.bazel declaration of "this is my TypeScript code and its dependencies".
- Most developers have a working TypeScript Language Service in their editor, so they got type hinting before they ran the build tool.
- 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

## ts_project#transpiler

The `transpiler` attribute of `ts_project` lets you select which tool produces the JavaScript outputs. The default value of `None` means that `tsc` should do transpiling along with type-checking, as this is the simplest configuration without additional dependencies. However as noted above, it's also the slowest.

The `transpiler` attribute accepts a rule or macro with this signature:
`name, srcs, js_outs, map_outs, **kwargs`
where the `**kwargs` attribute propagates the tags, visibility, and testonly attributes from `ts_project`.

If you need to pass additional attributes to the transpiler rule, you can use a
[partial](https://github.com/bazelbuild/bazel-skylib/blob/main/lib/partial.bzl)
to bind those arguments at the "make site", then pass that partial to this attribute where it will be called with the remaining arguments.

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.

## Macro expansion

When a custom transpiler is used, then the `ts_project` macro expands to these targets:

- `[name]` - the default target which can be included in the `deps` of downstream rules.
Note that it will successfully build *even if there are typecheck failures* because invoking `tsc` is 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.ts` files) as the default output.
Building this target always causes the typechecker to run.
- `[name]_typecheck_test` - a [`build_test`] target which simply depends on the `[name]_typecheck` target.
This ensures that typechecking will be run under `bazel test` with [`--build_tests_only`].
- `[name]_typings` - internal target which runs the binary from the `tsc` attribute
- Any additional target(s) the custom transpiler rule/macro produces.
(For example, ome rules produce one target per TypeScript input file.)


[`build_test`]: https://github.com/bazelbuild/bazel-skylib/blob/main/rules/build_test.bzl
[`--build_tests_only`]: https://docs.bazel.build/versions/main/user-manual.html#flag--build_tests_only
94 changes: 94 additions & 0 deletions docs/tsconfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Configuring TypeScript

TypeScript provides "compiler options" which interact with how Bazel builds and type-checks code.

## General guidance

Keep a `tsconfig.json` file at the root of your TypeScript sources tree, as an ancestor of all TypeScript files.
This should have your standard settings that apply to all code in the package or repository.
This ensures that editors agree with rules_ts, and that you have minimal repetition of settings which can get diverged over time.

## Mirroring tsconfig settings

`ts_project` needs to know some of the values from tsconfig.json.
This is so we can mimic the semantics of `tsc` for things like which files are included in the program, and to predict output locations.

These attributes are named as snake-case equivalents of the tsconfig.json settings.
For example, [`outDir`](https://www.typescriptlang.org/tsconfig#outDir) is translated to `out_dir`.

The `ts_project` macro expands to include a validation action, which uses the TypeScript API to load the `tsconfig.json` file (along with any that it `extends`) and compare these values to attributes on the `ts_project` rule.
It produces [buildozer] commands to correct the BUILD.bazel file when they disagree.

[buildozer]: https://github.com/bazelbuild/buildtools/blob/master/buildozer/README.md

## Locations of tsconfig.json files

You can use a single `tsconfig.json` file for a repository.
Since rules_js expects files to appear in the `bazel-out` tree, the common pattern is:

1. In the `BUILD.bazel` file next to `tsconfig.json`, expose it using a `ts_config` rule:

```starlark
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")

ts_config(
name = "tsconfig",
src = "tsconfig.json",
visibility = [":__subpackages__"],
)
```

2. In child packages, set the `tsconfig` attribute of `ts_project` rules in subpackages to point to this rule.

```
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")

ts_project(
...
tsconfig = "//my_root:tsconfig",
)
```

You can also use nested `tsconfig.json` files. Typically you want these to inherit common settings from the parent, so use the [`extends`](https://www.typescriptlang.org/tsconfig#extends) feature in the `tsconfig.json` file. Then you'll need to tell Bazel about this dependency structure, so add a `deps` list to `ts_config` and repeat the files there.

## Inline (generated) tsconfig

The `ts_project#tsconfig` attribute accepts a dictionary.
If supplied, this dictionary is converted into a JSON file.
It should have a top-level `compilerOptions` key, matching the tsconfig file JSON schema.

Since its location differs from `tsconfig.json` in the source tree, and TypeScript
resolves paths in `tsconfig.json` relative to its location, some paths must be
written into the generated file:

- each file in srcs will be converted to a relative path in the `files` section.
- the `extends` attribute will be converted to a relative path

The generated `tsconfig.json` file can be inspected in `bazel-out`.

> Remember that editors need to know some of the tsconfig settings, so if you rely
> exclusively on this approach, you may find that the editor skew affects development.

You can mix-and-match values in the dictionary with attributes.
Values in the dictionary take precedence over those in the attributes,
and conflicts between them are not validated. For example, in

```starlark
ts_project(
name = "which",
tsconfig = {
"compilerOptions": {
"declaration": True,
"rootDir": "subdir",
},
},
out_dir = "dist",
root_dir = "other",
)
```

the value `subdir` will be used by `tsc`, and `other` will be silently ignored.
Both `outDir: dist` and `declaration: true` will be used.

As with any Starlark code, you could define this dictionary in a central location and load it as a symbol into your BUILD.bazel files.

1 change: 0 additions & 1 deletion examples/path_mappings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ ts_project(
name = "path_mappings",
srcs = ["foo.ts"],
declaration = True,
tsconfig = "tsconfig.json",
validate = False,
deps = _DEPS,
)
Expand Down
1 change: 0 additions & 1 deletion examples/project_references/app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ ts_project(
composite = True,
declaration = True,
extends = "//examples/project_references:tsconfig-base",
tsconfig = "tsconfig.json",
deps = ["//examples/project_references/lib_b"],
)
Loading