From ee47d03d14b14901395d6184cfb0eb29e48cdba6 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Wed, 28 Aug 2024 17:14:08 -0700 Subject: [PATCH] feat: add emitIsolatedDts support --- docs/swc.md | 6 +++-- e2e/emit_types/.bazelrc | 15 +++++++++++ e2e/emit_types/.bazelversion | 1 + e2e/emit_types/BUILD.bazel | 34 +++++++++++++++++++++++++ e2e/emit_types/MODULE.bazel | 14 ++++++++++ e2e/emit_types/WORKSPACE.bazel | 31 +++++++++++++++++++++++ e2e/emit_types/WORKSPACE.bzlmod | 1 + e2e/emit_types/a.ts | 8 ++++++ e2e/emit_types/b.ts | 6 +++++ swc/defs.bzl | 3 +++ swc/private/swc.bzl | 45 ++++++++++++++++++++++++++++++--- 11 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 e2e/emit_types/.bazelrc create mode 120000 e2e/emit_types/.bazelversion create mode 100644 e2e/emit_types/BUILD.bazel create mode 100644 e2e/emit_types/MODULE.bazel create mode 100644 e2e/emit_types/WORKSPACE.bazel create mode 100644 e2e/emit_types/WORKSPACE.bzlmod create mode 100644 e2e/emit_types/a.ts create mode 100644 e2e/emit_types/b.ts diff --git a/docs/swc.md b/docs/swc.md index 4fefde1..fe1f670 100644 --- a/docs/swc.md +++ b/docs/swc.md @@ -18,8 +18,8 @@ swc( ## swc_compile
-swc_compile(name, srcs, data, args, js_outs, map_outs, out_dir, output_dir, plugins, root_dir,
-            source_maps, source_root, swcrc)
+swc_compile(name, srcs, data, args, dts_outs, emit_isolated_dts, js_outs, map_outs, out_dir,
+            output_dir, plugins, root_dir, source_maps, source_root, swcrc)
 
Underlying rule for the `swc` macro. @@ -39,6 +39,8 @@ for example to set your own output labels for `js_outs`. | srcs | source files, typically .ts files in the source tree | List of labels | required | | | data | Runtime dependencies to include in binaries/tests that depend on this target.

Follows the same semantics as `js_library` `data` attribute. See https://docs.aspect.build/rulesets/aspect_rules_js/docs/js_library#data for more info. | List of labels | optional | `[]` | | args | Additional arguments to pass to swcx cli (NOT swc!).

NB: this is not the same as the CLI arguments for @swc/cli npm package. For performance, rules_swc does not call a Node.js program wrapping the swc rust binding. Instead, we directly spawn the (somewhat experimental) native Rust binary shipped inside the @swc/core npm package, which the swc project calls "swcx" Tracking issue for feature parity: https://github.com/swc-project/swc/issues/4017 | List of strings | optional | `[]` | +| dts_outs | list of expected TypeScript declaration files.

Can be empty, meaning no dts files should be produced. If non-empty, there should be one for each entry in srcs. | List of labels | optional | `[]` | +| emit_isolated_dts | Emit .d.ts files instead of .js for TypeScript sources

EXPERIMENTAL: this API is undocumented, experimental and may change without notice | Boolean | optional | `False` | | js_outs | list of expected JavaScript output files.

There should be one for each entry in srcs. | List of labels | optional | `[]` | | map_outs | list of expected source map output files.

Can be empty, meaning no source maps should be produced. If non-empty, there should be one for each entry in srcs. | List of labels | optional | `[]` | | out_dir | With output_dir=False, output files will have this directory prefix.

With output_dir=True, this is the name of the output directory. | String | optional | `""` | diff --git a/e2e/emit_types/.bazelrc b/e2e/emit_types/.bazelrc new file mode 100644 index 0000000..413c995 --- /dev/null +++ b/e2e/emit_types/.bazelrc @@ -0,0 +1,15 @@ +# Import Aspect bazelrc presets +try-import %workspace%/../../.aspect/bazelrc/bazel7.bazelrc +import %workspace%/../../.aspect/bazelrc/convenience.bazelrc +import %workspace%/../../.aspect/bazelrc/correctness.bazelrc +import %workspace%/../../.aspect/bazelrc/debug.bazelrc +import %workspace%/../../.aspect/bazelrc/javascript.bazelrc +import %workspace%/../../.aspect/bazelrc/performance.bazelrc + +### YOUR PROJECT SPECIFIC OPTIONS GO HERE ### + +# Load any settings & overrides specific to the current user from `.aspect/bazelrc/user.bazelrc`. +# This file should appear in `.gitignore` so that settings are not shared with team members. This +# should be last statement in this config so the user configuration is able to overwrite flags from +# this file. See https://bazel.build/configure/best-practices#bazelrc-file. +try-import %workspace%/../../.aspect/bazelrc/user.bazelrc diff --git a/e2e/emit_types/.bazelversion b/e2e/emit_types/.bazelversion new file mode 120000 index 0000000..96cf949 --- /dev/null +++ b/e2e/emit_types/.bazelversion @@ -0,0 +1 @@ +../../.bazelversion \ No newline at end of file diff --git a/e2e/emit_types/BUILD.bazel b/e2e/emit_types/BUILD.bazel new file mode 100644 index 0000000..6d5fd55 --- /dev/null +++ b/e2e/emit_types/BUILD.bazel @@ -0,0 +1,34 @@ +load("@aspect_rules_swc//swc:defs.bzl", "swc") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +swc( + name = "compile", + srcs = [ + "a.ts", + "b.ts", + ], +) + +swc( + name = "compile_emit_dts", + srcs = [ + "a.ts", + "b.ts", + ], + emit_isolated_dts = True, + out_dir = "dts_out", +) + +build_test( + name = "test", + targets = [ + ":compile", + ":compile_emit_dts", + "a.js", + "b.js", + "dts_out/a.js", + "dts_out/b.js", + "dts_out/a.d.ts", + "dts_out/b.d.ts", + ], +) diff --git a/e2e/emit_types/MODULE.bazel b/e2e/emit_types/MODULE.bazel new file mode 100644 index 0000000..7f8a854 --- /dev/null +++ b/e2e/emit_types/MODULE.bazel @@ -0,0 +1,14 @@ +bazel_dep(name = "aspect_rules_swc", version = "0.0.0", dev_dependency = True) +local_path_override( + module_name = "aspect_rules_swc", + path = "../..", +) + +bazel_dep(name = "bazel_skylib", version = "1.5.0", dev_dependency = True) + +# Use a recent swc version which includes the experimental emitIsolatedDts feature. +swc = use_extension("@aspect_rules_swc//swc:extensions.bzl", "swc", dev_dependency = True) +swc.toolchain( + name = "swc", + swc_version = "v1.6.6", +) diff --git a/e2e/emit_types/WORKSPACE.bazel b/e2e/emit_types/WORKSPACE.bazel new file mode 100644 index 0000000..f2395b6 --- /dev/null +++ b/e2e/emit_types/WORKSPACE.bazel @@ -0,0 +1,31 @@ +# Override http_archive for local testing +local_repository( + name = "aspect_rules_swc", + path = "../..", +) + +#---SNIP--- Below here is re-used in the snippet published on releases + +################### +# rules_swc setup # +################### + +# Fetches the rules_swc dependencies. +# If you want to have a different version of some dependency, +# you should fetch it *before* calling this. +# Alternatively, you can skip calling this function, so long as you've +# already fetched all the dependencies. +load("@aspect_rules_swc//swc:dependencies.bzl", "rules_swc_dependencies") + +rules_swc_dependencies() + +# Fetches a SWC cli from +# https://github.com/swc-project/swc/releases +# If you'd rather compile it from source, you can use rules_rust, fetch the project, +# then register the toolchain yourself. (Note, this is not yet documented) +load("@aspect_rules_swc//swc:repositories.bzl", "swc_register_toolchains") + +swc_register_toolchains( + name = "swc", + swc_version = "v1.6.6", +) diff --git a/e2e/emit_types/WORKSPACE.bzlmod b/e2e/emit_types/WORKSPACE.bzlmod new file mode 100644 index 0000000..a9d1093 --- /dev/null +++ b/e2e/emit_types/WORKSPACE.bzlmod @@ -0,0 +1 @@ +# This file replaces `WORKSPACE.bazel` when --enable_bzlmod is set. diff --git a/e2e/emit_types/a.ts b/e2e/emit_types/a.ts new file mode 100644 index 0000000..f0cf013 --- /dev/null +++ b/e2e/emit_types/a.ts @@ -0,0 +1,8 @@ +export interface Foo { + name: string; +} + +export const A = 1; +export const AF: Foo = { + name: "bar", +}; diff --git a/e2e/emit_types/b.ts b/e2e/emit_types/b.ts new file mode 100644 index 0000000..cf59416 --- /dev/null +++ b/e2e/emit_types/b.ts @@ -0,0 +1,6 @@ +import { A, Foo } from "./a"; + +export const B: typeof A = 1; +export const BF: Foo = { + name: "baz", +}; diff --git a/swc/defs.bzl b/swc/defs.bzl index a15e7ad..4ca7656 100644 --- a/swc/defs.bzl +++ b/swc/defs.bzl @@ -100,10 +100,12 @@ def swc(name, srcs, args = [], data = [], plugins = [], output_dir = False, swcr # Determine js & map outputs js_outs = [] map_outs = [] + dts_outs = [] if not output_dir: js_outs = _swc_lib.calculate_js_outs(srcs, out_dir, root_dir) map_outs = _swc_lib.calculate_map_outs(srcs, source_maps, out_dir, root_dir) + dts_outs = _swc_lib.calculate_dts_outs(srcs, kwargs.get("emit_isolated_dts", False), out_dir, root_dir) swc_compile( name = name, @@ -111,6 +113,7 @@ def swc(name, srcs, args = [], data = [], plugins = [], output_dir = False, swcr plugins = plugins, js_outs = js_outs, map_outs = map_outs, + dts_outs = dts_outs, output_dir = output_dir, source_maps = source_maps, args = args, diff --git a/swc/private/swc.bzl b/swc/private/swc.bzl index 353d723..045c0c9 100644 --- a/swc/private/swc.bzl +++ b/swc/private/swc.bzl @@ -66,6 +66,13 @@ https://docs.aspect.build/rulesets/aspect_rules_js/docs/js_library#data for more "root_dir": attr.string( doc = "a subdirectory under the input package which should be consider the root directory of all the input files", ), + "emit_isolated_dts": attr.bool( + doc = """Emit .d.ts files instead of .js for TypeScript sources + +EXPERIMENTAL: this API is undocumented, experimental and may change without notice +""", + default = False, + ), } _outputs = { @@ -75,6 +82,10 @@ There should be one for each entry in srcs."""), "map_outs": attr.output_list(doc = """list of expected source map output files. Can be empty, meaning no source maps should be produced. +If non-empty, there should be one for each entry in srcs."""), + "dts_outs": attr.output_list(doc = """list of expected TypeScript declaration files. + +Can be empty, meaning no dts files should be produced. If non-empty, there should be one for each entry in srcs."""), } @@ -184,6 +195,21 @@ def _calculate_map_outs(srcs, source_maps, out_dir, root_dir): out.append(map_out) return out +def _to_dts_out(src, emit_isolated_dts, out_dir, root_dir): + if not _is_supported_src(src) or not emit_isolated_dts: + return None + dts_out = src[:src.rindex(".")] + ".d.ts" + dts_out = _to_out_path(dts_out, out_dir, root_dir) + return dts_out + +def _calculate_dts_outs(srcs, emit_isolated_dts, out_dir, root_dir): + out = [] + for f in srcs: + dts_out = _to_dts_out(f, emit_isolated_dts, out_dir, root_dir) + if dts_out: + out.append(dts_out) + return out + def _calculate_source_file(ctx, src): if not (ctx.attr.out_dir or ctx.attr.root_dir): return src.basename @@ -259,6 +285,15 @@ def _swc_impl(ctx): inputs.extend(ctx.files.plugins) args.add_all(plugin_args) + if ctx.attr.emit_isolated_dts: + args.add_all(["--config-json", json.encode({ + "jsc": { + "experimental": { + "emitIsolatedDts": True, + }, + }, + })]) + if ctx.attr.output_dir: if len(ctx.attr.srcs) != 1: fail("Under output_dir, there must be a single entry in srcs") @@ -309,13 +344,16 @@ def _swc_impl(ctx): continue js_out = ctx.actions.declare_file(js_out_path) outputs = [js_out] - map_out_path = _to_map_out(src_path, ctx.attr.source_maps, ctx.attr.out_dir, ctx.attr.root_dir) + map_out_path = _to_map_out(src_path, ctx.attr.source_maps, ctx.attr.out_dir, ctx.attr.root_dir) if map_out_path: js_map_out = ctx.actions.declare_file(map_out_path) outputs.append(js_map_out) - src_inputs = [src] + inputs + dts_out_path = _to_dts_out(src_path, ctx.attr.emit_isolated_dts, ctx.attr.out_dir, ctx.attr.root_dir) + if dts_out_path: + dts_out = ctx.actions.declare_file(dts_out_path) + outputs.append(dts_out) src_args.add("--out-file", js_out) @@ -324,7 +362,7 @@ def _swc_impl(ctx): _swc_action( ctx, swc_toolchain.swcinfo.swc_binary, - inputs = src_inputs, + inputs = [src] + inputs, arguments = [ args, src_args, @@ -385,4 +423,5 @@ swc = struct( SUPPORTED_EXTENSIONS = _SUPPORTED_EXTENSIONS, calculate_js_outs = _calculate_js_outs, calculate_map_outs = _calculate_map_outs, + calculate_dts_outs = _calculate_dts_outs, )