Skip to content

fix: collect buildNodeUrl dependencies in a render filter - #810

Open
jkevan wants to merge 7 commits into
mainfrom
fix/autocollect-render-filter
Open

jkevan wants to merge 7 commits into
mainfrom
fix/autocollect-render-filter

Conversation

@jkevan

@jkevan jkevan commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This pull request moves the collection of the buildNodeUrl cache dependencies out of the React renderer. A render filter now registers the collected paths, and the merge above 100 paths no longer covers more than one site.

Why

#783 kept the collected paths in a Set of the React server context. The React renderer returned that set next to the markup, and JSScript read the new {html, autocollectedDependencies} result. Three layers had to change together, and the design had three consequences:

  • A call to buildNodeUrl that passes its own context collected nothing, because that object carries no set. getImageProps and any helper outside a component make this call.
  • A view renderer that returns a string failed in JSScript, because JSScript read a member of an object.
  • Above 100 paths, one regexp on the common root replaced every path. A menu with a link under /sites/x/files merged to /sites/x(/.*)?, and one link under /modules merged to /.*, which matches every path of the repository.

Changes

  • buildNodeUrl calls server.render.autocollectCacheDependency(path, renderContext.getRequest()). The collection needs only the request, so a call that passes its own context collects too. An object that stands in for the render context, as in the test view of fix(library): keep the origin of a URL that encodeURL made absolute #811, works as well.
  • CollectedCacheDependenciesFilter runs at priority 17. In prepare, it pushes a collector for the resource with RenderChain.pushAttribute, and the render chain restores the collector of the parent when a nested render ends. In execute, the filter registers what the collector holds, before the cache filter at 16 stores the fragment.
  • The filter has the conditions of AggregateCacheFilter: live mode, no json, no include, wrapper or option, no fragment cached with its parent, and no render under skip aggregation. A resource the cache does not store gets no collector, so its view collects into the fragment that holds its markup.
  • The filter reads cache.autocollectDependencies from the properties of the view in prepare. NodeAttributesFilter has already resolved the script at priority 16.8. A view that opted out collects nothing.
  • Above 100 paths, the merge takes the deepest folder that covers two entries or more, and repeats until the entries fit. A lone link outside a listing stays exact. No merge goes above /sites/<key>, and a warning reports a result that stays above the limit. The merge drops the paths under /modules, because the core cache filters store no dependency there.
  • The React renderer and JSScript return to the string contract. ServerContext loses the internal autocollectedDependencies field.

Validation

  • mvn -pl javascript-modules-engine-java -am test -Dtest='CollectedCacheDependencies*Test': 24 tests pass. One test generates 500 sets of paths with a fixed seed, and checks the invariants of the merge on each: no path lost, nothing merged within the limit, no merge above /sites/<key>, no redundant entry, and no folder left to merge above the limit. The test fails on each of three bugs injected into the merge. The worst case, 5000 paths in 2500 folders, reduces in 6 ms.
  • mvn -pl javascript-modules-engine-java,javascript-modules-library,javascript-modules-engine -am package -DskipTests: the generated types carry autocollectCacheDependency, and the library and the engine compile against them.
  • cacheDependencyAutocollectTest.cy.ts gains four views that must be flushed and one that must stay cached. The flushed views pass their context explicitly, build the URL in an include of the current node, or build it in a virtual node that Render content renders. The cached view opts out for a single call. The spec passes in the standalone integration tests, on the jahia-ee-dev:8-SNAPSHOT image.

Documentation

The changelog entry of #783 is not released yet. It now names this pull request and states how a view with more than 100 links refreshes.

ADR

None.

buildNodeUrl now hands each linked path to the Java engine, which keeps it
for the resource being rendered. A render filter at priority 17 registers
the paths before the cache filters store the fragment. The React renderer
and JSScript return to the string contract they had before.

- A call that passes its own context now collects, because the collection
  needs only the render context.
- Above 100 paths, the merge groups the paths by site and never builds a
  regexp above /sites/<key>. A lone path of another site stays exact.
- Paths under /modules are dropped, as the core cache filters drop them.
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

🦜 Chachalog

