I want to setup the bazel build system to build a monorepo with multiple JavaScript packages with the new rules_js rule set. The documentation of rules_js says that it supports "nested npm packages in a monorepo" via "workspaces", but I don't understand how to wire everything up so that:
I tried using @npm//$DEPENDENCY
in the deps
of rules like js_library
to refer to my npm dependencies (like with the older rules_nodejs ruleset), but I just get errors about non-existing targets.
Set up a pnpm workspace and use npm_link_all_packages
multiple times to set up separate node_modules
folders in your bin tree. Then refer to the dependencies in these node_modules
folders by :node_modules/$DEPENDENCY
in your deps
.
rules_js uses the npm_link_all_packages
rule to set up node_modules
folders based on a pnpm-lock.yaml
lockfile. This is similar to the pnpm install
command of the pnpm package manager. The difference is that pnpm install
creates the node_modules
folders in the source tree and npm_link_all_packages
creates them in the bin tree.
Since you want that each package can have different external dependencies, they each need to have their own node_modules
. pnpm supports a single lock file for multiple packages if you set up a pnpm workspace. The resulting lock file can be used by rules_js
to set up a Bazel workspace with multiple packages that each get their own node_modules
folder in the bin tree. The npm_link_all_packages
rule will automatically set up the correct node_modules
folder based on the Bazel package name and the pnpm-lock.yaml
.
So you have to set up a pnpm workspace and use npm_link_all_packages
multiple times to set up separate node_modules
folders in your bin tree.
To achieve this, you can put the following pieces in your workspace root:
pnpm-workspace.yaml
to configure the list of pnpm workspace packagespnpm-lock.yaml
to record how all all direct and indirect npm dependencies are resolvedWORKSPACE.bazel
to setup rules_js
package.json
to avoid confusing pnpm
, and in case you want global scripts or settings for your IDE or similarBUILD.bazel
to create the virtual store with npm_link_all_packages
And in each pnpm workspace package $PACKAGE
you put:
$PACKAGE/package.json
to configure the direct npm dependencies of $PACKAGE
$PACKAGE/BUILD.bazel
to create the node_modules folder
in the bin tree with npm_link_all_packages
, and for your js_library
or similarSo every pnpm workspace package is also a bazel package (since it has a BUILD.bazel
).
deps
?In the deps
of js_library
and similar, you can point to the dependencies in the node_modules
folders generated by npm_link_all_packages
. Typically, the js_library
is in the same Bazel package as the npm_link_all_package
, so you can just use :node_modules/$DEPENDENCY
. If the js_library
is in a sub-package of $PACKAGE
, you can use //$PACKAGE:node_modules/$DEPENDENCY
instead. Then in your JavaScript files, you can import from "$DEPENDENCY"
or require("$DEPENDENCY")
. This will work out at runtime because the node_modules
folder will be at an appropriate place in the runfiles for node
to find it.
If you want one of your packages $PACKAGE
to depend on another one of your packages $OTHER
, you put a "$OTHER": "workspace:*"
into the $PACKAGE/package.json
as usual with pnpm. You also have to make sure that the default target of $OTHER
(for example, //some/path/to/other:other
) is an npm_package
rule. Then you can use :node_modules/$OTHER
or //$PACKAGE:node_modules/$OTHER
in the deps of a js_library
or similar to refer to $OTHER
just like you would refer to an npm dependency.
pnpm-lock.yaml
?Note that this is a working pnpm setup with some additional BUILD.bazel
files, so you can:
pnpm install --lockfile-only
to create the pnpm-lock.yaml
pnpm install
to create the pnpm-lock.yaml
and also create node_modules
folders in the source tree. These folders are not needed for rules_js
but can be helpful for your IDE.