Skip to content

Commit 093f69b

Browse files
committed
feat: add emitIsolatedDts support
1 parent 0fc9df4 commit 093f69b

File tree

9 files changed

+196
-9
lines changed

9 files changed

+196
-9
lines changed

.prettierignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
docs/*.md
22
examples/pnpm-lock.yaml
3-
**/expected.js
4-
**/expected.cjs
3+
**/expected.*
54
**/expected_tsc

docs/swc.md

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/emit_types/BUILD.bazel

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
2+
load("@aspect_rules_swc//swc:defs.bzl", "swc")
3+
load("@bazel_skylib//rules:build_test.bzl", "build_test")
4+
5+
# No root/out
6+
swc(
7+
name = "emit_dts",
8+
srcs = [
9+
"src/a.ts",
10+
"src/b.ts",
11+
],
12+
emit_isolated_dts = True,
13+
)
14+
15+
build_test(
16+
name = "emit_dts-test",
17+
targets = [
18+
"src/a.js",
19+
"src/a.d.ts",
20+
"src/b.js",
21+
"src/b.d.ts",
22+
],
23+
)
24+
25+
# With out_dir
26+
swc(
27+
name = "emit_dts_outdir",
28+
srcs = [
29+
"src/a.ts",
30+
"src/b.ts",
31+
],
32+
emit_isolated_dts = True,
33+
out_dir = "out",
34+
)
35+
36+
build_test(
37+
name = "emit_dts_outdir-test",
38+
targets = [
39+
"out/src/a.js",
40+
"out/src/a.d.ts",
41+
"out/src/b.js",
42+
"out/src/b.d.ts",
43+
],
44+
)
45+
46+
# With root_dir
47+
swc(
48+
name = "emit_dts_rootdir",
49+
srcs = [
50+
"src/a.ts",
51+
"src/b.ts",
52+
],
53+
emit_isolated_dts = True,
54+
root_dir = "src",
55+
)
56+
57+
build_test(
58+
name = "emit_dts_rootdir-test",
59+
targets = [
60+
"a.js",
61+
"a.d.ts",
62+
"b.js",
63+
"b.d.ts",
64+
],
65+
)
66+
67+
# With out_dir and root_dir
68+
swc(
69+
name = "emit_dts_outdir_rootdir",
70+
srcs = [
71+
"src/a.ts",
72+
"src/b.ts",
73+
],
74+
emit_isolated_dts = True,
75+
out_dir = "out_root",
76+
root_dir = "src",
77+
)
78+
79+
build_test(
80+
name = "emit_dts_outdir_rootdir-test",
81+
targets = [
82+
"out_root/a.js",
83+
"out_root/a.d.ts",
84+
"out_root/b.js",
85+
"out_root/b.d.ts",
86+
],
87+
)
88+
89+
# Assert the output files are correct, have not changed etc.
90+
write_source_files(
91+
name = "outputs_test",
92+
files = {
93+
"expected.a.d.ts": "src/a.d.ts",
94+
"expected.b.d.ts": "src/b.d.ts",
95+
},
96+
)

examples/emit_types/expected.a.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* A basic interface */ export interface Foo {
2+
// teh name!
3+
name: string;
4+
}
5+
// Implicit type
6+
export declare const A: number;
7+
// Explicit type
8+
export declare const AF: Foo;

examples/emit_types/expected.b.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Imported and used as types
2+
import { A, Foo } from "./a";
3+
// Use of imported types
4+
export declare const B: typeof A;
5+
/** Another use of imported types */ export declare const BF: Foo;

examples/emit_types/src/a.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* A basic interface */
2+
export interface Foo {
3+
// teh name!
4+
name: string;
5+
}
6+
7+
// Implicit type
8+
export const A = 1;
9+
10+
// Explicit type
11+
export const AF: Foo = {
12+
name: "bar",
13+
};

examples/emit_types/src/b.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Imported and used as types
2+
import { A, Foo } from "./a";
3+
4+
// Use of imported types
5+
export const B: typeof A = 1;
6+
7+
/** Another use of imported types */
8+
export const BF: Foo = {
9+
name: "baz",
10+
};

