@@ -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
2671The repo uses ** Yarn 4 (Berry)** ; ` yarn.lock ` is ` __metadata: version: 8 ` . We keep
0 commit comments