Skip to content

Commit 3ca6cac

Browse files
Alex Eaglealexeagle
authored andcommitted
fix(typescript): fail the build when ts_project produces zero outputs
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
1 parent 815a3ca commit 3ca6cac

4 files changed

Lines changed: 54 additions & 13 deletions

File tree

packages/typescript/install.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,26 @@ The TypeScript rules integrate the TypeScript compiler with Bazel.
66

77
This package provides Bazel wrappers around the TypeScript compiler.
88

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

12+
`tsc` is the raw TypeScript compiler published by the team at Microsoft.
13+
Like any npm package that exposes a binary, rules_nodejs will generate an `index.bzl` file allowing
14+
you to run `tsc`.
15+
16+
To use it, add the load statement `load("@npm//typescript:index.bzl", "tsc")` to your BUILD file.
17+
Then call it, using the [`npm_package_bin`](Built-ins#npm_package_bin) documentation.
18+
19+
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.
20+
(For example if the `.ts` files are generated by some tool).
21+
This will produce an opaque directory of `.js` file outputs, which you won't be able to individually reference.
22+
Any other use case for `tsc` is better served by using `ts_project`.
23+
1224
`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.
1325
It is intended as an easy on-boarding for existing TypeScript code and should be familiar if your background is in frontend ecosystem idioms.
1426
Any behavior of `ts_project` should be reproducible outside of Bazel, with a couple of caveats noted in the rule documentation below.
1527

16-
> We used to recommend using the `tsc` rule directly from the `typescript` project, like
17-
> `load("@npm//typescript:index.bzl", "tsc")`
18-
> However `ts_project` is strictly better and should be used instead.
19-
20-
`ts_library` is an open-sourced version of the rule we use to compile TS code at Google.
28+
`ts_library` is an open-sourced version of the rule used to compile TS code at Google.
2129
It should be familiar if your background is in Bazel idioms.
2230
It is very complex, involving code generation of the `tsconfig.json` file, a custom compiler binary, and a lot of extra features.
2331
It is also opinionated, and may not work with existing TypeScript code. For example:

packages/typescript/internal/ts_project.bzl

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ def ts_project_macro(
597597
write_tsconfig(
598598
name = "_gen_tsconfig_%s" % name,
599599
config = tsconfig,
600-
files = srcs,
600+
files = [s for s in srcs if _is_ts_src(s, allow_js)],
601601
extends = Label("//%s:%s" % (native.package_name(), name)).relative(extends) if extends else None,
602602
out = "tsconfig_%s.json" % name,
603603
)
@@ -659,6 +659,26 @@ def ts_project_macro(
659659

660660
typings_out_dir = declaration_dir if declaration_dir else out_dir
661661
tsbuildinfo_path = ts_build_info_file if ts_build_info_file else name + ".tsbuildinfo"
662+
js_outs = []
663+
map_outs = []
664+
typings_outs = []
665+
typing_maps_outs = []
666+
667+
if not emit_declaration_only:
668+
js_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js"))
669+
if source_map and not emit_declaration_only:
670+
map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js.map"))
671+
if declaration or composite:
672+
typings_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts"))
673+
if declaration_map:
674+
typing_maps_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts.map"))
675+
676+
if not len(js_outs) and not len(typings_outs):
677+
fail("""ts_project target "//{}:{}" is configured to produce no outputs.
678+
679+
Note that ts_project must know the srcs in advance in order to predeclare the outputs.
680+
Check the srcs attribute to see that some .ts files are present (or .js files with allow_js=True).
681+
""".format(native.package_name(), name))
662682

663683
ts_project(
664684
name = name,
@@ -670,10 +690,10 @@ def ts_project_macro(
670690
declaration_dir = declaration_dir,
671691
out_dir = out_dir,
672692
root_dir = root_dir,
673-
js_outs = _out_paths(srcs, out_dir, root_dir, False, ".js") if not emit_declaration_only else [],
674-
map_outs = _out_paths(srcs, out_dir, root_dir, False, ".js.map") if source_map and not emit_declaration_only else [],
675-
typings_outs = _out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts") if declaration or composite else [],
676-
typing_maps_outs = _out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts.map") if declaration_map else [],
693+
js_outs = js_outs,
694+
map_outs = map_outs,
695+
typings_outs = typings_outs,
696+
typing_maps_outs = typing_maps_outs,
677697
buildinfo_out = tsbuildinfo_path if composite or incremental else None,
678698
tsc = tsc,
679699
link_workspace_root = link_workspace_root,
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
load("//packages/typescript:index.bzl", "ts_project")
1+
# Use the ts_project rule directly, not the wrapper macro. We don't want checking for empty outs.
2+
load("//packages/typescript/internal:ts_project.bzl", "ts_project")
23

34
ts_project(
45
name = "tsconfig-a",
56
srcs = ["a.d.ts"],
7+
tsconfig = ":tsconfig-a.json",
68
)
79

10+
# Just verify that the a.d.ts file is transitively propagated
811
ts_project(
912
name = "tsconfig-b",
1013
srcs = [],
14+
tsconfig = ":tsconfig-b.json",
1115
deps = ["tsconfig-a"],
1216
)
1317

1418
ts_project(
1519
name = "tsconfig-c",
1620
srcs = ["c.ts"],
21+
js_outs = ["c.js"],
22+
tsconfig = ":tsconfig-c.json",
1723
deps = ["tsconfig-b"],
1824
)

packages/typescript/test/ts_project/json/BUILD.bazel

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ ts_project(
2525
ts_project(
2626
name = "tsconfig-decl-only",
2727
srcs = SRCS,
28-
emit_declaration_only = True,
28+
tsconfig = {
29+
"compilerOptions": {
30+
"declaration": True,
31+
"emitDeclarationOnly": True,
32+
"resolveJsonModule": True,
33+
"types": [],
34+
},
35+
},
2936
)
3037

3138
nodejs_test(

0 commit comments

Comments
 (0)