chore(deps): update pre-commit hook commitizen-tools/commitizen to v4 (#2680)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[commitizen-tools/commitizen](https://redirect.github.com/commitizen-tools/commitizen)
| repository | major | `v3.31.0` → `v4.12.1` |

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>commitizen-tools/commitizen
(commitizen-tools/commitizen)</summary>

###
[`v4.12.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4121-2026-01-22)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.12.0...v4.12.1)

##### Fix

- **pre-commit-hooks**: remove magic constants on pre-push hook
([#&#8203;1815](https://redirect.github.com/commitizen-tools/commitizen/issues/1815))

###
[`v4.12.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4120-2026-01-19)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.6...v4.12.0)

##### Feat

- **prek**: supporting prek as an alternative to pre-commit and
switching to prek
([#&#8203;1799](https://redirect.github.com/commitizen-tools/commitizen/issues/1799))

###
[`v4.11.6`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4116-2026-01-15)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.5...v4.11.6)

##### Fix

- **git**: commit bodies with carriage returns are correctly split by …
([#&#8203;1780](https://redirect.github.com/commitizen-tools/commitizen/issues/1780))

###
[`v4.11.5`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4115-2026-01-15)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.4...v4.11.5)

##### Fix

- **config**: ensure the actually used config file is correct, better
test coverage
([#&#8203;1784](https://redirect.github.com/commitizen-tools/commitizen/issues/1784))

###
[`v4.11.4`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4114-2026-01-15)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.3...v4.11.4)

##### Fix

- **cli**: move sys.excepthook override to correct line, rename 'type'
parameter, fix no argv test
([#&#8203;1791](https://redirect.github.com/commitizen-tools/commitizen/issues/1791))

###
[`v4.11.3`](https://redirect.github.com/commitizen-tools/commitizen/releases/tag/v4.11.3)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.2...v4.11.3)

#### v4.11.3 (2026-01-13)

##### Fix

- **bump**: fix the issue that changelog\_merge\_prerelease not working
on cz bump

\[master
[`aa82b98`](https://redirect.github.com/commitizen-tools/commitizen/commit/aa82b982)]
bump: version 4.11.2 → 4.11.3
5 files changed, 10 insertions(+), 4 deletions(-)

###
[`v4.11.2`](https://redirect.github.com/commitizen-tools/commitizen/releases/tag/v4.11.2)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.1...v4.11.2)

#### v4.11.2 (2026-01-12)

##### Fix

- **config**: add warning for multiple configuration files and update
documentation
([#&#8203;1773](https://redirect.github.com/commitizen-tools/commitizen/issues/1773))

\[master
[`193859b`](https://redirect.github.com/commitizen-tools/commitizen/commit/193859be)]
bump: version 4.11.1 → 4.11.2
5 files changed, 10 insertions(+), 4 deletions(-)

###
[`v4.11.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4111-2026-01-03)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.11.0...v4.11.1)

##### Fix

- **providers**: normalize package names in uv provider for uv.lock
matching

###
[`v4.11.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4110-2025-12-29)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.10.1...v4.11.0)

##### Feat

- Drop support for Python 3.9 as EOL reached and add Python 3.14 support
- add custom validation

###
[`v4.10.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4101-2025-12-11)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.10.0...v4.10.1)

##### Fix

- **version**: fix the behavior of cz version --major
- **cli**: debug and no\_raise can be used together in sys.excepthook
- **git**: replace lstrip with strip for compatibility issue
- **bump**: remove NotAllowed related to --get-next option, other
related refactoring

##### Refactor

- **version**: rename class member to align with other classes
- **cargo\_provider**: cleanup and get rid of potential type errors
- **bump**: extract option validation and new version resolution to new
functions
- **changelog**: raise NotAllow when file\_name not passed instead of
using assert
- **bump**: rename parameter and variables

##### Perf

- **ruff**: enable ruff rules TC001\~TC006
- add TYPE\_CHECKING to CzQuestion imports

###
[`v4.10.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v4100-2025-11-10)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.9.1...v4.10.0)

##### Feat

- add config option for line length warning
- **conventional\_commits**: allow exclamation in title on BC
- **version**: add the ability to just print major or minor version
- allow `amend!` prefix as created by `git --fixup=reword:<commit>`

##### Fix

- **commands/version**: add missing return
- **test**: set terminal width for cli tests
- **Init**: raise InitFailedError on keyboard interrupt on pre-commit
hook question, simplify logic, remove unreachable code path

##### Refactor

- **bump**: cleanup related to update\_version\_file
- **RestructuredTest**: rename variable, fix typo and remove unnecessary
string copy
- **TomlConfig**: minor cleanups for DX
- **Commit**: refactor \_prompt\_commit\_questions and fix some type
hint
- **hooks**: refactor to improve readability
- **Init**: make project\_info a module and remove self.project\_info
- **BaseConfig**: update docstring, extract factory method and remove
unnecessary variable assignment
- remove self.encoding for better maintainability
- **utils**: make get\_backup\_file\_path to return a path for semantic
correctness
- remove unnecessary class member tag\_format
- **Bump**: remove use of getattr
- **ConventionalCommitsCz**: rewrite message method to make the pattern
more clear
- **cmd**: unnest try except
- **BaseCommitizen**: remove NotImplementedError and make them abstract
method
- **BaseCommitizen**: construct Style object directly to get rid of
potential type error

###
[`v4.9.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v491-2025-09-10)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.9.0...v4.9.1)

##### Fix

- **dependency**: move deprecated to project.dependencies

###
[`v4.9.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v490-2025-09-09)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.8.4...v4.9.0)

##### Feat

- **check**: add check against default branch

##### Fix

- **changelog**: mark get\_smart\_tag\_range as deprecated
- **init**: use pre-push as pre-commit stage
- **init**: use pre-push as pre-commit stage
- **init**: make welcome message easier to read
- **Init**: fix a typo in \_ask\_version\_provider options and remove
unnecessary filter, use named tuple for options
- **ExitCode**: add from\_str in ExitCode and replace parse\_no\_raise
with it
- raise NoVersionSpecifiedError if version is None, and adjust call
sites of get\_version
- **Changelog**: fix \_export\_template variable type
- **Bump**: rewrite --get-next NotAllowed error message for consistency

##### Refactor

- **changelog**: add get\_next\_tag\_name\_after\_version and test, mark
unused for get\_smart\_tag\_range
- **changelog**: simplify logic for get\_oldest\_and\_newest\_rev
- **changelog**: shorten generate\_tree\_from\_commits
- **Init**: remove the variable values\_to\_add and the update\_config
function for readability
- **Init**: remove unnecessary methods from ProjectInfo and refactor
\_ask\_tag
- **Init**: fix unbounded variable in \_ask\_tag\_format
- **init**: remote extra words
- **process\_commit\_message**: better type and early return
- **Init**: extract \_get\_config\_data for readability
- **changelog**: shorten condition expression and early return
- **Changelog**: remove unnecessary intermediate variables for better
readability
- **bump**: use a loop to shorten a series of similar NotAllowed
exceptions
- **Init**: use ternary operator
- **TagRules**: extract tag\_formats property and simplify list
comprehension
- **git**: remove redundant if branch
- **ScmProvider**: replace sorted with max
- **ExpectedExit**: make the constructor more compact
- **ParseArgs**: simplify **call** function body

###
[`v4.8.4`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v484-2025-09-05)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.8.3...v4.8.4)

##### Fix

- members in workspace, use exclude
- cargo workspaces

##### Refactor

- reduce code indentation

###
[`v4.8.3`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v483-2025-06-09)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.8.2...v4.8.3)

##### Fix

- **cli**: update description for deprecate warning
- **commit**: emit deprecated warning of cz commit -s
- **Check**: make parameters backward compatible
- **BaseConfig**: mypy error
- **deprecated**: mark deprecate in v5
- **defaults**: add non-capitalized default constants back and
deprecated warning

##### Refactor

- **jira**: refactor message
- **conventional\_commits**: use TypedDict for answers
- **conventional\_commits**: make schema\_pattern more readable
- do not guess if changelog format is provided
- **check**: compile once and rename variable
- **questions**: type questions with TypedDict
- **bump**: simplify nested if
- **git**: retype get\_commits parameter to make it more friendly to
call sites
- **git**: simplify tag logic
- **bump**: eliminate similar patterns in code
- **bump**: use any to replace 'or' chain
- remove unnecessary bool() and remove Any type from TypedDict get
- **bump**: improve readability and still bypass mypy check
- **commands**: remove unused args, type version command args
- **commit**: type commit args
- **check**: type CheckArgs arguments
- **check**: remove unused argument
- **changelog**: type untyped arguments
- **bump**: TypedDict for bump argument
- make methods protected, better type
- **conventional\_commits**: remove unnecessary checks
- fix mypy output and better type
- **BaseCommitizen**: remove unused process\_commit
- remove `TypeError` handling since `Python >=3.9` is required
- add comment clarifying `no_raise` parsing to `list[int]`
- **cli.py**: add type hints
- **mypy**: remove `unused-ignore`
- **changelog**: better typing, yield
- **cli**: early return and improve test coverage
- **git**: extract \_create\_commit\_cmd\_string
- misc cleanup
- **bump**: clean up
- **bump**: add type for out, replace function with re escape
- **BaseConfig**: use setter
- **changelog**: minor cleanup
- **git**: refactor get\_tag\_names
- **EOLType**: add eol enum back and reorganize methods
- **git**: code cleanup and better test coverage
- **commit**: simplify call
- **version\_scheme**: cleanup
- improve readability and fix typos

##### Perf

- **bump**: avoid unnecessary list construction and rename variable to
avoid confusion
- **tags**: use set

###
[`v4.8.2`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v482-2025-05-22)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.8.1...v4.8.2)

##### Refactor

- **check**: simplify code
- **check**: remove unnecessary variable

###
[`v4.8.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v481-2025-05-22)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.8.0...v4.8.1)

##### Refactor

- **customize**: improve code readability

###
[`v4.8.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v480-2025-05-20)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.7.2...v4.8.0)

##### Feat

- **cli**: add --tag-format argument to changelog command

###
[`v4.7.2`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v472-2025-05-18)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.7.1...v4.7.2)

##### Refactor

- **default**: capitalize all constants and remove unnecessary variable

###
[`v4.7.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v471-2025-05-16)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.7.0...v4.7.1)

##### Fix

- **bump**: don't fail if an invalid version tag is present (fix
[#&#8203;1410](https://redirect.github.com/commitizen-tools/commitizen/issues/1410))
([#&#8203;1418](https://redirect.github.com/commitizen-tools/commitizen/issues/1418))

###
[`v4.7.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v470-2025-05-10)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.6.3...v4.7.0)

##### Feat

- **providers**: add support for `Cargo.lock`

##### Refactor

- **tests**: increase verbosity of variables

###
[`v4.6.3`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v463-2025-05-07)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.6.2...v4.6.3)

##### Fix

- **changelog.py**: cross-platform path handling using os.path.join and
modify the path linter and test parameter
- **changelog.py**: modify the CHANGELOG.md generated by cz bump
--changelog to the right place

###
[`v4.6.2`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v462-2025-05-05)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.6.1...v4.6.2)

##### Fix

- **docs**: fix url link and table formatting in the customization docs
([#&#8203;1399](https://redirect.github.com/commitizen-tools/commitizen/issues/1399))

###
[`v4.6.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v461-2025-05-05)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.6.0...v4.6.1)

##### Fix

- **commit**: use os.unlink to remove temp file

###
[`v4.6.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v460-2025-04-13)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.5.1...v4.6.0)

##### Feat

- **changelog**: expose commit parents' digests when processing commits
- **git**: add parents' digests in commit information

###
[`v4.5.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v451-2025-04-09)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.5.0...v4.5.1)

##### Fix

- print which tag is invalid

###
[`v4.5.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v450-2025-04-04)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.4.1...v4.5.0)

##### Feat

- **init**: set uv to default value if both pyproject.toml and uv.lock
present

##### Fix

- **commands/init**: add missing uv provider to "cz init"

###
[`v4.4.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v441-2025-03-02)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.4.0...v4.4.1)

##### Fix

- **tags**: fixes ImportError on Python >=3.11
([#&#8203;1363](https://redirect.github.com/commitizen-tools/commitizen/issues/1363))
([#&#8203;1364](https://redirect.github.com/commitizen-tools/commitizen/issues/1364))

###
[`v4.4.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v440-2025-03-02)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.3.0...v4.4.0)

##### Feat

- **tags**: adds `legacy_tag_formats` and `ignored_tag_formats` settings

##### Refactor

- **get\_tag\_regexes**: dedup tag regexes definition

###
[`v4.3.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v430-2025-02-28)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.2.2...v4.3.0)

##### Feat

- **providers**: add uv\_provider

###
[`v4.2.2`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v422-2025-02-18)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.2.1...v4.2.2)

##### Fix

- **bump**: manual version bump if prerelease offset is configured

###
[`v4.2.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v421-2025-02-08)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.2.0...v4.2.1)

