diff --git a/docs/swc.md b/docs/swc.md index 4bdff99..a5a2b01 100644 --- a/docs/swc.md +++ b/docs/swc.md @@ -7,7 +7,7 @@ swc rule ## swc
-swc(name, srcs, args, data, swcrc, source_maps, source_map_outputs, kwargs)
+swc(name, srcs, args, data, output_dir, swcrc, source_maps, source_map_outputs, kwargs)
 
Execute the swc compiler @@ -21,6 +21,7 @@ Execute the swc compiler | srcs | source files, typically .ts files in the source tree | None | | args | additional arguments to pass to swc cli, see https://swc.rs/docs/usage/cli | [] | | data | runtime dependencies to be propagated in the runfiles | [] | +| output_dir | whether to produce a directory output rather than individual files | False | | swcrc | label of a configuration file for swc, see https://swc.rs/docs/configuration/swcrc | None | | source_maps | If set, the --source-maps argument is passed to the swc cli with the value. True/False are automaticaly converted to "true"/"false" string values the cli expects. If source_maps is "true" or "both" then source_map_outputs is automatically set to True. | None | | source_map_outputs | if the rule is expected to produce a .js.map file output for each .js file output | False | diff --git a/examples/directory/BUILD.bazel b/examples/directory/BUILD.bazel new file mode 100644 index 0000000..c77a31a --- /dev/null +++ b/examples/directory/BUILD.bazel @@ -0,0 +1,33 @@ +"""Directory use case for swc: minifying a folder of code-split files + +This has to be a directory-in, directory-out pattern since the codesplitter produces unpredictable filenames. + +Note that this example also depends on the setup in /WORKSPACE at the root of this repository. +""" + +load("@aspect_rules_swc//swc:swc.bzl", "swc") +load(":mocks.bzl", "mock_codesplit") + +mock_codesplit( + name = "split_app", +) + +# Runs `swc path/to/split_app --out-dir ../../bazel-bin/examples/directory/minify` +# You can run `bazel build --subcommands //examples/directory:minify` +# to see the exact command line Bazel runs. +# Note that by default, sources are found by glob(["**/*.ts"]) +swc( + name = "minify", + srcs = ["split_app"], + output_dir = True, +) + +sh_test( + name = "test", + srcs = ["test.sh"], + # Workaround https://github.com/swc-project/swc/issues/3028 + # The test will need to know this folder to make the assertion + args = ["$(TARGET_CPU)-$(COMPILATION_MODE)"], + data = ["minify"], + deps = ["@bazel_tools//tools/bash/runfiles"], +) diff --git a/examples/directory/mocks.bzl b/examples/directory/mocks.bzl new file mode 100644 index 0000000..c172341 --- /dev/null +++ b/examples/directory/mocks.bzl @@ -0,0 +1,22 @@ +"Placeholder for a rule like webpack which may produce an unpredictable number of JS chunks" + +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@bazel_skylib//rules:write_file.bzl", "write_file") + +def mock_codesplit(name): + write_file( + name = "write1", + out = "file1.js", + content = ["const a = 1"], + ) + + write_file( + name = "write2", + out = "file2.js", + content = ["const a = 2"], + ) + + copy_to_directory( + name = name, + srcs = ["file1.js", "file2.js"], + ) diff --git a/examples/directory/test.sh b/examples/directory/test.sh new file mode 100755 index 0000000..01f3401 --- /dev/null +++ b/examples/directory/test.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +set -o errexit + +ARCH=$1 +readonly in_folder=$(rlocation $TEST_WORKSPACE/$(dirname $TEST_BINARY)/minify) + +# Due to https://github.com/swc-project/swc/issues/3028 the path is longer than we'd like +# It should really just contain $in_folder/file1.js +if ! [[ -e $in_folder/$ARCH/bin/examples/directory/split_app/file1.js ]]; then + echo >&2 "Missing expected output file in directory" + exit 1 +fi diff --git a/swc/private/swc.bzl b/swc/private/swc.bzl index 15eea59..1069a77 100644 --- a/swc/private/swc.bzl +++ b/swc/private/swc.bzl @@ -6,6 +6,7 @@ _attrs = { "srcs": attr.label_list(allow_files = True, mandatory = True), "args": attr.string_list(), "source_maps": attr.string(), + "output_dir": attr.bool(), "data": attr.label_list(default = [], allow_files = True), "swcrc": attr.label(allow_single_file = True), "swc_cli": attr.label( @@ -21,60 +22,89 @@ _outputs = { } def _impl(ctx): - outputs = ctx.outputs.js_outs + ctx.outputs.map_outs + outputs = [] source_maps = len(ctx.outputs.map_outs) > 0 + binding = ctx.toolchains["@aspect_rules_swc//swc:toolchain_type"].swcinfo.binding + args = ctx.actions.args() - for src in ctx.files.srcs: - js_out = ctx.actions.declare_file(paths.replace_extension(src.basename, ".js"), sibling = src) - outs = [js_out] - if source_maps: - outs.append(ctx.actions.declare_file(paths.replace_extension(src.basename, ".js.map"), sibling = src)) - args = ctx.actions.args() - - # Add user specified arguments *before* rule supplied arguments - args.add_all(ctx.attr.args) - - # Pass in the swcrc config if it is set; if it is - # then source paths are expected to be relative to the swcrc directory - src_path = src.path - if ctx.file.swcrc: - swcrc_path = ctx.file.swcrc.path - swcrc_directory = paths.dirname(swcrc_path) - args.add_all([ - "--config-file", - swcrc_path, - ]) - - # TODO(greg): determine if this is needed, given that relativize below should provide a correct path - # if not src_path.startswith(swcrc_directory): - # fail("sources must be in swcrc directory or subdirectory if swcrc is specified") - src_path = paths.relativize(src_path, swcrc_directory) + # Add user specified arguments *before* rule supplied arguments + args.add_all(ctx.attr.args) + if ctx.attr.output_dir: + if len(ctx.attr.srcs) != 1: + fail("Under output_dir, there must be a single entry in srcs") + if not ctx.files.srcs[0].is_directory: + fail("Under output_dir, the srcs must be directories, not files") + out = ctx.actions.declare_file(ctx.label.name) + outputs.append(out) args.add_all([ - src_path, - "--out-file", - js_out.path, + ctx.files.srcs[0].path, + "--out-dir", + out.path, "--no-swcrc", "-q", ]) - binding = ctx.toolchains["@aspect_rules_swc//swc:toolchain_type"].swcinfo.binding - ctx.actions.run( - inputs = [src] + ctx.toolchains["@aspect_rules_swc//swc:toolchain_type"].swcinfo.tool_files, + inputs = ctx.files.srcs + ctx.toolchains["@aspect_rules_swc//swc:toolchain_type"].swcinfo.tool_files, arguments = [args], - outputs = outs, + outputs = [out], env = { # Our patch for @swc/core uses this environment variable to locate the rust binding "SWC_BINARY_PATH": binding, }, executable = ctx.executable.swc_cli, - progress_message = "Transpiling with swc %s [swc %s]" % ( - ctx.label, - src.short_path, - ), + progress_message = "Transpiling with swc %s" % ctx.label, ) + else: + outputs.extend(ctx.outputs.js_outs) + outputs.extend(ctx.outputs.map_outs) + for src in ctx.files.srcs: + js_out = ctx.actions.declare_file(paths.replace_extension(src.basename, ".js"), sibling = src) + outs = [js_out] + if source_maps: + outs.append(ctx.actions.declare_file(paths.replace_extension(src.basename, ".js.map"), sibling = src)) + + # Pass in the swcrc config if it is set; if it is + # then source paths are expected to be relative to the swcrc directory + src_path = src.path + if ctx.file.swcrc: + swcrc_path = ctx.file.swcrc.path + swcrc_directory = paths.dirname(swcrc_path) + args.add_all([ + "--config-file", + swcrc_path, + ]) + + # TODO(greg): determine if this is needed, given that relativize below should provide a correct path + # if not src_path.startswith(swcrc_directory): + # fail("sources must be in swcrc directory or subdirectory if swcrc is specified") + src_path = paths.relativize(src_path, swcrc_directory) + + args.add_all([ + src_path, + "--out-file", + js_out.path, + "--no-swcrc", + "-q", + ]) + + ctx.actions.run( + inputs = [src] + ctx.toolchains["@aspect_rules_swc//swc:toolchain_type"].swcinfo.tool_files, + arguments = [args], + outputs = outs, + env = { + # Our patch for @swc/core uses this environment variable to locate the rust binding + "SWC_BINARY_PATH": binding, + }, + executable = ctx.executable.swc_cli, + progress_message = "Transpiling with swc %s [swc %s]" % ( + ctx.label, + src.short_path, + ), + ) + providers = [ DefaultInfo( files = depset(outputs), diff --git a/swc/swc.bzl b/swc/swc.bzl index f0d867d..71a7137 100644 --- a/swc/swc.bzl +++ b/swc/swc.bzl @@ -20,13 +20,14 @@ def _is_supported_src(src): return True return False -def swc(name, srcs = None, args = [], data = [], swcrc = None, source_maps = None, source_map_outputs = False, **kwargs): +def swc(name, srcs = None, args = [], data = [], output_dir = False, swcrc = None, source_maps = None, source_map_outputs = False, **kwargs): """Execute the swc compiler Args: name: A name for the target srcs: source files, typically .ts files in the source tree data: runtime dependencies to be propagated in the runfiles + output_dir: whether to produce a directory output rather than individual files args: additional arguments to pass to swc cli, see https://swc.rs/docs/usage/cli source_maps: If set, the --source-maps argument is passed to the swc cli with the value. True/False are automaticaly converted to "true"/"false" string values the cli expects. @@ -55,17 +56,20 @@ def swc(name, srcs = None, args = [], data = [], swcrc = None, source_maps = Non # Determine js & map outputs js_outs = [] map_outs = [] - for f in srcs: - if _is_supported_src(f): - js_outs.append(paths.replace_extension(f, ".js")) - if source_map_outputs: - map_outs.append(paths.replace_extension(f, ".js.map")) + + if not output_dir: + for f in srcs: + if _is_supported_src(f): + js_outs.append(paths.replace_extension(f, ".js")) + if source_map_outputs: + map_outs.append(paths.replace_extension(f, ".js.map")) _swc( name = name, srcs = srcs, js_outs = js_outs, map_outs = map_outs, + output_dir = output_dir, args = args, data = data, swcrc = swcrc,