diff --git a/docs/swc.md b/docs/swc.md
index 4fefde17..fe1f6702 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/examples/emit_types/.bazelrc b/examples/emit_types/.bazelrc
new file mode 100644
index 00000000..413c9950
--- /dev/null
+++ b/examples/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/examples/emit_types/.bazelversion b/examples/emit_types/.bazelversion
new file mode 120000
index 00000000..96cf9496
--- /dev/null
+++ b/examples/emit_types/.bazelversion
@@ -0,0 +1 @@
+../../.bazelversion
\ No newline at end of file
diff --git a/examples/emit_types/BUILD.bazel b/examples/emit_types/BUILD.bazel
new file mode 100644
index 00000000..f36e099a
--- /dev/null
+++ b/examples/emit_types/BUILD.bazel
@@ -0,0 +1,86 @@
+load("@aspect_rules_swc//swc:defs.bzl", "swc")
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+
+# No root/out
+swc(
+ name = "emit_dts",
+ srcs = [
+ "src/a.ts",
+ "src/b.ts",
+ ],
+ emit_isolated_dts = True,
+)
+
+build_test(
+ name = "emit_dts-test",
+ targets = [
+ "src/a.js",
+ "src/a.d.ts",
+ "src/b.js",
+ "src/b.d.ts",
+ ],
+)
+
+# With out_dir
+swc(
+ name = "emit_dts_outdir",
+ srcs = [
+ "src/a.ts",
+ "src/b.ts",
+ ],
+ emit_isolated_dts = True,
+ out_dir = "out",
+)
+
+build_test(
+ name = "emit_dts_outdir-test",
+ targets = [
+ "out/src/a.js",
+ "out/src/a.d.ts",
+ "out/src/b.js",
+ "out/src/b.d.ts",
+ ],
+)
+
+# With root_dir
+swc(
+ name = "emit_dts_rootdir",
+ srcs = [
+ "src/a.ts",
+ "src/b.ts",
+ ],
+ emit_isolated_dts = True,
+ root_dir = "src",
+)
+
+build_test(
+ name = "emit_dts_rootdir-test",
+ targets = [
+ "a.js",
+ "a.d.ts",
+ "b.js",
+ "b.d.ts",
+ ],
+)
+
+# With out_dir and root_dir
+swc(
+ name = "emit_dts_outdir_rootdir",
+ srcs = [
+ "src/a.ts",
+ "src/b.ts",
+ ],
+ emit_isolated_dts = True,
+ out_dir = "out_root",
+ root_dir = "src",
+)
+
+build_test(
+ name = "emit_dts_outdir_rootdir-test",
+ targets = [
+ "out_root/a.js",
+ "out_root/a.d.ts",
+ "out_root/b.js",
+ "out_root/b.d.ts",
+ ],
+)
diff --git a/examples/emit_types/src/a.ts b/examples/emit_types/src/a.ts
new file mode 100644
index 00000000..f0cf0135
--- /dev/null
+++ b/examples/emit_types/src/a.ts
@@ -0,0 +1,8 @@
+export interface Foo {
+ name: string;
+}
+
+export const A = 1;
+export const AF: Foo = {
+ name: "bar",
+};
diff --git a/examples/emit_types/src/b.ts b/examples/emit_types/src/b.ts
new file mode 100644
index 00000000..cf59416c
--- /dev/null
+++ b/examples/emit_types/src/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 a15e7ad3..4ca76569 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 4479d815..de9591d3 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,12 +82,19 @@ 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."""),
}
def _is_ts_src(src):
return src.endswith(".ts") or src.endswith(".mts") or src.endswith(".cts") or src.endswith(".tsx") or src.endswith(".jsx")
+def _is_typings_src(src):
+ return src.endswith(".d.ts") or src.endswith(".d.mts") or src.endswith(".d.cts")
+
def _is_js_src(src):
return src.endswith(".mjs") or src.endswith(".cjs") or src.endswith(".js")
@@ -112,7 +126,7 @@ def _remove_extension(f):
return f if i <= 0 else f[:-(len(f) - i)]
def _to_js_out(src, out_dir, root_dir, js_outs = []):
- if not _is_supported_src(src):
+ if not _is_supported_src(src) or _is_typings_src(src):
return None
exts = {
@@ -153,7 +167,7 @@ def _calculate_js_outs(srcs, out_dir, root_dir):
def _to_map_out(src, source_maps, out_dir, root_dir):
if source_maps == "false" or source_maps == "inline":
return None
- if not _is_supported_src(src):
+ if not _is_supported_src(src) or _is_typings_src(src):
return None
exts = {
".mts": ".mjs.map",
@@ -177,6 +191,23 @@ 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 emit_isolated_dts:
+ return None
+ if not _is_supported_src(src) or _is_typings_src(src):
+ 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
@@ -252,6 +283,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")
@@ -296,19 +336,29 @@ def _swc_impl(ctx):
src_path = _relative_to_package(src.path, ctx)
+ # This source file is a typings file and not transpiled
+ if _is_typings_src(src_path):
+ # Copy to the output directory if emitting dts files is enabled
+ if ctx.attr.emit_isolated_dts:
+ output_sources.append(src)
+ continue
+
js_out_path = _to_js_out(src_path, ctx.attr.out_dir, ctx.attr.root_dir, js_outs_relative)
if not js_out_path:
# This source file is not a supported src
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)
@@ -317,7 +367,7 @@ def _swc_impl(ctx):
_swc_action(
ctx,
swc_toolchain.swcinfo.swc_binary,
- inputs = src_inputs,
+ inputs = [src] + inputs,
arguments = [
args,
src_args,
@@ -377,4 +427,5 @@ swc = struct(
toolchains = ["@aspect_rules_swc//swc:toolchain_type"],
calculate_js_outs = _calculate_js_outs,
calculate_map_outs = _calculate_map_outs,
+ calculate_dts_outs = _calculate_dts_outs,
)