Skip to content

Bundling

ts_binary and ts_bundle collect transitive .js outputs and optionally invoke a pluggable bundler.

Basic Usage (Placeholder Mode)

Without a bundler, the rule concatenates all .js files in dependency order. This is useful during development and keeps the build graph valid.

load("@rules_typescript//ts:defs.bzl", "ts_binary")

ts_binary(
    name = "app",
    entry_point = "//src/app",
    format = "esm",
    sourcemap = True,
)
bazel build //:app

With Vite

load("@rules_typescript//vite:bundler.bzl", "vite_bundler")
load("@rules_typescript//npm:defs.bzl", "node_modules")
load("@rules_typescript//ts:defs.bzl", "ts_bundle")

node_modules(
    name = "node_modules",
    deps = ["@npm//:vite"],
)

vite_bundler(
    name = "vite",
    vite = "@npm//:vite",
    node_modules = ":node_modules",
)

ts_bundle(
    name = "app",
    entry_point = "//src/app",
    bundler = ":vite",
    format = "esm",
    sourcemap = True,
    minify = True,
    external = ["react", "react-dom"],
)

Custom Bundler (BundlerInfo Interface)

Any Bazel rule that returns BundlerInfo can plug into ts_bundle and ts_binary. This lets you bring your own bundler — esbuild, Rolldown, webpack — without modifying rules_typescript.

load("@rules_typescript//ts:defs.bzl", "BundlerInfo")

def _my_bundler_impl(ctx):
    return [BundlerInfo(
        bundler_binary = ctx.file.binary,
        config_file = None,                 # optional static config
        runtime_deps = depset([]),           # files needed at bundle time
        use_generated_config = False,        # set True for Vite-style config
    )]

my_bundler = rule(
    implementation = _my_bundler_impl,
    attrs = {
        "binary": attr.label(
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

BundlerInfo Invocation Modes

Mode 1 — Standard CLI (use_generated_config = False, the default)

ts_bundle invokes the bundler binary with:

<bundler_binary>
  --entry  <path/to/entry.js>
  --out-dir <output/dir>
  --format esm|cjs|iife
  [--external <pkg>]...
  [--sourcemap]
  [--config <config_file>]   (only when config_file is set)

Output is expected at <out-dir>/<bundle_name>.js (and .js.map if --sourcemap).

Mode 2 — Generated config (use_generated_config = True)

ts_bundle generates a vite.config.mjs containing all bundle options and invokes:

<bundler_binary> <absolute_path_to_vite.config.mjs> <entry_path> <out_dir>
Format Output file
esm <bundle_name>.es.js
cjs <bundle_name>.cjs.js
iife <bundle_name>.iife.js

BundlerInfo Fields

Field Type Description
bundler_binary File The executable that performs bundling
config_file File or None Optional static config passed via --config (mode 1 only)
runtime_deps depset of File Files the bundler needs at runtime
use_generated_config bool When True, use mode 2 (generated vite.config.mjs)

ts_binary / ts_bundle Attributes

Attribute Type Default Description
entry_point label required ts_compile target providing JsInfo
bundler label None Target providing BundlerInfo
bundle_name string rule name Output file name (without .js)
format string "esm" Output format: esm, cjs, iife
sourcemap bool True Emit source map
minify bool True Minify the bundle (esbuild minifier via Vite)
split_chunks bool False Enable chunk splitting (Vite mode only; output is a directory)
external string_list [] Module specifiers to leave external
define string_dict {} Global constant replacements