diff --git a/packages/typescript/internal/ts_config.bzl b/packages/typescript/internal/ts_config.bzl index 2fc4f8e690..a54d5c9d99 100644 --- a/packages/typescript/internal/ts_config.bzl +++ b/packages/typescript/internal/ts_config.bzl @@ -14,7 +14,14 @@ "tsconfig.json files using extends" -TsConfigInfo = provider() +TsConfigInfo = provider( + doc = """Provides TypeScript configuration, in the form of a tsconfig.json file + along with any transitively referenced tsconfig.json files chained by the + "extends" feature""", + fields = { + "deps": "all tsconfig.json files needed to configure TypeScript", + }, +) def _ts_config_impl(ctx): files = depset([ctx.file.src]) @@ -22,7 +29,10 @@ def _ts_config_impl(ctx): for dep in ctx.attr.deps: if TsConfigInfo in dep: transitive_deps.extend(dep[TsConfigInfo].deps) - return [DefaultInfo(files = files), TsConfigInfo(deps = ctx.files.deps + transitive_deps)] + return [ + DefaultInfo(files = files), + TsConfigInfo(deps = [ctx.file.src] + ctx.files.deps + transitive_deps), + ] ts_config = rule( implementation = _ts_config_impl, @@ -30,7 +40,6 @@ ts_config = rule( "deps": attr.label_list( doc = """Additional tsconfig.json files referenced via extends""", allow_files = True, - mandatory = True, ), "src": attr.label( doc = """The tsconfig.json file passed to the TypeScript compiler""", @@ -41,7 +50,7 @@ ts_config = rule( doc = """Allows a tsconfig.json file to extend another file. Normally, you just give a single `tsconfig.json` file as the tsconfig attribute -of a `ts_library` rule. However, if your `tsconfig.json` uses the `extends` +of a `ts_library` or `ts_project` rule. However, if your `tsconfig.json` uses the `extends` feature from TypeScript, then the Bazel implementation needs to know about that extended configuration file as well, to pass them both to the TypeScript compiler. """, diff --git a/packages/typescript/internal/ts_project.bzl b/packages/typescript/internal/ts_project.bzl index 26f5ed10e5..7530f7bff5 100644 --- a/packages/typescript/internal/ts_project.bzl +++ b/packages/typescript/internal/ts_project.bzl @@ -2,6 +2,7 @@ load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "NpmPackageInfo", "declaration_info", "js_module_info", "run_node") load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "module_mappings_aspect") +load(":ts_config.bzl", "TsConfigInfo") _DEFAULT_TSC = ( # BEGIN-INTERNAL @@ -37,14 +38,6 @@ _OUTPUTS = { "typings_outs": attr.output_list(), } -_TsConfigInfo = provider( - doc = """Passes tsconfig.json files to downstream compilations so that TypeScript can read them. - This is needed to support Project References""", - fields = { - "tsconfigs": "depset of tsconfig.json files", - }, -) - def _join(*elements): return "/".join([f for f in elements if f]) @@ -87,8 +80,8 @@ def _ts_project_impl(ctx): deps_depsets = [] for dep in ctx.attr.deps: - if _TsConfigInfo in dep: - deps_depsets.append(dep[_TsConfigInfo].tsconfigs) + if TsConfigInfo in dep: + deps_depsets.append(dep[TsConfigInfo].deps) if NpmPackageInfo in dep: # TODO: we could maybe filter these to be tsconfig.json or *.d.ts only # we don't expect tsc wants to read any other files from npm packages. @@ -96,7 +89,11 @@ def _ts_project_impl(ctx): if DeclarationInfo in dep: deps_depsets.append(dep[DeclarationInfo].transitive_declarations) - inputs = ctx.files.srcs + depset(transitive = deps_depsets).to_list() + [ctx.file.tsconfig] + inputs = ctx.files.srcs + depset(transitive = deps_depsets).to_list() + if TsConfigInfo in ctx.attr.tsconfig: + inputs.extend(ctx.attr.tsconfig[TsConfigInfo].deps) + else: + inputs.append(ctx.file.tsconfig) if ctx.attr.extends: inputs.extend(ctx.files.extends) @@ -155,10 +152,10 @@ def _ts_project_impl(ctx): sources = depset(runtime_outputs), deps = ctx.attr.deps, ), - _TsConfigInfo(tsconfigs = depset([ctx.file.tsconfig] + ctx.files.extends, transitive = [ - dep[_TsConfigInfo].tsconfigs + TsConfigInfo(deps = depset([ctx.file.tsconfig] + ctx.files.extends, transitive = [ + dep[TsConfigInfo].deps for dep in ctx.attr.deps - if _TsConfigInfo in dep + if TsConfigInfo in dep ])), ] @@ -191,9 +188,15 @@ def _validate_options_impl(ctx): ts_build_info_file = ctx.attr.ts_build_info_file, ).to_json()]) + inputs = ctx.files.extends[:] + if TsConfigInfo in ctx.attr.tsconfig: + inputs.extend(ctx.attr.tsconfig[TsConfigInfo].deps) + else: + inputs.append(ctx.file.tsconfig) + run_node( ctx, - inputs = [ctx.file.tsconfig] + ctx.files.extends, + inputs = inputs, outputs = [marker], arguments = [arguments], executable = "validator", @@ -356,13 +359,14 @@ def ts_project_macro( deps: List of labels of other rules that produce TypeScript typings (.d.ts files) - tsconfig: Label of the tsconfig.json file to use for the compilation. + tsconfig: Label of the tsconfig.json file to use for the compilation, or a target that provides TsConfigInfo. - By default, we add `.json` to the `name` attribute. + By default, we assume the tsconfig file is named by adding `.json` to the `name` attribute. extends: List of labels of tsconfig file(s) referenced in `extends` section of tsconfig. - Must include any tsconfig files "chained" by extends clauses. + Any tsconfig files "chained" by extends clauses must either be transitive deps of the TsConfigInfo + provided to the `tsconfig` attribute, or must be explicitly listed here. args: List of strings of additional command-line arguments to pass to tsc. @@ -432,7 +436,7 @@ def ts_project_macro( extra_deps.append("_validate_%s_options" % name) typings_out_dir = declaration_dir if declaration_dir else out_dir - tsbuildinfo_path = ts_build_info_file if ts_build_info_file else tsconfig[:-5] + ".tsbuildinfo" + tsbuildinfo_path = ts_build_info_file if ts_build_info_file else name + ".tsbuildinfo" ts_project( name = name, diff --git a/packages/typescript/test/ts_project/BUILD b/packages/typescript/test/ts_project/BUILD index 96b6172fd3..c41a53b812 100644 --- a/packages/typescript/test/ts_project/BUILD +++ b/packages/typescript/test/ts_project/BUILD @@ -1 +1,9 @@ +load("//packages/typescript:index.bzl", "ts_config") + exports_files(["tsconfig-base.json"]) + +ts_config( + name = "tsconfig", + src = "tsconfig-base.json", + visibility = ["//packages/typescript/test/ts_project:__subpackages__"], +) diff --git a/packages/typescript/test/ts_project/ts_config/BUILD.bazel b/packages/typescript/test/ts_project/ts_config/BUILD.bazel new file mode 100644 index 0000000000..e16b566eaf --- /dev/null +++ b/packages/typescript/test/ts_project/ts_config/BUILD.bazel @@ -0,0 +1,21 @@ +""" +Test a tree of tsconfig.json files +""" + +load("//packages/typescript:index.bzl", "ts_config", "ts_project") + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "tsconfig-extended.json", + "//packages/typescript/test/ts_project:tsconfig", + ], +) + +ts_project( + name = "compile_ts", + composite = True, + declaration = True, + tsconfig = "tsconfig", +) diff --git a/packages/typescript/test/ts_project/ts_config/a.ts b/packages/typescript/test/ts_project/ts_config/a.ts new file mode 100644 index 0000000000..f32045c3a2 --- /dev/null +++ b/packages/typescript/test/ts_project/ts_config/a.ts @@ -0,0 +1 @@ +export const a: string = 'hello world'; diff --git a/packages/typescript/test/ts_project/ts_config/tsconfig-extended.json b/packages/typescript/test/ts_project/ts_config/tsconfig-extended.json new file mode 100644 index 0000000000..83df99288c --- /dev/null +++ b/packages/typescript/test/ts_project/ts_config/tsconfig-extended.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig-base", + "compilerOptions": { + "declaration": true + }, +} diff --git a/packages/typescript/test/ts_project/ts_config/tsconfig.json b/packages/typescript/test/ts_project/ts_config/tsconfig.json new file mode 100644 index 0000000000..3f14391a0e --- /dev/null +++ b/packages/typescript/test/ts_project/ts_config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig-extended.json", + "compilerOptions": { + "types": [] + } +}