| # use_execroot_entry_point | |
| This page describes the `use_execroot_entry_point` option on `js_run_binary` | |
| and provides guidance on when to use each value. The short version is that | |
| `use_execroot_entry_point=True` sets up a directory layout that is more | |
| friendly to some JavaScript tools, but at the expense of a performance hit and | |
| issues with cross-platform builds. | |
| ## Background | |
| When a `js_binary` is used as a tool in `js_run_binary`, Bazel runs it as a | |
| build action on the exec platform. The execroot is the root of the build | |
| sandbox; beneath it sits `bazel-out/`, which contains output directories for | |
| both the exec and target configurations. The tool's sources can therefore | |
| potentially appear in up to three places: | |
| - **Exec-platform bin** (`bazel-out/<exec-cfg>/bin/`): where build artifacts for | |
| the exec platform land. | |
| - **Runfiles tree** (`bazel-out/<exec-cfg>/bin/path/to/my_binary.runfiles/`): | |
| where the tool's runtime dependencies (including `node_modules`) are | |
| symlinked and made available to the build action. | |
| - **Target-platform bin** (`bazel-out/<target-cfg>/bin/`): where the `srcs` of | |
| the `js_run_binary` action land. This is also the default working directory | |
| for the build action, though it can be adjusted via the `chdir` attribute. | |
| Resolving the same package in more than one location can result in subtle bugs, | |
| so this is a potential danger here given that the same sources can appear in up | |
| to three places. As described below, `use_execroot_entry_point = True` | |
| addresses the problem by keeping all sources in the target-platform bin | |
| directory, but this has downsides. | |
| ## What `use_execroot_entry_point` does | |
| **`use_execroot_entry_point = True` (the current default):** | |
| The entry point used is the one in the target-platform bin | |
| directory--confusingly called the "execroot entry point" even though the | |
| execroot encompasses the whole sandbox. In order for the tool's sources to land | |
| in that directory, they end up being rebuilt for the target platform. With | |
| everything consolidated in `bazel-out/<target-cfg>/bin/`, Node.js sees a single | |
| `node_modules` tree. This can be the right choice for some frameworks such as | |
| Next.js, which expects inputs and outputs to be in the same directory tree. | |
| The tradeoff is that if the exec platform differs from the target platform (for | |
| example, cross-compiling from macOS to Linux), target-platform artifacts such as | |
| native Node.js addons are rebuilt for the target and may fail to run on the exec | |
| platform. | |
| **`use_execroot_entry_point = False`:** | |
| The entry point used is the one from the runfiles tree. All code executed during | |
| the build action runs from the runfiles tree, which avoids cross-platform | |
| issues. However, you must ensure that any code executed during the build (for | |
| example, JavaScript config files for tools like Webpack or Rspack) is a declared | |
| dependency of the `js_binary` tool, not merely a source file passed to | |
| `js_run_binary`. Source files in the `js_run_binary`'s `srcs` will land in the | |
| target-platform bin directory and will therefore not be visible to the tool's | |
| runfiles resolution. | |
| ## Recommendation | |
| We recommend setting `use_execroot_entry_point = False`, unless your tool | |
| requires its config and outputs to be adjacent to each other in the same | |
| directory (such as Next.js for example). If you do this and ensure that all | |
| code executed during the build is declared as a dependency of the `js_binary`, | |
| then your build will work reliably even in cross-platform situations. | |
| To disable `use_execroot_entry_point` by default, pass the build flag: | |
| ``` | |
| --@aspect_rules_js//js:use_execroot_entry_point=False | |
| ``` | |
| You may want to set it in your `.bazelrc` as follows: | |
| ``` | |
| common --@aspect_rules_js//js:use_execroot_entry_point=False | |
| ``` | |
| Individual targets can still override the flag by explicitly setting | |
| `use_execroot_entry_point = True` or `use_execroot_entry_point = False`. | |
| In a future major version, we will likely disable the `use_execroot_entry_point` | |
| behavior by default. | |
| ### Example | |
| A simple demonstration of the recommended way to set up a `js_run_binary` | |
| target is the [Rspack example](../examples/rspack/BUILD.bazel). This particular | |
| case *requires* `use_execroot_entry_point = False`, because otherwise the | |
| cross-platform build in that file would fail as a result of Bazel trying to use | |
| the wrong Rspack binary. Below is the key part, edited slightly for brevity: | |
| ``` | |
| js_library( | |
| name = "rspack_config", | |
| srcs = ["rspack.config.cjs"], | |
| deps = [":node_modules/@rspack/cli"], | |
| ) | |
| bin.rspack( | |
| name = "rspack_build", | |
| srcs = ["rspack_entry.js"], | |
| outs = ["rspack/main.bundle.js"], | |
| chdir = package_name(), | |
| data = [":rspack_config"], | |
| fixed_args = [ | |
| "build", | |
| "--config", | |
| "$$RUNFILES_DIR/$(rlocationpath :rspack_config)", | |
| ], | |
| use_execroot_entry_point = False, | |
| ) | |
| ``` | |
| Note that in this case `bin.rspack()` is a generated macro that creates both | |
| the `js_binary` for Rspack *and* the `js_run_binary` target that runs it. | |
| Key points: | |
| - The `rspack.config.cjs` file is wrapped in a `js_library` and taken as a | |
| `data` dependency of the `js_binary`. This ensures that the config file and | |
| its dependencies are built for the exec platform, which is appropriate | |
| since they will run during the build action. They will land in the runfiles | |
| directory adjacent to the other exec-platform sources, which will allow | |
| module resolution to proceed correctly. | |
| - `chdir = package_name()` causes the working directory to be | |
| `bazel-out/<target-cfg>/bin/rspack`. This is not strictly necessary, but it | |
| is convenient to have the outputs go directly in the build action's current | |
| directory. | |
| - The config file (`rspack.config.cjs`) refers to `process.cwd()`, not | |
| `__dirname`, for specifying the output path. This is key, because the config | |
| file (and therefore `__dirname`) will be in the runfiles directory and not | |
| the output tree. | |
| - We refer to `"$$RUNFILES_DIR/$(rlocationpath :rspack_config)"` in | |
| `fixed_args`. `$(rlocationpath ...)` is evaluated at analysis time and | |
| determines the path to the config file within the runfiles directory. This | |
| argument must go in `fixed_args` rather than `args`, to allow `$RUNFILES_DIR` | |
| to be evaluated at run time. Note the double dollar sign (`$$`) to prevent | |
| the `js_binary` implementation from attempting to evaluate that variable at | |
| analysis time. |