From c47e00eba1fc24ab89eea8361153c02f9b92c0f3 Mon Sep 17 00:00:00 2001
From: ericrafalovsky <88681631+ericrafalovsky@users.noreply.github.com>
Date: Wed, 22 Jan 2025 16:34:17 -0500
Subject: [PATCH] feat: add generate_trace option to run TypeScript with
`--generateTrace` (#765)
---
docs/rules.md | 16 +++++----
docs/troubleshooting.md | 4 +++
examples/generate_trace/BUILD.bazel | 6 ++++
examples/generate_trace/a.ts | 1 +
examples/generate_trace/tsconfig.json | 1 +
ts/BUILD.bazel | 17 ++++++++++
ts/defs.bzl | 5 +++
ts/private/options.bzl | 4 ++-
ts/private/ts_lib.bzl | 3 ++
ts/private/ts_project.bzl | 17 +++++++++-
ts/test/flags_test.bzl | 48 ++++++++++++++++++++++++++-
11 files changed, 112 insertions(+), 10 deletions(-)
create mode 100644 examples/generate_trace/BUILD.bazel
create mode 100644 examples/generate_trace/a.ts
create mode 100644 examples/generate_trace/tsconfig.json
diff --git a/docs/rules.md b/docs/rules.md
index 7dbfa0a9..5a9b0a6f 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -41,11 +41,11 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project_rule")
ts_project_rule(name, deps, srcs, data, allow_js, args, assets, buildinfo_out, composite,
declaration, declaration_dir, declaration_map, declaration_transpile,
- emit_declaration_only, extends, incremental, is_typescript_5_or_greater,
- isolated_typecheck, js_outs, map_outs, no_emit, out_dir, preserve_jsx,
- pretranspiled_dts, pretranspiled_js, resolve_json_module, resource_set, root_dir,
- source_map, supports_workers, transpile, ts_build_info_file, tsc, tsc_worker,
- tsconfig, typing_maps_outs, typings_outs, validate, validator)
+ emit_declaration_only, extends, generate_trace, incremental,
+ is_typescript_5_or_greater, isolated_typecheck, js_outs, map_outs, no_emit, out_dir,
+ preserve_jsx, pretranspiled_dts, pretranspiled_js, resolve_json_module, resource_set,
+ root_dir, source_map, supports_workers, transpile, ts_build_info_file, tsc,
+ tsc_worker, tsconfig, typing_maps_outs, typings_outs, validate, validator)
Implementation rule behind the ts_project macro.
@@ -74,6 +74,7 @@ for srcs and tsconfig, and pre-declaring output files.
| declaration_transpile | Whether tsc should be used to produce .d.ts outputs | Boolean | optional | `False` |
| emit_declaration_only | https://www.typescriptlang.org/tsconfig#emitDeclarationOnly | Boolean | optional | `False` |
| extends | https://www.typescriptlang.org/tsconfig#extends | Label | optional | `None` |
+| generate_trace | https://www.typescriptlang.org/tsconfig/#generateTrace | Boolean | optional | `False` |
| incremental | https://www.typescriptlang.org/tsconfig#incremental | Boolean | optional | `False` |
| is_typescript_5_or_greater | Whether TypeScript version is >= 5.0.0 | Boolean | optional | `False` |
| isolated_typecheck | Whether type-checking should be a separate action.
This allows the transpilation action to run without waiting for typings from dependencies.
Requires a minimum version of typescript 5.6 for the [noCheck](https://www.typescriptlang.org/tsconfig#noCheck) flag which is automatically set on the transpilation action when the typecheck action is isolated.
Requires [isolatedDeclarations](https://www.typescriptlang.org/tsconfig#isolatedDeclarations) to be set so that declarations can be emitted without dependencies. The use of `isolatedDeclarations` may require significant changes to your codebase and should be done as a pre-requisite to enabling `isolated_typecheck`. | Boolean | optional | `False` |
@@ -131,8 +132,8 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
ts_project(name, tsconfig, srcs, args, data, deps, assets, extends, allow_js, isolated_typecheck,
declaration, source_map, declaration_map, resolve_json_module, preserve_jsx, composite,
incremental, no_emit, emit_declaration_only, transpiler, declaration_transpiler,
- ts_build_info_file, tsc, tsc_worker, validate, validator, declaration_dir, out_dir,
- root_dir, supports_workers, kwargs)
+ ts_build_info_file, generate_trace, tsc, tsc_worker, validate, validator, declaration_dir,
+ out_dir, root_dir, supports_workers, kwargs)
Compiles one TypeScript project using `tsc --project`.
@@ -184,6 +185,7 @@ If you have problems getting your `ts_project` to work correctly, read the dedic
| transpiler | A custom transpiler tool to run that produces the JavaScript outputs instead of `tsc`.
Under `--@aspect_rules_ts//ts:default_to_tsc_transpiler`, the default is to use `tsc` to produce `.js` outputs in the same action that does the type-checking to produce `.d.ts` outputs. This is the simplest configuration, however `tsc` is slower than alternatives. It also means developers must wait for the type-checking in the developer loop.
Without `--@aspect_rules_ts//ts:default_to_tsc_transpiler`, an explicit value must be set. This may be the string `"tsc"` to explicitly choose `tsc`, just like the default above.
It may also be any rule or macro with this signature: `(name, srcs, **kwargs)`
If JavaScript outputs are configured to not be emitted the custom transpiler will not be used, such as when `no_emit = True` or `emit_declaration_only = True`.
See [docs/transpiler.md](/docs/transpiler.md) for more details. | `None` |
| declaration_transpiler | A custom transpiler tool to run that produces the TypeScript declaration outputs instead of `tsc`.
It may be any rule or macro with this signature: `(name, srcs, **kwargs)`
If TypeScript declaration outputs are configured to not be emitted the custom declaration transpiler will not be used, such as when `no_emit = True` or `declaration = False`.
See [docs/transpiler.md](/docs/transpiler.md) for more details. | `None` |
| ts_build_info_file | The user-specified value of `tsBuildInfoFile` from the tsconfig. Helps Bazel to predict the path where the .tsbuildinfo output is written. | `None` |
+| generate_trace | Whether to generate a trace file for TypeScript compiler performance analysis. When enabled, creates a trace directory containing performance tracing information that can be loaded in chrome://tracing. Use the `--@aspect_rules_ts//ts:generate_tsc_trace` flag to enable this by default. | `None` |
| tsc | Label of the TypeScript compiler binary to run. This allows you to use a custom API-compatible compiler in place of the regular `tsc` such as a custom `js_binary` or Angular's `ngc`. compatible with it such as Angular's `ngc`.
See examples of use in [examples/custom_compiler](https://github.com/aspect-build/rules_ts/blob/main/examples/custom_compiler/BUILD.bazel) | `"@npm_typescript//:tsc"` |
| tsc_worker | Label of a custom TypeScript compiler binary which understands Bazel's persistent worker protocol. | `"@npm_typescript//:tsc_worker"` |
| validate | Whether to check that the dependencies are valid and the tsconfig JSON settings match the attributes on this target. Set this to `False` to skip running our validator, in case you have a legitimate reason for these to differ, e.g. you have a setting enabled just for the editor but you want different behavior when Bazel runs `tsc`. | `True` |
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 25191747..dd1c7f8d 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -177,3 +177,7 @@ Possible solutions:
```
* Use pnpm workspaces and `npm_package`/`npm_link_package` in between the `ts_project` rule and the `webpack` rule, so that the loader finds files under the `node_modules` tree like it would with third-party npm packages.
+
+
+# Troubleshooting performance issues
+Running your build with `--@aspect_rules_ts//ts:generate_tsc_trace` causes the `ts_project` rule to run with the `generate_trace` option enabled. This generates a profile that can be analyzed to understand TypeScript compilation performance. The trace files will be written in `bazel-bin//_trace/`. To analyze it use ChromeDevTools or [@typescript/analyze-trace](https://www.npmjs.com/package/@typescript/analyze-trace). See the [TypeScript documentation](https://github.com/microsoft/TypeScript-wiki/blob/main/Performance-Tracing.md) for more information.
\ No newline at end of file
diff --git a/examples/generate_trace/BUILD.bazel b/examples/generate_trace/BUILD.bazel
new file mode 100644
index 00000000..f2c0cf62
--- /dev/null
+++ b/examples/generate_trace/BUILD.bazel
@@ -0,0 +1,6 @@
+load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
+
+ts_project(
+ name = "lib",
+ generate_trace = True,
+)
diff --git a/examples/generate_trace/a.ts b/examples/generate_trace/a.ts
new file mode 100644
index 00000000..2d0a38f9
--- /dev/null
+++ b/examples/generate_trace/a.ts
@@ -0,0 +1 @@
+export const a: string = 'a'
diff --git a/examples/generate_trace/tsconfig.json b/examples/generate_trace/tsconfig.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/examples/generate_trace/tsconfig.json
@@ -0,0 +1 @@
+{}
diff --git a/ts/BUILD.bazel b/ts/BUILD.bazel
index c1324d8f..12be7484 100644
--- a/ts/BUILD.bazel
+++ b/ts/BUILD.bazel
@@ -87,6 +87,12 @@ bool_flag(
visibility = ["//visibility:public"],
)
+bool_flag(
+ name = "generate_tsc_trace",
+ build_setting_default = False,
+ visibility = ["//visibility:public"],
+)
+
# Note, users could use a Transition to make a subgraph of their depgraph opt-in to skipLibCheck.
config_setting(
name = "skip_lib_check.always",
@@ -119,12 +125,23 @@ config_setting(
},
)
+config_setting(
+ name = "generate_tsc_trace_flag",
+ flag_values = {
+ ":generate_tsc_trace": "true",
+ },
+)
+
options(
name = "options",
default_to_tsc_transpiler = select({
":default_to_tsc_transpiler_flag": True,
"//conditions:default": False,
}),
+ generate_tsc_trace = select({
+ ":generate_tsc_trace_flag": True,
+ "//conditions:default": False,
+ }),
skip_lib_check = select(
{
"@aspect_rules_ts//ts:skip_lib_check.always": True,
diff --git a/ts/defs.bzl b/ts/defs.bzl
index 44a8f2fa..7d560d1f 100644
--- a/ts/defs.bzl
+++ b/ts/defs.bzl
@@ -52,6 +52,7 @@ def ts_project(
transpiler = None,
declaration_transpiler = None,
ts_build_info_file = None,
+ generate_trace = None,
tsc = _tsc,
tsc_worker = _tsc_worker,
validate = True,
@@ -239,6 +240,9 @@ def ts_project(
Instructs Bazel *not* to expect `.js` or `.js.map` outputs for `.ts` sources.
ts_build_info_file: The user-specified value of `tsBuildInfoFile` from the tsconfig.
Helps Bazel to predict the path where the .tsbuildinfo output is written.
+ generate_trace: Whether to generate a trace file for TypeScript compiler performance analysis.
+ When enabled, creates a trace directory containing performance tracing information that can be
+ loaded in chrome://tracing. Use the `--@aspect_rules_ts//ts:generate_tsc_trace` flag to enable this by default.
supports_workers: Whether the "Persistent Worker" protocol is enabled.
This uses a custom `tsc` compiler to make rebuilds faster.
@@ -468,6 +472,7 @@ def ts_project(
source_map = source_map,
declaration_map = declaration_map,
ts_build_info_file = ts_build_info_file,
+ generate_trace = generate_trace,
out_dir = out_dir,
root_dir = root_dir,
js_outs = tsc_js_outs,
diff --git a/ts/private/options.bzl b/ts/private/options.bzl
index 1dfb42f2..c3e74333 100644
--- a/ts/private/options.bzl
+++ b/ts/private/options.bzl
@@ -13,7 +13,7 @@ Please read https://docs.aspect.build/rules/aspect_rules_ts/docs/transpiler
OptionsInfo = provider(
doc = "Internal: Provider that carries verbosity and global worker support information.",
- fields = ["args", "default_to_tsc_transpiler", "verbose", "supports_workers"],
+ fields = ["args", "default_to_tsc_transpiler", "verbose", "supports_workers", "generate_tsc_trace"],
)
def _options_impl(ctx):
@@ -47,6 +47,7 @@ def _options_impl(ctx):
args = args,
supports_workers = ctx.attr.supports_workers,
default_to_tsc_transpiler = ctx.attr.default_to_tsc_transpiler,
+ generate_tsc_trace = ctx.attr.generate_tsc_trace,
)
options = rule(
@@ -56,5 +57,6 @@ options = rule(
"verbose": attr.bool(),
"supports_workers": attr.bool(),
"skip_lib_check": attr.bool(),
+ "generate_tsc_trace": attr.bool(),
},
)
diff --git a/ts/private/ts_lib.bzl b/ts/private/ts_lib.bzl
index 2da88f4d..9ca955e6 100644
--- a/ts/private/ts_lib.bzl
+++ b/ts/private/ts_lib.bzl
@@ -162,6 +162,9 @@ COMPILER_OPTION_ATTRS = {
"ts_build_info_file": attr.string(
doc = "https://www.typescriptlang.org/tsconfig#tsBuildInfoFile",
),
+ "generate_trace": attr.bool(
+ doc = "https://www.typescriptlang.org/tsconfig/#generateTrace",
+ ),
}
# tsc knows how to produce the following kinds of output files.
diff --git a/ts/private/ts_project.bzl b/ts/private/ts_project.bzl
index 1a016666..9f55498a 100644
--- a/ts/private/ts_project.bzl
+++ b/ts/private/ts_project.bzl
@@ -196,6 +196,8 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
common_args.extend(["--tsBuildInfoFile", to_output_relative_path(ctx.outputs.buildinfo_out)])
outputs.append(ctx.outputs.buildinfo_out)
+ should_generate_tsc_trace = options.generate_tsc_trace or ctx.attr.generate_trace
+
output_sources = js_outs + map_outs + assets_outs + ctx.files.pretranspiled_js
output_types = typings_outs + typing_maps_outs + ctx.files.pretranspiled_dts
@@ -241,16 +243,24 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
# or
# - not invoking tsc for output files at all
if ctx.attr.isolated_typecheck or not (use_tsc_for_js or use_tsc_for_dts):
+ typecheck_outputs = []
+
# The type-checking action still need to produce some output, so we output the stdout
# to a .typecheck file that ends up in the typecheck output group.
typecheck_output = ctx.actions.declare_file(ctx.attr.name + ".typecheck")
typecheck_outs.append(typecheck_output)
+ typecheck_outputs.append(typecheck_output)
typecheck_arguments = ctx.actions.args()
typecheck_arguments.add_all(common_args)
typecheck_arguments.add("--noEmit")
+ if should_generate_tsc_trace:
+ tsc_trace_dir = ctx.actions.declare_directory(ctx.attr.name + "_trace")
+ typecheck_outputs.append(tsc_trace_dir)
+ typecheck_arguments.add_all(["--generateTrace", to_output_relative_path(tsc_trace_dir)])
+
env = {
"BAZEL_BINDIR": ctx.bin_dir.path,
}
@@ -271,7 +281,7 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
executable = executable,
inputs = transitive_inputs_depset,
arguments = [typecheck_arguments],
- outputs = [typecheck_output],
+ outputs = typecheck_outputs,
mnemonic = "TsProjectCheck",
execution_requirements = execution_requirements,
resource_set = resource_set(ctx.attr),
@@ -302,6 +312,11 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
# Not emitting declarations
tsc_emit_arguments.add("--declaration", "false")
+ if should_generate_tsc_trace and not ctx.attr.isolated_typecheck:
+ tsc_trace_dir = ctx.actions.declare_directory(ctx.attr.name + "_trace")
+ outputs.append(tsc_trace_dir)
+ tsc_emit_arguments.add_all(["--generateTrace", to_output_relative_path(tsc_trace_dir)])
+
inputs_depset = inputs if ctx.attr.isolated_typecheck else transitive_inputs_depset
if supports_workers:
diff --git a/ts/test/flags_test.bzl b/ts/test/flags_test.bzl
index be504503..0fe8a0f6 100644
--- a/ts/test/flags_test.bzl
+++ b/ts/test/flags_test.bzl
@@ -11,6 +11,7 @@ def _transition_impl(_settings, attr):
"@aspect_rules_ts//ts:supports_workers": attr.supports_workers,
"@aspect_rules_ts//ts:verbose": attr.verbose,
"@aspect_rules_ts//ts:skipLibCheck": attr.skip_lib_check,
+ "@aspect_rules_ts//ts:generate_tsc_trace": attr.generate_tsc_trace,
}
configuration_transition = transition(
@@ -20,6 +21,7 @@ configuration_transition = transition(
"@aspect_rules_ts//ts:supports_workers",
"@aspect_rules_ts//ts:verbose",
"@aspect_rules_ts//ts:skipLibCheck",
+ "@aspect_rules_ts//ts:generate_tsc_trace",
],
)
@@ -37,6 +39,7 @@ _transition_rule = rule(
"supports_workers": attr.bool(default = True),
"verbose": attr.bool(default = False),
"skip_lib_check": attr.string(default = "honor_tsconfig"),
+ "generate_tsc_trace": attr.bool(default = False),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
@@ -103,7 +106,27 @@ def _skip_lib_check_honor_tsconfig_test_impl(ctx):
_skip_lib_check_honor_tsconfig_test = analysistest.make(_skip_lib_check_honor_tsconfig_test_impl)
-def _ts_project_with_flags(name, supports_workers = None, supports_workers_flag = None, verbose_flag = None, skip_lib_check_flag = None, **kwargs):
+# generate_tsc_trace flag = true test
+def _generate_tsc_trace_true_test_impl(ctx):
+ env = analysistest.begin(ctx)
+ target_under_test = analysistest.target_under_test(env)
+ action = _find_tsc_action(env, target_under_test)
+ asserts.true(env, "--generateTrace" in action.argv, "expected --generateTrace to be set")
+ return analysistest.end(env)
+
+_generate_tsc_trace_true_test = analysistest.make(_generate_tsc_trace_true_test_impl)
+
+# generate_tsc_trace flag = false test
+def _generate_tsc_trace_false_test_impl(ctx):
+ env = analysistest.begin(ctx)
+ target_under_test = analysistest.target_under_test(env)
+ action = _find_tsc_action(env, target_under_test)
+ asserts.false(env, "--generateTrace" in action.argv, "expected --generateTrace to not be set")
+ return analysistest.end(env)
+
+_generate_tsc_trace_false_test = analysistest.make(_generate_tsc_trace_false_test_impl)
+
+def _ts_project_with_flags(name, supports_workers = None, supports_workers_flag = None, verbose_flag = None, skip_lib_check_flag = None, generate_tsc_trace_flag = None, **kwargs):
write_file(
name = "{}_write".format(name),
out = "{}.ts".format(name),
@@ -123,6 +146,7 @@ def _ts_project_with_flags(name, supports_workers = None, supports_workers_flag
supports_workers = supports_workers_flag,
verbose = verbose_flag,
skip_lib_check = skip_lib_check_flag,
+ generate_tsc_trace = generate_tsc_trace_flag,
)
def ts_project_flags_test_suite(name):
@@ -179,6 +203,26 @@ def ts_project_flags_test_suite(name):
target_under_test = ":skip_lib_check_honor_tsconfig",
)
+ _ts_project_with_flags(
+ name = "generate_tsc_trace_true",
+ tsconfig = _TSCONFIG,
+ generate_tsc_trace_flag = True,
+ )
+ _generate_tsc_trace_true_test(
+ name = "generate_tsc_trace_true_test",
+ target_under_test = ":generate_tsc_trace_true",
+ )
+
+ _ts_project_with_flags(
+ name = "generate_tsc_trace_false",
+ tsconfig = _TSCONFIG,
+ generate_tsc_trace_flag = False,
+ )
+ _generate_tsc_trace_false_test(
+ name = "generate_tsc_trace_false_test",
+ target_under_test = ":generate_tsc_trace_false",
+ )
+
native.test_suite(
name = name,
tests = [
@@ -186,5 +230,7 @@ def ts_project_flags_test_suite(name):
":verbose_false_test",
":skip_lib_check_always_test",
":skip_lib_check_honor_tsconfig_test",
+ ":generate_tsc_trace_true_test",
+ ":generate_tsc_trace_false_test",
],
)