Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add emitIsolatedDts support #270

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/swc.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions e2e/emit_types/.bazelrc
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions e2e/emit_types/.bazelversion
34 changes: 34 additions & 0 deletions e2e/emit_types/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
],
)
14 changes: 14 additions & 0 deletions e2e/emit_types/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -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",
)
31 changes: 31 additions & 0 deletions e2e/emit_types/WORKSPACE.bazel
Original file line number Diff line number Diff line change
@@ -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",
)
1 change: 1 addition & 0 deletions e2e/emit_types/WORKSPACE.bzlmod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file replaces `WORKSPACE.bazel` when --enable_bzlmod is set.
8 changes: 8 additions & 0 deletions e2e/emit_types/a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Foo {
name: string;
}

export const A = 1;
export const AF: Foo = {
name: "bar",
};
6 changes: 6 additions & 0 deletions e2e/emit_types/b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { A, Foo } from "./a";

export const B: typeof A = 1;
export const BF: Foo = {
name: "baz",
};
3 changes: 3 additions & 0 deletions swc/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,20 @@ 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,
srcs = srcs,
plugins = plugins,
js_outs = js_outs,
map_outs = map_outs,
dts_outs = dts_outs,
output_dir = output_dir,
source_maps = source_maps,
args = args,
Expand Down
45 changes: 42 additions & 3 deletions swc/private/swc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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."""),
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you tested that --config-file foo --config-json '{"some": "object"}' has the desired behavior of overriding the file?

What happens if the user also gives a --config-json in the args they provide?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also would be good to comment if there's an ordering constraint between these two flags

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are a few things that I think need more testing, both here and our other --config-json cases (see plugins).

Note that we could also go the route where if experimental_emit_isolated_dts = True then we assume you set this emitIsolatedDts flag yourself, then maybe add some "validation" in the future like tsc does.

IMO if --config-json merges+overwrites the existing config then I prefer this instead of requiring validation, but both have trade-offs.

"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")
Expand Down Expand Up @@ -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)

Expand All @@ -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,
Expand Down Expand Up @@ -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,
)