Skip to content

feat(npm): enable symbolic macros for node_modules on Bazel 9+ - #2936

Closed
kirkobyte wants to merge 2 commits into
aspect-build:mainfrom
kirkobyte:kirkh/rules-js-pr-2666-ca0c08
Closed

feat(npm): enable symbolic macros for node_modules on Bazel 9+#2936
kirkobyte wants to merge 2 commits into
aspect-build:mainfrom
kirkobyte:kirkh/rules-js-pr-2666-ca0c08

Conversation

@kirkobyte

Copy link
Copy Markdown

Summary

Continues work from #2666. On Bazel 9+, the generated defs.bzl now wraps store invocations and link-package targets in symbolic macros (_all_stores, _npm_link_all_packages), preparing for future lazy macro evaluation. On Bazel 7 and 8, the generator emits the original legacy-macro code unchanged.

Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: no (no user-facing docs change)
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below: yes

Release notes: On Bazel 9+, npm_link_all_packages now uses symbolic macros for store invocations and link-package target creation, enabling visibility encapsulation and preparing for future lazy macro evaluation.

What changed

  • Version detection: Uses bazel_features.globals.macro and hasattr(attr, "label_list_dict") to detect symbolic macro support. This activates on Bazel 9+ only (macro() exists on Bazel 8 but attr.label_list_dict does not).
  • Conditional code generation: The npm_translate_lock code generator emits different defs.bzl content depending on the Bazel version:
    • Bazel 9+: Store calls wrapped in _all_stores = macro(...), link targets wrapped in _npm_link_all_packages = macro(...)
    • Bazel 7/8: Original inline code (no behavioral change)
  • FP store template: Added _FP_STORE_MACRO_TMPL with correct 4-space indentation for symbolic macro impl function bodies (fixes an indentation bug in the original PR).
  • BUILD dep: Added @bazel_features//:features to npm_translate_lock_generate bzl_library deps.

Fixes over #2666

  • Gates symbolic macro usage on Bazel version so Bazel 7/8 are not broken
  • Fixes first-party store template indentation (8→4 spaces for macro impl body)
  • Adjusts to_dict_attr indent depth to match macro nesting level

Test plan

  • bazel build //:node_modules passes on Bazel 7.7.1, 8.2.1, and 9.0.0
  • All write_npm_translate_lock_* snapshot tests pass on Bazel 7 (the repo's default .bazelversion)
  • Snapshot tests on Bazel 9 show expected diff (symbolic macro code vs inline code)

Continues work from aspect-build#2666. The generated defs.bzl now wraps store
invocations in `_all_stores` and link-package targets in
`_npm_link_all_packages` symbolic macros when the Bazel version supports
them (`macro()` global + `attr.label_list_dict`), which is Bazel 9+.

On Bazel 7 and 8 the generator emits the original legacy-macro code
unchanged, so there is no behavioral difference for those versions.

Key fixes over the original PR (aspect-build#2666):
- Version-gate via `bazel_features.globals.macro` and
  `hasattr(attr, "label_list_dict")` so Bazel 7/8 are not broken.
- Fix first-party store template indentation for the macro impl body
  (4-space instead of 8-space).
- Adjust `to_dict_attr` indent depth to match macro nesting level.

Tested: `bazel build //:node_modules` passes on Bazel 7.7.1, 8.2.1,
and 9.0.0.  All `write_npm_translate_lock_*` snapshot tests pass on
Bazel 7 (the repo's default version).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29d234a0f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if scope_targets:
for scope, scoped_targets in scope_targets.items():
_js_library(
name = "node_modules/{}".format(scope),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep scoped aggregators out of the symbolic macro

On Bazel 9, any importer with scoped packages reaches this branch and declares targets such as node_modules/@fastify from a symbolic macro whose name is node_modules; Bazel's symbolic macro naming rule only permits targets equal to the macro name or prefixed with node_modules_, node_modules-, or node_modules., so these scoped aggregate targets are declared but cannot be built or depended on. This regresses existing supported uses such as the scope aggregate deps in npm/private/test/BUILD.bazel (:node_modules/@fastify, :node_modules/@figma, etc.), while the Bazel 7/8 legacy path remains unaffected.

Useful? React with 👍 / 👎.

Scoped aggregate targets like `node_modules/@types` use `/` as a
separator, which is not allowed by Bazel's symbolic macro naming rules
(only `_`, `-`, `.` are valid).  This caused build failures on Bazel 9:

  Target //:node_modules/@types declared in symbolic macro
  'node_modules' violates macro naming rules and cannot be built.

Keep the `_all_stores` symbolic macro (its `.aspect_rules_` prefix is
valid) but emit the scoped and `node_modules` js_library targets inline,
the same way legacy macros do.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kirkobyte

Copy link
Copy Markdown
Author

Closing — the _all_stores symbolic macro works, but the main invalidation benefit requires wrapping link targets too, which is blocked by the symbolic macro naming constraint (/ is not a valid separator). Need to design a __-naming API to make this viable. See discussion in PR for details.

@kirkobyte kirkobyte closed this Jul 23, 2026
@jbedard

jbedard commented Jul 23, 2026

Copy link
Copy Markdown
Member

So that would require breaking our :node_modules/{pkg} convention completely? Due to what codex said:

Bazel's symbolic macro naming rule only permits targets equal to the macro name or prefixed with node_modules_, node_modules-, or node_modules.
?

I wonder if we can convince the bazel team to add / to that list...

@kirkobyte

Copy link
Copy Markdown
Author

I imagine it's there because it can conflict with the other / symbols, e.g. how do we tell the difference between the two slashes in :node_modules/lodash/sort and would supporting / in the name open the door to conflicts?

@jbedard

jbedard commented Jul 28, 2026

Copy link
Copy Markdown
Member

Scoped packages such as :node_modules/@angular/core already have multiple slashes...

@kirkobyte

Copy link
Copy Markdown
Author

I’m meaning to refer to this concept and whether / is disallowed to remove label ambiguity: https://bazel.googlesource.com/bazel/+/5f083b8f5a073a3427d575aa54eac5b779226708/site/docs/guide.md

In addition, Bazel allows a slash to be used instead of the colon required by the label syntax; this is often convenient when using Bash filename expansion. For example, foo/bar/wiz is equivalent to //foo/bar:wiz (if there is a package foo/bar) or to //foo:bar/wiz (if there is a package foo).

But I’d definitely avoid modifying all the paths if it is possible to allow / in the names

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants