Bazel works alongside your existing package manager, either npm or yarn.
You manage your package.json
file, editing by hand or by running commands like npm install
or yarn add
.
The package manager will also write a lock file, indicating exact versions for all transitive dependencies, which keeps your build hermetic and reproducible.
Bazel will run the package manager when the package.json
or *lock.json
files change, but you can also run the package manager yourself.
You have two options for managing your node_modules
dependencies: Bazel-managed or self-managed.
With the Bazel-managed dependencies approach, Bazel is responsible for making sure that node_modules
is
up to date with your package[-lock].json
or yarn.lock
files. This means Bazel will set it up when the
repository is first cloned, and rebuild it whenever it changes. With the yarn_install
or npm_install
repository rules, Bazel will setup your node_modules
for you in an external workspace named after the
repository rule. For example, a yarn_install(name = "npm", ...)
will setup an external
workspace named @npm
with the node_modules
folder inside of it as well as generating targets for each
root npm package in node_modules
for use as dependencies to other rules.
For Bazel to provide the strongest guarantees about reproducibility and the
fidelity of your build, it is recommended that you use Bazel-managed dependencies.
This approach also allows you to use the generated fine-grained npm package dependencies
which can significantly reduce the number of inputs to actions, making Bazel sand-boxing and
remote-execution faster if there are a large number of files under node_modules
.
Note that as of Bazel 0.26, and with the recommended
managed_directories
attribute on theworkspace
rule in/WORKSPACE
, the Bazel-managednode_modules
directory is placed in your workspace root in the standard location used by npm or yarn.
To have Bazel manage its own copy of node_modules
, which is useful to avoid
juggling multiple toolchains, you can add one of the following to your WORKSPACE
file:
Using Yarn (preferred):
load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install")
yarn_install(
name = "npm",
package_json = "//:package.json",
yarn_lock = "//:yarn.lock",
)
Using NPM:
load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")
npm_install(
name = "npm",
package_json = "//:package.json",
package_lock_json = "//:package-lock.json",
)
If you don’t need to pass any arguments to
node_repositories
, you can skip calling that function.yarn_install
andnpm_install
will do it by default.
You should now add the @npm
workspace to the managed_directories
option in the workspace
rule at the top of the file. This tells Bazel that the node_modules
directory is special and is managed by the package manager.
Add the workspace
rule if it isn’t already in your /WORKSPACE
file.
workspace(
name = "my_wksp",
managed_directories = {"@npm": ["node_modules"]},
)
As of Bazel 0.26 this feature is still experimental, so also add this line to the .bazelrc
to opt-in:
common --experimental_allow_incremental_repository_updates
yarn_install
is the preferred rule for setting up Bazel-managed dependencies for a number of reasons:
yarn_install
will use the global yarn cache by default which will improve your build performance (this can be turned off with the use_global_yarn_cache
attribute)You can then reference individual npm packages in your BUILD
rules via:
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
nodejs_binary(
name = "bar",
data = [
"@npm//foo",
"@npm//baz",
]
...
)
In this case, the bar
nodejs_binary depends only the foo
and baz
npm packages
and all of their transitive deps.
For other rules such as jasmine_node_test
, fine grained
npm dependencies are specified in the deps
attribute:
jasmine_node_test(
name = "test",
...
deps = [
"@npm//jasmine",
"@npm//foo",
"@npm//baz",
...
],
)
If your workspace has multiple applications, each with their own package.json
and npm deps, yarn_install
(or npm_install
) can be called separately for
each.
workspace(
name = "my_wksp",
managed_directories = {
"@app1_npm": ["app1/node_modules"],
"@app2_npm": ["app2/node_modules"],
},
)
yarn_install(
name = "app1_npm",
package_json = "//app1:package.json",
yarn_lock = "//app1:yarn.lock",
)
yarn_install(
name = "app2_npm",
package_json = "//app2:package.json",
yarn_lock = "//app2:yarn.lock",
)
Your application would then reference its deps as (for example) @app1_npm//lodash
, or @app2_npm//jquery
.
If an npm package lists one or more bin
entry points in its package.json
,
nodejs_binary
targets will be generated for these.
For example, the protractor
package has two bin entries in its package.json
:
"bin": {
"protractor": "bin/protractor",
"webdriver-manager": "bin/webdriver-manager"
},
These will result in two generated nodejs_binary
targets in the @npm//protractor/bin
package (if your npm deps workspace is @npm
):
@npm//protractor/bin:protractor
@npm//protractor/bin:webdriver-manager
These targets can be used as executables for actions in custom rules or can be run by Bazel directly. For example, you can run protractor with the following:
$ bazel run @npm//protractor/bin:protractor
Note: These targets are in the protractor/bin
package so they don’t
conflict with the targets to use in deps[]. For example, @npm//protractor:protractor
is target to use in deps[] while @npm//protractor/bin:protractor
is the binary target.
Using fine grained npm dependencies is recommended to minimize
the number of inputs to your rules. However, for backward compatibility
there are also filegroups defined by yarn_install
and npm_install
that include all packages under node_modules
and which can be used
with the node_modules
attribute of nodejs rules.
@npm//:node_modules
includes all packages under node_modules
as well as the .bin
folderload("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
nodejs_binary(
name = "bar",
node_modules = "@npm//:node_modules",
)
If you’d like to have Bazel use the node_modules
directory you are managing,
then next you will create a BUILD.bazel
file in your project root containing:
package(default_visibility = ["//visibility:public"])
filegroup(
name = "node_modules",
srcs = glob(
include = ["node_modules/**/*"],
exclude = [
# Files under test & docs may contain file names that
# are not legal Bazel labels (e.g.,
# node_modules/ecstatic/test/public/中文/檔案.html)
"node_modules/test/**",
"node_modules/docs/**",
# Files with spaces are not allowed in Bazel runfiles
# See https://github.com/bazelbuild/bazel/issues/4327
"node_modules/**/* */**",
"node_modules/**/* *",
],
),
)
The example in examples/user_managed_deps
uses self-managed dependencies.
To use the Yarn package manager, which we recommend for its built-in verification command, you can run:
$ bazel run @nodejs//:yarn_node_repositories
If you use npm instead, run:
$ bazel run @nodejs//:npm_node_repositories install
The @nodejs//:yarn_node_repositories
and @nodejs//:npm_node_repositories
targets will run yarn/npm on all of the
package.json contexts listed package_json
attribute of the node_repositories
repository rule in your WORKSPACE file (node_repositories(package_json = [...])
).
If there are multiple package.json contexts in this rule but you would like to run the bazel managed yarn or npm on a single context this can be done using the following targets:
$ bazel run @nodejs//:yarn -- <arguments passed to yarn>
If you use npm instead, run:
$ bazel run @nodejs//:npm -- <arguments passed to npm>
This will run yarn/npm in the current working directory. To add a package with the yarn add
command,
for example, you would use:
$ bazel run @nodejs//:yarn -- add <package>
Note: the arguments passed to bazel run
after --
are forwarded to the executable being run.