rules_storybook
Bazel rules for Storybook: hermetic build, deterministic story manifest, sandbox-escaping dev runner.
| Latest | 0.2.0 |
|---|---|
| Versions | 2 |
| Category | Bazel rules |
| Compat level | 1 |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_storybook/ |
| Source | github.com/tomato-bazel/rules_storybook |
bazel_dep(name = "rules_storybook", version = "0.2.0")
View source & releases on GitHub ↗
Bazel rules for Storybook. Hermetic
storybook build, deterministic story-manifest generation, plus a
storybook dev runner that escapes the sandbox for the HMR-driven dev
loop.
storybook_build— runsstorybook buildas a Bazel action; output is thestorybook-static/tree. ForcesSTORYBOOK_DISABLE_TELEMETRY=1+ reproducibility env vars (TZ=UTC,SOURCE_DATE_EPOCH=0).storybook_manifest— emits a deterministic JSON manifest of story file paths. Replaces the non-deterministicglobin.storybook/main.ts’sstories: [...]config; adding a story file now triggers an explicit Bazel-tracked input change.storybook_dev—bazel run //path:devmacro: runspnpm exec storybook devagainst the live workspace source. Intentionally non-hermetic (HMR + watch + Vite on-demand optim need filesystem access outside the runfiles tree).
See docs/defs.md for the full rule reference.
Install
Add the registry to your .bazelrc:
common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/
In your MODULE.bazel:
bazel_dep(name = "rules_storybook", version = "0.1.0")
You’ll also need npm linking that exposes storybook as a js_binary-compatible target (typically via aspect_rules_js’s :node_modules/storybook/dir).
Quick start
load("@rules_storybook//storybook:defs.bzl", "storybook_build", "storybook_dev", "storybook_manifest")
# 1. The build (hermetic, CI-friendly).
storybook_build(
name = "storybook",
srcs = glob(["stories/**/*", ".storybook/**/*"]),
deps = [
"//packages/some-lib:lib",
":node_modules/storybook",
":node_modules/react",
],
storybook_bin = ":node_modules/storybook/dir",
)
# 2. Manifest (drives .storybook/main.ts deterministically).
storybook_manifest(
name = "stories_manifest",
srcs = glob(["stories/**/*.stories.tsx"]),
relative_to = ".storybook",
)
# 3. Dev server (escapes the sandbox; bazel run -> pnpm exec storybook dev).
storybook_dev(
name = "dev",
port = "6006",
)
Consume the manifest from .storybook/main.ts:
import manifest from "./stories-manifest.json";
export default {
stories: manifest.stories,
// ...
};
Hermeticity
storybook_build runs in a Bazel sandbox with:
STORYBOOK_DISABLE_TELEMETRY=1— no phone-home.NODE_ENV=production— production code paths.TZ=UTC+SOURCE_DATE_EPOCH=0— deterministic time-sensitive bundle metadata + emitted-artifact mtimes.
Determinism caveats:
- All deps must be explicit. Storybook resolves modules via the package-local
node_moduleslink, sodepsmust list every workspace lib whose stories live under yoursrcs, plus every npm dep those stories pull in. - The
storybook_manifestrule expects pre-sorted srcs (handled internally — paths are lexically sorted before write) and re-emits only*.stories.ts(x)entries, so over-approximating thesrcsglob is safe.
storybook_dev is not hermetic by design — Storybook’s dev server needs HMR + Vite on-demand bundle optim, both of which require live filesystem access outside the runfiles tree.
Compatibility
- Bazel: 7.4+, bzlmod required.
- Storybook: 7+ tested. Earlier versions may work;
--output-dir+--quietflag contracts have been stable. - Workspace shape: assumes
pnpmforstorybook_dev(shells out topnpm exec storybook dev).
Contributing
Reference docs are stardoc-generated. After editing rule docstrings:
bazel run //docs:update
CI gates this via bazel test //docs/....
License
MIT.
Rules & providers#
Generated with Stardoc from the module's .bzl sources.
from docs/defs.md
User-facing rules for rules_storybook.
Three pieces:
storybook_build— runsstorybook buildas a Bazel action with explicit srcs + deps; output is thestorybook-static/tree. ForcesSTORYBOOK_DISABLE_TELEMETRY=1+ reproducibility env vars (TZ=UTC,SOURCE_DATE_EPOCH=0) so the action is deterministic across runners.storybook_manifest— emits a deterministic JSON manifest of story file paths..storybook/main.tsimports the manifest instead of globbing, so adding a story triggers an explicit Bazel-tracked input change.storybook_dev— sh_binary macro:bazel run //path:devinvokespnpm exec storybook devagainst the live workspace source (HMR-friendly; not hermetic — intentional, for the dev loop).
Targets returning StorybookBuildInfo expose the build’s output
tree programmatically so future rules (deploy targets, doc-site
extractors) can consume builds without re-running storybook.
storybook_build
load("@rules_storybook//storybook:defs.bzl", "storybook_build")
storybook_build(name, deps, srcs, data, storybook_bin)
Run storybook build and emit storybook-static as a tree artifact.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Workspace + npm targets imported by stories. Brought into runfiles. | List of labels | optional | [] |
| srcs | All Storybook config + story files. | List of labels | required | |
| data | Additional runtime files (e.g. service worker bundle). | List of labels | optional | [] |
| storybook_bin | Storybook CLI target, typically :node_modules/storybook/dir. | Label | required |
storybook_manifest
load("@rules_storybook//storybook:defs.bzl", "storybook_manifest")
storybook_manifest(name, srcs, out, relative_to)
Emit a deterministic JSON manifest of Storybook story files.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| srcs | Story files to enumerate. May over-approximate; only *.stories.ts(x) entries land in the manifest. | List of labels | required | |
| out | Output filename. Defaults to <name>.json. | String | optional | "" |
| relative_to | Package-relative root that consumed paths should be expressed against (matches Storybook’s stories: ['../foo.tsx'] convention from .storybook/main.ts). | String | optional | ".storybook" |
StorybookBuildInfo
load("@rules_storybook//storybook:defs.bzl", "StorybookBuildInfo")
StorybookBuildInfo(tree)
A storybook build output tree.
FIELDS
| Name | Description |
|---|---|
| tree | Directory: the storybook-static output. |
storybook_dev
load("@rules_storybook//storybook:defs.bzl", "storybook_dev")
storybook_dev(name, port, **kwargs)
bazel run //...:NAME invokes pnpm exec storybook dev.
Escapes the runfiles sandbox to run against the live workspace
source for HMR. Intentionally NOT hermetic — that’s storybook_build’s
job. This macro is the dev-loop counterpart.
PARAMETERS
| Name | Description | Default Value |
|---|---|---|
| name | target name. | none |
| port | dev server port. Defaults to 6006. | "6006" |
| kwargs | forwarded to the underlying sh_binary (e.g. tags). | none |
Conformance#
No gate findings. 17 contested atoms. See how gating works or the full report.
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
aspect_bazel_lib | 2.22.5 | 2.8.1 ×1 |
aspect_rules_js | 3.1.2 | 2.1.3 ×1 |
bazel_lib | 3.2.2 | 3.0.0 ×12 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
gawk | 5.3.2.bcr.3 | 5.3.2.bcr.1 ×12 |
jq.bzl | 0.4.0 | 0.1.0 ×12 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
package_metadata | 0.0.2 | 0.0.5 ×3 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_nodejs | 6.7.4 | 6.3.0 ×16.7.3 ×1 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
tar.bzl | 0.10.4 | 0.5.1 ×110.6.0 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
yq.bzl | 0.3.4 | 0.1.1 ×12 |
Dependencies#
Depends on
Versions#
2 published versions, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.2.0 latest | 9KIdIOWiniS5KEkV… | tag archive ↗ |
0.1.0 | IAf2Wor6yE4FzqAe… | tag archive ↗ |
Changelog#
All notable changes to rules_storybook. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.
0.1.0 — initial release
- Initial release of Bazel rules for [Storybook]: hermetic
storybook buildplus a deterministic story-manifest generator and a sandbox-escapingstorybook devrunner for the HMR loop. storybook_buildrunsstorybook buildas a Bazel action withSTORYBOOK_DISABLE_TELEMETRY=1,TZ=UTC, andSOURCE_DATE_EPOCH=0forced for reproducibility; output is thestorybook-static/tree.storybook_manifestemits a deterministic JSON manifest of story file paths so.storybook/main.ts’sstories: [...]no longer relies on a non-deterministicglob; adding a story now triggers an explicit Bazel-tracked input change.storybook_devmacro provides abazel runentry point forpnpm exec storybook devagainst the live workspace source (intentionally non-hermetic — HMR + watch + on-demand Vite optim).