Skip to main content
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.