swc/defs.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,20 @@ def swc(name, srcs, args = [], data = [], plugins = [], output_dir = False, swcr
100100
# Determine js & map outputs
101101
js_outs = []
102102
map_outs = []
103+
dts_outs = []
103104

104105
if not output_dir:
105106
js_outs = _swc_lib.calculate_js_outs(srcs, out_dir, root_dir)
106107
map_outs = _swc_lib.calculate_map_outs(srcs, source_maps, out_dir, root_dir)
108+
dts_outs = _swc_lib.calculate_dts_outs(srcs, kwargs.get("emit_isolated_dts", False), out_dir, root_dir)
107109

108110
swc_compile(
109111
name = name,
110112
srcs = srcs,
111113
plugins = plugins,
112114
js_outs = js_outs,
113115
map_outs = map_outs,
116+
dts_outs = dts_outs,
114117
output_dir = output_dir,
115118
source_maps = source_maps,
116119
args = args,

swc/private/swc.bzl

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ https://docs.aspect.build/rulesets/aspect_rules_js/docs/js_library#data for more
6666
"root_dir": attr.string(
6767
doc = "a subdirectory under the input package which should be consider the root directory of all the input files",
6868
),
69+
"emit_isolated_dts": attr.bool(
70+
doc = """Emit .d.ts files instead of .js for TypeScript sources
71+
72+
EXPERIMENTAL: this API is undocumented, experimental and may change without notice
73+
""",
74+
default = False,
75+
),
6976
}
7077

7178
_outputs = {
@@ -75,12 +82,19 @@ There should be one for each entry in srcs."""),
7582
"map_outs": attr.output_list(doc = """list of expected source map output files.
7683
7784
Can be empty, meaning no source maps should be produced.
85+
If non-empty, there should be one for each entry in srcs."""),
86+
"dts_outs": attr.output_list(doc = """list of expected TypeScript declaration files.
87+
88+
Can be empty, meaning no dts files should be produced.
7889
If non-empty, there should be one for each entry in srcs."""),
7990
}
8091

8192
def _is_ts_src(src):
8293
return src.endswith(".ts") or src.endswith(".mts") or src.endswith(".cts") or src.endswith(".tsx") or src.endswith(".jsx")
8394

95+
def _is_typings_src(src):
96+
return src.endswith(".d.ts") or src.endswith(".d.mts") or src.endswith(".d.cts")
97+
8498
def _is_js_src(src):
8599
return src.endswith(".mjs") or src.endswith(".cjs") or src.endswith(".js")
86100

@@ -112,7 +126,7 @@ def _remove_extension(f):
112126
return f if i <= 0 else f[:-(len(f) - i)]
113127

114128
def _to_js_out(src, out_dir, root_dir, js_outs = []):
115-
if not _is_supported_src(src):
129+
if not _is_supported_src(src) or _is_typings_src(src):
116130
return None
117131

118132
exts = {
@@ -153,7 +167,7 @@ def _calculate_js_outs(srcs, out_dir, root_dir):
153167
def _to_map_out(src, source_maps, out_dir, root_dir):
154168
if source_maps == "false" or source_maps == "inline":
155169
return None
156-
if not _is_supported_src(src):
170+
if not _is_supported_src(src) or _is_typings_src(src):
157171
return None
158172
exts = {
159173
".mts": ".mjs.map",
@@ -177,6 +191,23 @@ def _calculate_map_outs(srcs, source_maps, out_dir, root_dir):
177191
out.append(map_out)
178192
return out
179193

194+
def _to_dts_out(src, emit_isolated_dts, out_dir, root_dir):
195+
if not emit_isolated_dts:
196+
return None
197+
if not _is_supported_src(src) or _is_typings_src(src):
198+
return None
199+
dts_out = src[:src.rindex(".")] + ".d.ts"
200+
dts_out = _to_out_path(dts_out, out_dir, root_dir)
201+
return dts_out
202+
203+
def _calculate_dts_outs(srcs, emit_isolated_dts, out_dir, root_dir):
204+
out = []
205+
for f in srcs:
206+
dts_out = _to_dts_out(f, emit_isolated_dts, out_dir, root_dir)
207+
if dts_out:
208+
out.append(dts_out)
209+
return out
210+
180211
def _calculate_source_file(ctx, src):
181212
if not (ctx.attr.out_dir or ctx.attr.root_dir):
182213
return src.basename
@@ -252,6 +283,15 @@ def _swc_impl(ctx):
252283
inputs.extend(ctx.files.plugins)
253284
args.add_all(plugin_args)
254285

286+
if ctx.attr.emit_isolated_dts:
287+
args.add_all(["--config-json", json.encode({
288+
"jsc": {
289+
"experimental": {
290+
"emitIsolatedDts": True,
291+
},
292+
},
293+
})])
294+
255295
if ctx.attr.output_dir:
256296
if len(ctx.attr.srcs) != 1:
257297
fail("Under output_dir, there must be a single entry in srcs")
@@ -296,19 +336,29 @@ def _swc_impl(ctx):
296336

297337
src_path = _relative_to_package(src.path, ctx)
298338

339+
# This source file is a typings file and not transpiled
340+
if _is_typings_src(src_path):
341+
# Copy to the output directory if emitting dts files is enabled
342+
if ctx.attr.emit_isolated_dts:
343+
output_sources.append(src)
344+
continue
345+
299346
js_out_path = _to_js_out(src_path, ctx.attr.out_dir, ctx.attr.root_dir, js_outs_relative)
300347
if not js_out_path:
301348
# This source file is not a supported src
302349
continue
303350
js_out = ctx.actions.declare_file(js_out_path)
304351
outputs = [js_out]
305-
map_out_path = _to_map_out(src_path, ctx.attr.source_maps, ctx.attr.out_dir, ctx.attr.root_dir)
306352

353+
map_out_path = _to_map_out(src_path, ctx.attr.source_maps, ctx.attr.out_dir, ctx.attr.root_dir)
307354
if map_out_path:
308355
js_map_out = ctx.actions.declare_file(map_out_path)
309356
outputs.append(js_map_out)
310357

311-
src_inputs = [src] + inputs
358+
dts_out_path = _to_dts_out(src_path, ctx.attr.emit_isolated_dts, ctx.attr.out_dir, ctx.attr.root_dir)
359+
if dts_out_path:
360+
dts_out = ctx.actions.declare_file(dts_out_path)
361+
outputs.append(dts_out)
312362

313363
src_args.add("--out-file", js_out)
314364

@@ -317,7 +367,7 @@ def _swc_impl(ctx):
317367
_swc_action(
318368
ctx,
319369
swc_toolchain.swcinfo.swc_binary,
320-
inputs = src_inputs,
370+
inputs = [src] + inputs,
321371
arguments = [
322372
args,
323373
src_args,
@@ -377,4 +427,5 @@ swc = struct(
377427
toolchains = ["@aspect_rules_swc//swc:toolchain_type"],
378428
calculate_js_outs = _calculate_js_outs,
379429
calculate_map_outs = _calculate_map_outs,
430+
calculate_dts_outs = _calculate_dts_outs,
380431
)

0 commit comments

Comments
 (0)