Skip to content

Commit 290ed74

Browse files
SaadnajmiCopilot
andcommitted
docs: honest scope/limitations — Bazel for the native slice, not the JS inner loop
Captures the lessons from the pow.rs 'Bazel is incompatible with JavaScript' critique: keep the JS dev loop (yarn/Metro/Jest/debugging) off Bazel, consume artifacts at seams rather than re-Bazelifying node_modules, stay a two-way door (additive/manual), and mind the single-server-per-output_base serialization. Grounds the node_modules friction in our own copy_tree.js/first_party.bzl workarounds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 73b75ac commit 290ed74

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

docs/bazel.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,51 @@ XCFrameworks from source in Bazel too (see the roadmap).
2121
resource) is modeled explicitly, the way Meta does it in Buck2's `js`/`apple`
2222
preludes — but reusing Metro and rules_apple instead of reimplementing them.
2323

24+
## Scope: where Bazel earns its keep here (and where it doesn't)
25+
26+
Bazel is a *poor* choice for the JavaScript inner loop, and the critique in
27+
["Bazel is incompatible with JavaScript" (pow.rs)](https://pow.rs/blog/bazel-is-incompatible-with-javascript/)
28+
is largely fair **for that use case**. We hit its Problem 1 directly: rules_js uses a
29+
strict, **non-hoisted, copied** `node_modules`, which is exactly why this slice needs
30+
`tools/bazel/js/copy_tree.js` (to stage a symlink-free project dir Metro's file-map can
31+
hash) and `first_party.bzl` (to consume first-party packages in built `dist` form
32+
because their `src` entry points `require('../../../scripts/babel-register')` and escape
33+
the copied tree). That is real friction and real disk/IO cost.
34+
35+
The important distinction: **we do not put the JS dev loop on Bazel.** `yarn`, Metro's
36+
dev server, Jest, and JS debugging stay exactly as they are — Bazel is opt-in
37+
(`manual`-tagged targets) and additive, so none of the day-to-day JS workflow is
38+
affected. What Bazel is actually for here is the thing the JS ecosystem tools
39+
(Turborepo/Nx/Lage) *cannot* do: run the **Apple toolchain**, link **XCFrameworks**,
40+
run **codegen**, and assemble a signed **`.app`** — one reproducible, remotely-cacheable
41+
graph over JS **and** native. Our build time is dominated by the native compiles
42+
(Hermes, React C++), which is precisely where Bazel's action cache / RBE pays off; the
43+
JS bundle is a small, leaf step.
44+
45+
Design guardrails we adopt as a result:
46+
47+
* **Keep the JS inner loop off Bazel.** Never make `bazel` a prerequisite for editing JS,
48+
running Metro, or debugging. The article's strongest point.
49+
* **Consume artifacts at seams, don't re-Bazelify the world.** We already treat the
50+
XCFrameworks as prebuilt inputs; the JS bundle can be treated the same way (build it
51+
with plain Metro, feed the `.jsbundle` in) if the rules_js `node_modules` tax ever
52+
outweighs the benefit of an in-graph bundle. Prefer the `:node_modules` glob over
53+
hand-declaring individual packages (the article notes this keeps you correct).
54+
* **Stay a two-way door.** Everything is additive and `manual`; no forced repo-wide
55+
migration. Adoption and *removal* are both cheap.
56+
* **Mind the single Bazel server.** Bazel is massively parallel *within* a build, but one
57+
server per `--output_base` serializes separate `bazel` invocations ("Another Bazel
58+
command is running…"). Use distinct `--output_base`s for parallel lanes/CI shards
59+
rather than expecting two CLIs to share one. (The article's "single-threaded" framing
60+
conflates these — intra-build parallelism is a Bazel strength.)
61+
* **Expect to enumerate outputs.** Declaring every generated file is inherent to Bazel's
62+
model (we hit it with codegen — see `rn_codegen`'s explicit `_CODEGEN_OUTS`). Use
63+
`out_dirs` TreeArtifacts where a step's outputs aren't statically known.
64+
65+
Net: use **JS-native tools (Turborepo/Nx/Lage/pnpm) for the JS package graph and dev
66+
loop**, and **Bazel only for the native app slice** where it's genuinely better. They
67+
coexist — Turborepo can even run inside a Bazel monorepo.
68+
2469
## The single-source-of-truth lockfile problem (and the rules_js Berry fork)
2570

2671
The repo uses **Yarn 4 (Berry)**; `yarn.lock` is `__metadata: version: 8`. We keep

0 commit comments

Comments
 (0)