Skip to content

Commit

Permalink
fix(typescript): fail the build when ts_project produces zero outputs
Browse files Browse the repository at this point in the history
Also fix a violation of this constraint in our tests.
That in turn exposed a subtle bug where the `files` block in the generated tsconfig.json would reference non-.ts inputs

BREAKING CHANGE:
any ts_project rule that produces no outputs must be fixed or removed

Fixes #2301
  • Loading branch information
Alex Eagle authored and alexeagle committed Dec 1, 2020
1 parent 815a3ca commit 3ca6cac
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 13 deletions.
20 changes: 14 additions & 6 deletions packages/typescript/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@ The TypeScript rules integrate the TypeScript compiler with Bazel.

This package provides Bazel wrappers around the TypeScript compiler.

At a high level, there are two alternatives provided: `ts_project` and `ts_library`.
At a high level, there are three alternatives provided: `tsc`, `ts_project`, `ts_library`.
This section describes the trade-offs between these rules.

`tsc` is the raw TypeScript compiler published by the team at Microsoft.
Like any npm package that exposes a binary, rules_nodejs will generate an `index.bzl` file allowing
you to run `tsc`.

To use it, add the load statement `load("@npm//typescript:index.bzl", "tsc")` to your BUILD file.
Then call it, using the [`npm_package_bin`](Built-ins#npm_package_bin) documentation.

The only reason to use raw `tsc` is if you want to compile an opaque directory of `.ts` files and cannot enumerate them to Bazel.
(For example if the `.ts` files are generated by some tool).
This will produce an opaque directory of `.js` file outputs, which you won't be able to individually reference.
Any other use case for `tsc` is better served by using `ts_project`.

`ts_project` simply runs `tsc --project`, with Bazel knowing which outputs to expect based on the TypeScript compiler options, and with interoperability with other TypeScript rules via a Bazel Provider (DeclarationInfo) that transmits the type information.
It is intended as an easy on-boarding for existing TypeScript code and should be familiar if your background is in frontend ecosystem idioms.
Any behavior of `ts_project` should be reproducible outside of Bazel, with a couple of caveats noted in the rule documentation below.

> We used to recommend using the `tsc` rule directly from the `typescript` project, like
> `load("@npm//typescript:index.bzl", "tsc")`
> However `ts_project` is strictly better and should be used instead.
`ts_library` is an open-sourced version of the rule we use to compile TS code at Google.
`ts_library` is an open-sourced version of the rule used to compile TS code at Google.
It should be familiar if your background is in Bazel idioms.
It is very complex, involving code generation of the `tsconfig.json` file, a custom compiler binary, and a lot of extra features.
It is also opinionated, and may not work with existing TypeScript code. For example:
Expand Down
30 changes: 25 additions & 5 deletions packages/typescript/internal/ts_project.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ def ts_project_macro(
write_tsconfig(
name = "_gen_tsconfig_%s" % name,
config = tsconfig,
files = srcs,
files = [s for s in srcs if _is_ts_src(s, allow_js)],
extends = Label("//%s:%s" % (native.package_name(), name)).relative(extends) if extends else None,
out = "tsconfig_%s.json" % name,
)
Expand Down Expand Up @@ -659,6 +659,26 @@ def ts_project_macro(

typings_out_dir = declaration_dir if declaration_dir else out_dir
tsbuildinfo_path = ts_build_info_file if ts_build_info_file else name + ".tsbuildinfo"
js_outs = []
map_outs = []
typings_outs = []
typing_maps_outs = []

if not emit_declaration_only:
js_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js"))
if source_map and not emit_declaration_only:
map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js.map"))
if declaration or composite:
typings_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts"))
if declaration_map:
typing_maps_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts.map"))

if not len(js_outs) and not len(typings_outs):
fail("""ts_project target "//{}:{}" is configured to produce no outputs.
Note that ts_project must know the srcs in advance in order to predeclare the outputs.
Check the srcs attribute to see that some .ts files are present (or .js files with allow_js=True).
""".format(native.package_name(), name))

ts_project(
name = name,
Expand All @@ -670,10 +690,10 @@ def ts_project_macro(
declaration_dir = declaration_dir,
out_dir = out_dir,
root_dir = root_dir,
js_outs = _out_paths(srcs, out_dir, root_dir, False, ".js") if not emit_declaration_only else [],
map_outs = _out_paths(srcs, out_dir, root_dir, False, ".js.map") if source_map and not emit_declaration_only else [],
typings_outs = _out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts") if declaration or composite else [],
typing_maps_outs = _out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts.map") if declaration_map else [],
js_outs = js_outs,
map_outs = map_outs,
typings_outs = typings_outs,
typing_maps_outs = typing_maps_outs,
buildinfo_out = tsbuildinfo_path if composite or incremental else None,
tsc = tsc,
link_workspace_root = link_workspace_root,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
load("//packages/typescript:index.bzl", "ts_project")
# Use the ts_project rule directly, not the wrapper macro. We don't want checking for empty outs.
load("//packages/typescript/internal:ts_project.bzl", "ts_project")

ts_project(
name = "tsconfig-a",
srcs = ["a.d.ts"],
tsconfig = ":tsconfig-a.json",
)

# Just verify that the a.d.ts file is transitively propagated
ts_project(
name = "tsconfig-b",
srcs = [],
tsconfig = ":tsconfig-b.json",
deps = ["tsconfig-a"],
)

ts_project(
name = "tsconfig-c",
srcs = ["c.ts"],
js_outs = ["c.js"],
tsconfig = ":tsconfig-c.json",
deps = ["tsconfig-b"],
)
9 changes: 8 additions & 1 deletion packages/typescript/test/ts_project/json/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ ts_project(
ts_project(
name = "tsconfig-decl-only",
srcs = SRCS,
emit_declaration_only = True,
tsconfig = {
"compilerOptions": {
"declaration": True,
"emitDeclarationOnly": True,
"resolveJsonModule": True,
"types": [],
},
},
)

nodejs_test(
Expand Down

0 comments on commit 3ca6cac

Please sign in to comment.