> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# rules_js

> Bazel rules_js usage guide: practical patterns for linking node_modules, running JavaScript programs, packaging, and using npm dependencies in Bazel.

This document covers practical patterns for working with `rules_js` in your Bazel project.
For installation and package management setup, see the [JavaScript & TypeScript overview](/docs/bazel/javascript).

## Link node\_modules

After importing your `pnpm-lock.yaml`, you need to link `npm` packages into a `node_modules` tree that the Node.js runtime can use. If you use [pnpm workspaces](https://pnpm.io/workspaces), the `node_modules` tree contains first-party packages from your monorepo as well as third-party packages from `npm`.

<Note>
  Bazel doesn't use the `node_modules` installed in your source tree, so you don't need to run `pnpm install` before running Bazel commands. This means that changes you make to files under `node_modules` in your source tree aren't reflected in Bazel results.
</Note>

Typically, you link all `npm` packages into the Bazel package containing the `package.json` file.
If you use `pnpm` workspaces, you need to do this for each `npm` package in your monorepo.

In `BUILD.bazel`:

```js theme={null}
load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages()
```

To verify this works, run `bazel build ...`, then check the `bazel-bin` folder.
The output should be something like this:

```bash theme={null}
# the package store
bazel-bin/node_modules/.aspect_rules_js
# symlink into the package store
bazel-bin/node_modules/some_pkg
# If you used pnpm workspaces:
bazel-bin/packages/some_pkg/node_modules/some_dep
```

**API docs:**

* [npm\_import](https://docs.aspect.build/bazel/javascript/aspect_rules_js/npm_extensions#tag-npm_import): Import all packages from the `pnpm-lock.yaml` file, or import individual packages.
* [npm\_link\_package](https://docs.aspect.build/bazel/javascript/aspect_rules_js/npm_defs#function-npm_link_package): Link npm package(s) into the `bazel-bin/[path/to/package]/node_modules` tree so that the Node.js runtime can resolve them.

### pnpm workspaces

If you use pnpm workspaces, define which directories contain packages in a `pnpm-workspace.yaml` file at the root of your repository.

The following example finds all projects under `apps` or `packages` at any depth, and anything directly under `tools`:

```yaml theme={null}
packages:
    - 'apps/**'
    - 'packages/**'
    - 'tools/*'
```

## Source files in bazel-bin

The Node.js module resolution algorithm requires that you co-locate all files—sources, generated code, and dependencies—in a common filesystem tree:

* dependencies in `bazel-bin/[path/to/package]/node_modules`
* generated and source files in `bazel-bin/[path/to/package]`

<Note>
  Note that other Bazel rulesets accommodate tooling by teaching it to mix a source folder and an output folder. This isn't possible with Node.js without breaking compatibility of many tools, so `rules_js` copies sources to `bazel-bin` instead.
</Note>

Custom rules like `js_library` take care of copying their sources to `bazel-bin` automatically. However, this only works when those sources are under the same `BUILD` file as the target that does the copying. If you have a source file in another `BUILD` file, you need to explicitly copy it
with a rule like [`copy_to_bin`](https://docs.aspect.build/bazel/utility/bazel_lib/lib_copy_to_bin).

## Use binaries published to npm

`rules_js` automatically mirrors the `bin` field from the `package.json` file of your `npm` dependencies to a Starlark API you can load from in your `BUILD` file or macro.

For example, if you depend on the `typescript` `npm` package in your root `package.json`, you can access the `tsc` bin entry in a `BUILD`:

```js theme={null}
load("@npm//:typescript/package_json.bzl", typescript_bin = "bin")

typescript_bin.tsc(
    name = "compile",
    srcs = [
        "fs.cts",
        "tsconfig.json",
        "//:node_modules/@types/node",
    ],
    outs = ["fs.cjs"],
    chdir = package_name(),
    args = ["-p", "tsconfig.json"],
)
```

If you depend on the `typescript` npm package from a nested `package.json` such as `myapp/package.json`, load the bin entry from the nested package:

```js theme={null}
load("@npm//myapp:typescript/package_json.bzl", typescript_bin = "bin")
```

Each bin exposes three rules, one for each Bazel command, or verb, that correspond with a given rule API:

* `bazel build`: [js\_run\_binary](/docs/bazel/javascript/aspect_rules_js/js_defs#function-js_run_binary)
* `bazel test`: [js\_test](/docs/bazel/javascript/aspect_rules_js/js_defs#function-js_test)
* `bazel run`:  [js\_binary](/docs/bazel/javascript/aspect_rules_js/js_defs#function-js_binary)

For example:

| Rule         | Underlying Rule | Invoked with  | To              |
| ------------ | --------------- | ------------- | --------------- |
| `tsc`        | `js_run_binary` | `bazel build` | produce outputs |
| `tsc_binary` | `js_binary`     | `bazel run`   | side-effects    |
| `tsc_test`   | `js_test`       | `bazel test`  | assert exit `0` |

<Tip>
  This doesn't cause an eager fetch. Bazel doesn't download the `typescript` package when loading this file, so you can safely write this even in a `BUILD.bazel` file that includes unrelated rules.
</Tip>

### Inspect the npm workspace

To inspect what's in the `@npm` workspace, start with a `bazel query` such as:

```shell theme={null}
bazel query @npm//... --output=location | grep bzl_library
```

The output lists the on-disk locations where you can load `npm` packages.

<Note>
  These queries only work when you pass `generate_bzl_library_targets = True` to `npm_translate_lock`. If you get no results, confirm that the settings in your `MODULE.bazel` file are correct and try again.
</Note>

Run another `bazel query` to view the definition of a specific target:

```shell theme={null}
bazel query --output=build @npm//:typescript_bzl_library
```

This shows the label for loading the "bin" symbol. You can also follow the location on disk to find that file.

## Macros

[Bazel macros](https://bazel.build/rules/macros) are a critical part of making your BUILD files more maintainable. You can think of macros as a way to create your own build system by piping existing tools together, like a Unix pipeline that composes command-line utilities.

Make sure to follow the [Macros section of the Starlark Style Guide](https://bazel.build/rules/bzl-style#macros) when writing a macro, since some anti-patterns can make your BUILD files difficult to change in the future.

As an example, the following `tsc.bzl` file wraps the preceding `typescript_bin.tsc` rule, setting default arguments and the working directory:

```js theme={null}
load("@npm//:typescript/package_json.bzl", typescript_bin = "bin")

def tsc(name, args = ["-p", "tsconfig.json"], **kwargs):
    typescript_bin.tsc(
        name = name,
        args = args,
        # Always run tsc with the working directory in the project folder
        chdir = native.package_name(),
        **kwargs
    )
```

With this macro in place, `BUILD` files become shorter and more consistent:

```js theme={null}
load(":tsc.bzl", "tsc")

tsc(
    name = "two",
    srcs = [
        "tsconfig.json",
        "two.ts",
        "//:node_modules/@types/node",
        "//examples/js_library/one",
    ],
    outs = [
        "two.js",
    ],
)
```

Like custom rules, macros require the Starlark language, but writing a macro is much easier since it only relies on using existing rules together, rather than writing anything from scratch.

Macros can meet most use cases, and save you time so you don't have to learn how to write custom rules. If you're really interested creating your own custom rules, be aware that the expertise required to writing custom rules requires a significant time investment.

### Document macros with Stardoc

You can use [Stardoc](https://github.com/bazelbuild/stardoc) to produce API documentation from Starlark code. Aspect recommends producing Markdown output, and checking those `.md` files into your source repository so they're easy to browse at the same revision as the sources.

As a best practice, create `bzl_library` targets for your Starlark files as it lets users of your code generate their own documentation.

Aspect's `bazel-lib` provides `stardoc_with_diff_test` and `update_docs`, helpers that run Stardoc and verify that your checked-in docs stay up to date.

Continuing the preceding example, that includes a macro in `tsc.bzl`, you'd write this to document it in `BUILD`:

```js theme={null}
load("@bazel_lib//lib:docs.bzl", "stardoc_with_diff_test", "update_docs")
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

bzl_library(
    name = "tsc",
    srcs = ["tsc.bzl"],
    deps = [
        # this is a bzl_library target, exposing the package_json.bzl file we depend on
        "@npm//:typescript",
    ],
)

stardoc_with_diff_test(
    name = "tsc-docs",
    bzl_library_target = ":tsc",
)

update_docs(name = "docs")
```

This snippet declares a `bzl_library` for the `tsc.bzl` macro, then uses `stardoc_with_diff_test` to generate a Markdown doc and fail the build if the checked-in copy is out of date. The `update_docs` target lets you regenerate all docs with `bazel run //:docs`.

Find a similar example in [examples/macro](https://github.com/aspect-build/rules_js/blob/main/examples/macro/BUILD.bazel).

## Debugging

Add the debug settings from the [`rules_nodejs` common.bazelrc](https://github.com/bazel-contrib/rules_nodejs/blob/stable/common.bazelrc) to your project's `.bazelrc` file to enable `--config=debug` for Node.js programs.

For example, you can debug the `js_test` target with:

```shell theme={null}
$ bazel run //examples/js_binary:test_test --config=debug
Starting local Bazel server and connecting to it...
INFO: Analyzed target //examples/js_binary:test_test (65 packages loaded, 1023 targets configured).
INFO: Found 1 target...
Target //examples/js_binary:test_test up-to-date:
  bazel-bin/examples/js_binary/test_test.sh
INFO: Elapsed time: 6.774s, Critical Path: 0.08s
INFO: 6 processes: 4 internal, 2 local.
INFO: Build completed successfully, 6 total actions
INFO: Build completed successfully, 6 total actions
exec ${PAGER:-/usr/bin/less} "$0" || exit 1
Executing tests from //examples/js_binary:test_test
-----------------------------------------------------------------------------
Debugger listening on ws://127.0.0.1:9229/76b4bb42-7d4e-41f6-a7fe-92b57db356ad
For help, see: https://nodejs.org/en/docs/inspector
```

### Debugging with Chrome DevTools

Go to `chrome://inspect/` in Chrome to find the debugging session and connect to it with Chrome DevTools.

See [Debugging Node.js with Chrome DevTools](https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27)
to understand the basics of using the DevTools with Node.

### Debugging with Visual Studio Code

You can add a `.vscode/launch.json` configuration file
to launch into a debugging session directly from the
[Run & Debug](https://code.visualstudio.com/docs/editor/debugging) window.
See the [JavaScript/TypeScript Bazel Starter](https://github.com/aspect-starters/js) for an example.
