The web-vite rules give you a Vite-style development experience — dev server, hot
reload, TypeScript/JSX/CSS out of the box — built entirely on rbs’s hermetic toolchains.
There is no Vite CLI or Webpack underneath: rbs bundles with Rollup (or optionally
Rolldown), and uses the same bundler for development and production, so what works in
dev works in prod.
Workspace setup
Declare the web toolchain once in WORKSPACE.rbs. One call sets up Node.js, esbuild, and
the bundler; add nodejs_repository entries for your app’s packages and
vitest_test_deps() for testing:
Dependencies work like package.json, split into three buckets on every app rule:
deps — runtime dependencies, bundled for the browser
dev_deps — build-time only (TypeScript, @types/*)
server_deps — server-only (Express, ws), never bundled for the browser
See Node.js & TypeScript for how npm
resolution and the rbs.lock lockfile work.
Your first app
A minimal React app is a package with an index.html, sources under src/, and one
target per mode in BUILD.rbs:
If srcs is omitted, sources are globbed from src/ automatically (.ts, .tsx, .js,
.jsx, CSS/SCSS, JSON, SVG). Files in public/ are copied into the output as-is.
The dev server workflow
Run the development target and open the printed URL:
The dev server:
- serves your app with hot reload — on every file change it rebuilds (unminified, with
sourcemaps) and refreshes the browser
- uses the same bundler as the production build, so there are no dev-only surprises
- defaults to port
5173 with the reload channel on 24678; set them via
define_config(port = ..., hmr_port = ...)
You can also let rbs restart a target on changes from the outside:
Production builds
mode = "production" (the default) produces an optimized bundle in dist/ — minified,
tree-shaken, with content-hashed assets — and a preview server so you can run the built
app locally:
Build behavior (target, minifier, sourcemaps, output directory, base path…) is tuned
through define_config:
vite_app attributes
vite_dev_server is a standalone rule with the same attributes (minus mode, port, and
server_deps) for when you want a dev-server-only target.
rollup is the default, battle-tested engine. rolldown (the Rust engine behind Vite 8)
is available as an opt-in via bundler = "rolldown" and should be considered
experimental.
CSS: SCSS, PostCSS, Tailwind
Plain CSS and CSS Modules (*.module.css) work with zero configuration. For
preprocessors and Tailwind, pass a css_config():
A tailwind.config.js (or .ts) and postcss.config.js in the package are picked up
automatically. Remember to enable the matching tool versions in
web_js_bundle_toolchain (tailwind_version, sass_version, …).
Shared libraries
vite_library shares components between apps at the source level — the consuming app
compiles the library’s TS/TSX together with its own code, so there is no separate library
build step and tree shaking sees the whole graph.
Consumers declare the library under an import scope and import it like an npm package:
Any npm packages the library declares in its deps are forwarded to the consuming app —
you don’t repeat them.
Testing and coverage
Component tests use vitest_test — real Vitest in a jsdom environment with V8 lcov
coverage:
Set min_line_coverage / min_branch_coverage / min_function_coverage on the target to
gate coverage in CI. Tests that exercise a vite_library declare it via the same
lib_deps scopes as the app. Full attribute reference and setup:
Component testing with vitest_test.
Micro-frontends with Module Federation
The mf_host and mf_remote rules build micro-frontend architectures on the official
@module-federation/enhanced runtime — with runtime version negotiation, shared-dependency
singletons, and interoperability with Webpack/Rspack/Vite federation apps. Both support
development and production modes, using the same bundler pipeline in each.
A remote exposes modules; a host (shell) loads them by URL:
Run the whole system in one command:
shared declares the packages every micro-frontend must resolve to a single instance
(React being the classic case) — each entry becomes a singleton with a
^version requirement, enforced by the federation runtime at load time.
For production targets, pass options = build_options(mode = "prod", minify = True)
(loaded from @rbs//web-vite/federation.rbs).
mf_host attributes
mf_remote attributes
mf_remote shares the attributes above (defaults: port = 3001, hmr_port = 24681),
minus remotes, plus:
Server-side rendering
mf_ssr_custom builds a federation-aware SSR app where you own the server: it bundles
a client build and a server build from your own server entry (e.g. an Express or NestJS
app that renders React on the server), and wires up dev mode with hot reload.
It accepts the federation attributes (remotes, shared, options, css_config, …)
plus server_entry, server_srcs, server_tsconfig, server_externals (packages kept
external in the server bundle), and static_files_path.
TypeScript configuration
The web-vite rules generate and own the TypeScript configuration at every level:
- Each build writes a self-contained
tsconfig into the staged build tree, configured for
the bundler (moduleResolution: "bundler", jsx: "react-jsx", path mappings for your
lib_deps scopes).
- Builds also generate an editor config (under
.rbs/lsp/) so the IDE resolves npm
packages, JSX, and @scope/scope library imports on a cold checkout — no build-first,
no manual setup.
To adjust compiler behavior, use the target’s attributes:
Don’t hand-maintain a parallel tsconfig.json that contradicts the generated
configuration (e.g. different jsx or moduleResolution settings) and expect it to drive
the build — the generated configs win, and fighting them typically shows up as phantom
JSX or import errors in the editor. Express customizations through ts_compiler_options
or the tsconfig attribute so the generated configs incorporate them.