| # 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. |