##### Fix

- **bump**: add debugging to bump

###
[`v4.2.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v420-2025-02-07)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.1.1...v4.2.0)

##### Feat

- draft of the --empty parameter

##### Refactor

- **bump**: rename --empty as --allow-no-commit

###
[`v4.1.1`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v411-2025-01-26)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.1.0...v4.1.1)

##### Fix

- **get-next-bump**: add a test case
- **get-next-bump**: fix to permit usage of --get-next options even when
update\_changelog\_on\_bump is set to true

###
[`v4.1.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v410-2024-12-06)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v4.0.0...v4.1.0)

##### Feat

- **commit**: allow '-- --allow-empty' to create empty commits

###
[`v4.0.0`](https://redirect.github.com/commitizen-tools/commitizen/blob/HEAD/CHANGELOG.md#v400-2024-11-26)

[Compare
Source](https://redirect.github.com/commitizen-tools/commitizen/compare/v3.31.0...v4.0.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/aspect-build/rules_js).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi44NS4xIiwidXBkYXRlZEluVmVyIjoiNDIuODUuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwcyJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1 file changed
tree: 649cae459110eb006797c9aa93061d8e165145f4
  1. .aspect/
  2. .bcr/
  3. .github/
  4. .vscode/
  5. contrib/
  6. docs/
  7. e2e/
  8. examples/
  9. js/
  10. npm/
  11. platforms/
  12. tools/
  13. .bazelignore
  14. .bazelrc
  15. .bazelversion
  16. .editorconfig
  17. .gitattributes
  18. .gitignore
  19. .npmrc
  20. .pre-commit-config.yaml
  21. .prettierignore
  22. .prettierrc
  23. BUILD.bazel
  24. CONTRIBUTING.md
  25. LICENSE
  26. MODULE.bazel
  27. package.json
  28. pnpm-lock.yaml
  29. pnpm-workspace.yaml
  30. README.md
  31. renovate.json
  32. workspace_status.sh
README.md

Bazel rules for JavaScript

This ruleset is a high-performance Bazel integration for JavaScript, based on the pnpm package manager.

  • Lazy: only fetches/installs npm packages needed for the requested build/test targets.
  • Correct: works seamlessly with node.js module resolution. For example there are no pathMapping issues with TypeScript rootDirs.
  • Fast: Bazel's sandbox only sees npm packages as directories, not individual files. https://blog.aspect.build/rulesjs-npm-benchmarks shows benchmarks for fetching, installing, and linking packages under rules_js as well as typical alternatives like npm and yarn.
  • Supports npm “workspaces”: nested npm packages in a monorepo.

Many companies are successfully building with rules_js. If you're getting value from the project, please let us know! Just comment on our Adoption Discussion.

rules_js is just a part Aspect's monorepo developer platform:

Known issues

Installation

Follow instructions from the release you wish to use: https://github.com/aspect-build/rules_js/releases.

Usage

See the documentation in the docs folder and generated API docs at https://registry.bazel.build/docs/aspect_rules_js.

Examples

Basic usage examples can be found under the examples folder.

Note that the examples also rely on code in the /MODULE file in the root of this repo.

The e2e folder also has a few useful examples such as js_image_layer for containerizing a js_binary and js_run_devserver, a generic rule for running a devserver in watch mode with ibazel.

Larger examples can be found in our bazel-examples repository including:

Relationship to rules_nodejs

rules_js is an alternative to the build_bazel_rules_nodejs Bazel module and accompanying npm packages hosted in https://github.com/bazelbuild/rules_nodejs, which is now unmaintained. All users are recommended to use rules_js instead.

rules_js replaces some parts of bazelbuild/rules_nodejs and re-uses other parts:

LayerLegacyModern
Custom rulesnpm:@bazel/typescript, etc.aspect_rules_ts, etc.
Package manager and Basic rulesbuild_bazel_rules_nodejsaspect_rules_js
Toolchain and core providersrules_nodejsrules_nodejs

The common layer here is the rules_nodejs Bazel module, documented as the “core” in https://bazel-contrib.github.io/rules_nodejs/:

It is currently useful for Bazel Rules developers who want to make their own JavaScript support.

That‘s what rules_js does! It’s a completely different approach to making JS tooling work under Bazel.

First, there's dependency management.

  • build_bazel_rules_nodejs uses existing package managers by calling npm install or yarn install on a whole package.json.
  • rules_js uses Bazel's downloader to fetch only the packages needed for the requested targets, then mimics pnpm to lay out a node_modules tree.

Then, there's how a Node.js tool can be executed:

  • build_bazel_rules_nodejs follows the Bazel idiom: sources in one folder, outputs in another.
  • rules_js follows the npm idiom: sources and outputs together in a common folder.

There are trade-offs involved here, but we think the rules_js approach is superior for all users, especially those at large scale. Read below for more in-depth discussion of the design differences and trade-offs you should be aware of. Also see the slides for our Bazel eXchange talk

Design

The authors of rules_js spent four years writing and re-writing build_bazel_rules_nodejs. We learned a lot from that project, as well as from discussions with Rush maintainer @octogonz.

There are two core problems:

  • How do you install third-party dependencies?
  • How does a running Node.js program resolve those dependencies?

And there's a fundamental trade-off: make it fast and deterministic, or support 100% of existing use cases.

Over the years we tried a number of solutions and each end of the trade-off spectrum.

Installing third-party libraries

Downloading packages should be Bazel‘s job. It has a full featured remote downloader, with a content-address-cached (confusingly called the “repository cache”). We now mirror pnpm’s lock file into starlark code, then use only Bazel repository rules to perform fetches and translate the dependency graph into Bazel's representation.

For historical context, we started thinking about this in February 2021 in a (now outdated) design doc and have been working through the details since then.

Running Node.js programs

Fundamentally, Bazel operates out of a different filesystem layout than Node. Bazel keeps outputs in a distinct tree outside of the sources.

Our first attempt was based on what Yarn PnP and Google-internal Node.js rules do: monkey-patch the implementation of require in NodeJS itself, so that every resolution can be aware of the source/output tree difference. The main downside to this is compatibility: many packages on npm make their own assumptions about how to resolve dependencies without asking the require implementation, and you can‘t patch them all. Unlike Google, most of us don’t want to re-write all the npm packages we use to be compatible.

Our second attempt was essentially to run npm link before running a program, using a runtime linker. This was largely successful at papering over the filesystem layout differences without disrupting execution of programs. However, it required a lot of workarounds anytime a JS tool wanted to be aware of the input and output locations on disk. For example, many tools like react-scripts (the build system used by Create React App aka. CRA) insist on writing their outputs relative to the working directory. Such programs were forced to be run with Bazel's output folder as the working directory, and their sources copied to that location.

rules_js takes a better approach, where we follow that react-scripts-prompted workaround to the extreme. We always run JS tools with the working directory in Bazel's output tree. We can use a pnpm-style layout tool to create a node_modules under bazel-out, and all resolutions naturally work.

This third approach has trade-offs.

  • The benefit is that very intractable problems like TypeScript‘s rootDirs just go away. In that example, we filed https://github.com/microsoft/TypeScript/issues/37378 but it probably won’t be solved, so many users trip over issues like this and this. Now this just works, plus results like sourcemaps look like users expect: just like they would if the tool had written outputs in the source tree.
  • The downside is that Bazel rules/macro authors (even genrule authors) must re-path inputs and outputs to account for the working directory under bazel-out, and must ensure that sources are copied there first. This forces users to pass a BAZEL_BINDIR in the environment of every node action. https://github.com/bazelbuild/bazel/issues/15470 suggests a way to improve that, avoiding that imposition on users.

Telemetry & privacy policy

This ruleset collects limited usage data via tools_telemetry, which is reported to Aspect Build Inc and governed by our privacy policy.