javascript-modules minor
  • buildNodeUrl now autocollects cache dependencies for the content it links to, preventing 404 errors when the linked content is renamed or moved. (feat: autocollect buildNodeUrl dependencies #783, fix: collect buildNodeUrl dependencies in a render filter #810)

    This mechanism is enabled by default but can be disabled on a per-view basis by setting "cache.autocollectDependencies": "false" in the view's properties, or on a per-call basis with the new autocollectDependency: false option in buildNodeUrl.

    When a view links to more than 100 contents, a change in a folder that holds many of them also refreshes the view.

Create a new entry online or run npx chachalog@0.5.6 prompt to create a new entry locally.

@pkg-pr-new

pkg-pr-new Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

yarn add https://pkg.pr.new/@jahia/create-module@810.tgz
yarn add https://pkg.pr.new/@jahia/javascript-modules-library@810.tgz
yarn add https://pkg.pr.new/@jahia/vite-plugin@810.tgz

commit: 7c35385

The filter now pushes a collector for each resource in prepare, with
RenderChain.pushAttribute. A nested render pushes its own collector, and the
render chain restores the collector of the parent when that render ends. This
replaces the request-wide map of the resources.

The view property is read once, at the first collected path, because the
script of the resource is then resolved. A view that opted out stores no path.
@jkevan
jkevan marked this pull request as ready for review October 13, 2026 13:18
Copilot AI balanced review requested due to automatic review settings October 13, 2026 13:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The reducer can exceed its documented dependency limit when paths span many distinct scopes.

2 open findings
What changed in this PR

Moves automatic URL cache-dependency collection into a render filter while restoring string-based renderer results.

Changes:

  • Collects dependencies through the active render context.
  • Reduces dependencies per site and drops module paths.
  • Extends unit and end-to-end coverage.
File Description
tests/​cypress/​e2e/​ui/​cacheDependencyAutocollectTest.cy.ts Tests explicit contexts and per-call opt-out.
javascript-modules-library/​src/​utils/​urlBuilder/​urlBuilder.ts Delegates dependency collection to the render helper.
javascript-modules-library/​src/​hooks/​useServerContext.tsx Removes React-specific collection state.
javascript-modules-engine/​src/​server/​init-react.tsx Restores string renderer results.
javascript-modules-engine-java/​src/​test/​java/​org/​jahia/​modules/​javascript/​modules/​engine/​js/​server/​CollectedCacheDependenciesTest.java Expands collection and reduction tests.
javascript-modules-engine-java/​src/​main/​java/​org/​jahia/​modules/​javascript/​modules/​engine/​views/​JSScript.java Restores the renderer’s string contract.
javascript-modules-engine-java/​src/​main/​java/​org/​jahia/​modules/​javascript/​modules/​engine/​js/​server/​RenderHelper.java Exposes render-context dependency collection.
javascript-modules-engine-java/​src/​main/​java/​org/​jahia/​modules/​javascript/​modules/​engine/​js/​server/​CollectedCacheDependenciesFilter.java Adds request-scoped collection and registration.
javascript-modules-engine-java/​src/​main/​java/​org/​jahia/​modules/​javascript/​modules/​engine/​js/​server/​CollectedCacheDependencies.java Implements scoped collection and grouped reduction.
jahia-test-module/​src/​react/​server/​views/​testAutocollectedCacheDependency/​TestAutocollectedCacheDependency.tsx Adds integration-test views.
.chachalog/​Kp7mQx2R.md Updates the release note.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .chachalog/Kp7mQx2R.md
…the limit needs

The filter now has the conditions of AggregateCacheFilter: live mode, no json,
no include, wrapper or option, no fragment cached with its parent, and no
render under skip aggregation. A resource the cache does not store gets no
collector, so its view collects into the fragment that holds its markup.

The filter reads the opt-out of the view in prepare, because
NodeAttributesFilter has already resolved the script at priority 16.8.

Above 100 paths, the merge takes the deepest folder that covers two entries or
more, and repeats until the entries fit. A lone link outside a listing stays
exact. No merge goes above /sites/<key>, and a warning reports a result that
stays above the limit.
…rent

A view renders a virtual node with Render content, which the render helper
marks to be cached with its parent fragment. The view of the virtual node
builds the image URL, and the rename of the image must flush the parent.

The Javadoc of the filter states that the opt-out of the enclosing view
decides for a view that renders inside its fragment.
A test generates 500 sets of paths over sites, user folders and modules, with
a fixed seed. On each set it checks that no path is lost, that nothing merges
within the limit, that no merge goes above /sites/<key>, that each merged
folder holds two paths or more, that no entry lies inside a merged folder,
and that a result above the limit has no folder left to merge.

The merge now groups the entries once per depth, from the deepest. Two
folders at one depth never overlap, so the result is the same, and 5000
paths in 2500 folders reduce in a few milliseconds.

Other tests cover the boundary at 100 paths, a deeper merge absorbed by a
higher one, an exact path equal to the merged folder, the paths above the
floor, and what register() puts on the resource.
buildNodeUrl reads its context through JavaScript calls only, so a view may
pass an object that stands in for the render context. The test view of #811
does this to imitate a link to another site.

autocollectCacheDependency took a RenderContext, and GraalVM cannot convert
such an object to that Java type, so the render failed. The method now takes
the request, which buildNodeUrl reads with getRequest().
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