Implementation details for js_run_devserver rule



Runs a devserver via binary target or command.

A simple http-server, for example, can be setup as follows,

load("@aspect_rules_js//js:defs.bzl", "js_run_devserver")
load("@npm//:http-server/package_json.bzl", http_server_bin = "bin")

    name = "http_server",

    name = "serve",
    args = ["."],
    data = ["index.html"],
    tool = ":http_server",

A Next.js devserver can be setup as follows,

    name = "dev",
    args = ["dev"],
    command = "./node_modules/.bin/next",
    data = [

where the ./node_modules/.bin/next bin entry of Next.js is configured in npm_translate_lock as such,

    name = "npm",
    bins = {
        # derived from "bin" attribute in node_modules/next/package.json
        "next": {
            "next": "./dist/bin/next",
    pnpm_lock = "//:pnpm-lock.yaml",

and run in watch mode using ibazel with ibazel run //:dev.

The devserver specified by either tool or command is run in a custom sandbox that is more compatible with devserver watch modes in Node.js tools such as Webpack and Next.js.

The custom sandbox is populated with the default outputs of all targets in data as well as transitive sources & npm links.

An an optimization, virtual store files are explicitly excluded from the sandbox since the npm links will point to the virtual store in the execroot and Node.js will follow those links as it does within the execroot. As a result, rules_js npm package link targets such as //:node_modules/next are handled efficiently. Since these targets are symlinks in the output tree, they are recreated as symlinks in the custom sandbox and do not incur a fully copy of the underlying npm packages.

Supports running with ibazel. Only data files that change on incremental builds are synchronized when running with ibazel.


A unique name for this target.


Working directory to run the binary or test in, relative to the workspace.

By default, js_binary runs in the root of the output tree.

To run in the directory containing the js_binary use

chdir = package_name()

(or if you're in a macro, use native.package_name())

WARNING: this will affect other paths passed to the program, either as arguments or in configuration files, which are workspace-relative.

You may need ../../ segments to re-relativize such paths to the new working directory. In a BUILD file you could do something like this to point to the output path:

    chdir = package_name(),
    # ../.. segments to re-relative paths from the chdir back to workspace;
    # add an additional 3 segments to account for running js_binary running
    # in the root of the output tree
    args = ["/".join([".."] * len(package_name().split("/")) + "$(rootpath //path/to/some:file)"],


The devserver command to run.

For example, this could be the bin entry of an npm package that is included in data such as ./node_modules/.bin/next.

Using the bin entry of next, for example, resolves issues with Next.js and React being found in multiple node_modules trees when next is run as an encapsulated js_binary tool.

Only one of command or tool may be specified.


When True, data files and the entry_point file are copied to the Bazel output tree before being passed as inputs to runfiles.

Defaults to True so that a js_binary with the default value is compatible with js_run_binary with use_execroot_entry_point set to True, the default there.

Setting this to False is more optimal in terms of inputs, but there is a yet unresolved issue of ESM imports skirting the node fs patches and escaping the sandbox: This is hit in some popular test runners such as mocha, which use native import() statements ( When set to False, a program such as mocha that uses ESM imports may escape the execroot by following symlinks into the source tree. When set to True, such a program would escape the sandbox but will end up in the output tree where node_modules and other inputs required will be available.


Runtime dependencies of the program.

The transitive closure of the data dependencies will be available in the .runfiles folder for this binary/test.

You can use the @bazel/runfiles npm library to access these files at runtime.

npm packages are also linked into the .runfiles/node_modules folder so they may be resolved directly from runfiles.


Whether runfiles are enabled in the current build configuration.

Typical usage of this rule is via a macro which automatically sets this attribute based on a config_setting rule.


The main script which is evaluated by node.js.

This is the module referenced by the require.main property in the runtime.

This must be a target that provides a single file or a DirectoryPathInfo from @aspect_bazel_lib//lib::directory_path.bzl.

See for more info on creating a target that provides a DirectoryPathInfo.


Environment variables of the action.

Subject to $(location) and make variable expansion.


The expected exit code.

Can be used to write tests that are expected to fail.


When True, declarations and transitive_declarations from JsInfo providers in data targets are included in the runfiles of the target.

Defaults to false since declarations are generally not needed at runtime and introducing them could slow down developer round trip time due to having to generate typings on source file changes.


When True, npm is included in the runfiles of the target.

An npm binary is also added on the PATH so tools can spawn npm processes. This is a bash script on Linux and MacOS and a batch script on Windows.

A minimum of rules_nodejs version 5.7.0 is required which contains the Node.js toolchain changes to use npm.


When True, files in npm_linked_packages and transitive_npm_linked_packages from JsInfo providers in data targets are included in the runfiles of the target.

transitive_files from NpmPackageStoreInfo providers in data targets are also included in the runfiles of the target.


When True, transitive_sources from JsInfo providers in data targets are included in the runfiles of the target.


Set the logging level.

Log from are written to stderr. They will be supressed on success when running as the tool of a js_run_binary when silent_on_success is True. In that case, they will be shown only on a build failure along with the stdout & stderr of the node tool being run.


Options to pass to the node invocation on the command line.

These options are passed directly to the node invocation on the command line. Options passed here will take precendence over options passed via the NODE_OPTIONS environment variable. Options passed here are not added to the NODE_OPTIONS environment variable so will not be automatically picked up by child processes that inherit that enviroment variable.


Patch the to Node.js fs API ( for this node program to prevent the program from following symlinks out of the execroot, runfiles and the sandbox.

When enabled, js_binary patches the Node.js sync and async fs API functions lstat, readlink, realpath, readdir and opendir so that the node program being run cannot resolve symlinks out of the execroot and the runfiles tree. When in the sandbox, these patches prevent the program being run from resolving symlinks out of the sandbox.

When disabled, node programs can leave the execroot, runfiles and sandbox by following symlinks which can lead to non-hermetic behavior.

When True, the --preserve-symlinks-main flag is passed to node.

This prevents node from following an ESM entry script out of runfiles and the sandbox. This can happen for .mjs ESM entry points where the fs node patches, which guard the runfiles and sandbox, are not applied. See for more information. Once #362 is resolved, the default for this attribute can be set to False.

This flag was added in Node.js v10.2.0 (released 2018-05-23). If your node toolchain is configured to use a Node.js version older than this you'll need to set this attribute to False.

See for more information.


The devserver binary target to run.

Only one of command or tool may be specified.