feat(library): an image API for JavaScript modules - #746
Conversation
JCRNodeWrapper.getProvider() and JCRStoreProvider.isDefault() exist in core but never reached the published typings: JCRStoreProvider sat in the java-ts-bind blacklist and neither method was whitelisted. Modules that need to know where an asset lives — a local file or an external DAM mount, which decides how a resize reaches it — had to cast their way there. Refs #739
…provider The option was documented as if it worked everywhere, and this JSDoc ships in the published .d.ts. Core's JCRNodeWrapperImpl.getUrl(List) discards its parameters, so on a local /files asset the args produce the plain URL — which reads as a broken API rather than a provider-specific channel. Also points at the thumbnails Jahia pre-generates, the only variants a plain instance serves. Refs #736
Rendering a content image well needs a file sized for its slot, reserved space, a cache dependency and alternative text. None of that existed here, so every module derived it again — and the samples showed the full-resolution original with an empty alt. Three tiers, so each layer can be used on its own: - buildImageUrl picks the channel that actually carries a size for the node at hand: an external provider's decorator (a DAM signs a transformed URL), a pre-generated thumbnail (the only resize a plain instance performs), or the `?w=` query parameters that Media Optimization honours in the Cloud. The chosen channel is returned, so "nothing resizes on my machine" becomes a fact the API states rather than a mystery. - getImageProps derives both `srcSet` and `sizes` from layout intent (constrained | fixed | full-width) plus the slot width, instead of asking every call site to hand-write candidate widths and a sizes expression. It returns plain serializable data, which is also what an Island needs, and requires `alt` — decorative images declare `alt=""` deliberately. - Image renders it: an unstyled <img>, lazy only once the intrinsic dimensions are known, eager and high-priority under `priority`. Refs #735, #743, #744, #738
The monorepo had no unit-test runner. The image logic is pure computation with edge cases worth holding still: channel routing, clamping to the original, no-op detection, candidate de-duplication keeping the smallest width when a provider collapses several onto one rendition, comma encoding for Jahia's srcset rewriter, and the layout-to-sizes derivation. Refs #745
…teach No platform surface mentioned srcset, sizes, resizing or Media Optimization, while the blog tutorial and the hydrogen sample both rendered the full-resolution original with alt="" — the pattern every module author copies. The guide covers layout intent, why sizes exists, priority for the LCP image, required alternative text, images across an Island boundary, and a table of which channel resizes where, including the one that surprises everyone: a plain instance serves identical bytes for every ?w= candidate. Refs #737
📝 Documentation GuidelinesThank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:
This comment is posted automatically when changes are detected in the |
🦜 Chachalog
|
commit: |
`Image` collides with the design-system `Image` that most modules already have, forcing an alias at every import site. `JImage` follows JCRNodeWrapper's J prefix and reads as "the Jahia one". Refs #743
The guide asked developers to declare a slot width without saying why a list of other widths then appears in the markup — the obvious reading being that the library second-guesses the size they just gave it. Names the two kinds of width, says which layouts consult the ladder and which do not, and states the two limits and their reasons: candidates stop at twice the slot width, and start at 320. Refs #737, #744
…he inert resize A module could not tell a developer's instance from a production one, so the library could not say anything only a developer wants to hear. The engine already knows — SettingsBean.isDevelopmentMode() — it just was not on the `server` bridge. Puts it on ConfigHelper (`server.config.isDevelopmentMode()`) and uses it for the worst image trap: on an instance without Media Optimization, `?w=` candidates come back byte-identical, and until now nothing said so — the markup looked right and the bytes never shrank. getImageProps now warns once, naming an example image, pointing at the guide. Once per engine lifetime, not per image: what it reports is a property of the instance, so a second line would repeat the first. Development mode only, and silent about anything it cannot read — a diagnostic that breaks a render is worse than no diagnostic. Refs #748, #737
Summary
Gives the library an image concept, so a module author renders a responsive, accessible, cacheable content image by declaring intent instead of deriving it. First implementation pass on #734.
That one line now produces a
srcsized for the slot,srcSetcandidates, a derivedsizes, intrinsicwidth/height,loading="lazy", and a registered cache dependency.Why
Nothing in the platform mentioned srcset, sizes, resizing or Media Optimization, and both the blog tutorial and the hydrogen sample rendered the full-resolution original with
alt=""— the pattern every module copies. So each module re-derived the same logic (Jahia/luxe-jahia-demo: ~250 lines), including two facts documented nowhere: a size hint reaches an external DAM throughgetUrl(List)args but the default provider through?w=query parameters, and a plain local instance honours neither, returning identical bytes for every candidate.Changes
buildImageUrl(node, { width, height })— picks and reports the channel:provider(a DAM decorator signs a transformed URL),thumbnail(a pre-generated size, the only resize a plain instance performs),query(?w=, honoured by Media Optimization in the Cloud),original(vectors, and no-op resizes). Clamps to the intrinsic size; never upscales.getImageProps(node, { alt, layout, width })— derives bothsrcSetandsizesfrom layout intent (constrained|fixed|full-width) plus the slot width.widths/sizesstay as escape hatches. Returns plain serializable data, so it is also what crosses anIsland;altis required, withalt=""as the deliberate decorative declaration. Registers the cache dependency.<JImage>— an unstyled<img>, lazy only once the intrinsic dimensions are known,priorityfor the LCP image,fallbackfor a missing node. Server-only..java-ts-bindnow includesJCRStoreProviderand whitelistsgetProvider/isDefault, so provider routing needs no cast.buildNodeUrl'sargsJSDoc — says that the default provider discards them, instead of documenting a silent no-op as a feature. This JSDoc ships in the published.d.ts.docs/2-guides/8-images), and the tutorial and hydrogen sample switched toJImage.Semantics worth flagging, each learned from a real bug in the userland copy: candidate de-duplication keeps the smallest width when a provider collapses several requests onto one rendition (over-claiming makes browsers paint an upscaled file); the original joins the ladder only when it is within 2× of the largest requested width (never send an 8000px master to a 640px card); and commas are percent-encoded inside
srcSetonly, because core's rewriter splits on every comma (Jahia/jahia#23).Validation
tsc --noEmitclean,eslintclean,prettier --checkclean.yarn workspace @jahia/javascript-modules-library test— 21 passing.Decided, and what is left
JImage—Imagewould collide with the design-systemImagemost modules already have, forcing an alias at every import site.constrainedbelow its maximum,full-widthalways;fixednever), and gives the reason for each limit: candidates stop at 2× the slot (a 3× file roughly doubles the bytes for a difference few people see) and start at 320.src/jcr-provider-augmentation.d.tsoncetarget/typesis regenerated. That regeneration is a Maven step which downloads the OpenJDK 17 source tree — java-ts-bind reads Java sources, not bytecode, so it needs the JDK's own sources for thejava.*types that appear in Jahia signatures. Nothing this PR can run; the file is not shipped, and is a typed declaration merge rather than a cast.Fixes nothing on its own — part of #734, and each commit references the sub-issue it implements.