Add automated image optimization to image component

Created on 27 March 2025, 4 months ago

Overview

Experience Builder needs a modern image optimization system to improve page performance, enhance user experience, and ensure compatibility with modern web
standards. Currently, images in Experience Builder don't have built-in optimization for responsive viewing, modern formats, or loading techniques.

Proposed resolution

Implement a Next.js-style image optimization system for Experience Builder that provides:

  • Automatic responsive image handling - Generates appropriate srcsets for different device sizes
  • Modern image formats - Supports WebP and AVIF with fallbacks for older browsers
  • Loading optimizations - Implements lazy loading with blur placeholders
  • Layout stability - Prevents content shifting during page load
  • Priority loading - Allows critical images to load immediately
  • Quality optimization - Balances quality and performance

User interface changes

The image component UI has been updated with new options:

  • Layout - Choose between responsive, fixed, fill, or intrinsic layouts
  • Priority - Toggle priority loading for above-the-fold images
  • Quality - Select image quality (low, medium, high, or auto)
  • Format - Choose between auto (WebP/AVIF with fallbacks), or specific formats
  • Loading - Select eager or lazy loading behavior
  • Placeholder - Enable blur placeholders for better loading experience
Feature request
Status

Active

Version

0.0

Component

Page builder

Created by

🇺🇸United States grasmash

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @grasmash
  • Merge request !821Draft: Resolve #3515646 "Add automated image" → (Closed) created by grasmash
  • Pipeline finished with Failed
    4 months ago
    Total: 620s
    #458379
  • Pipeline finished with Failed
    4 months ago
    Total: 2119s
    #458386
  • Pipeline finished with Failed
    4 months ago
    Total: 1202s
    #458895
  • Pipeline finished with Failed
    4 months ago
    Total: 1167s
    #458919
  • Pipeline finished with Failed
    4 months ago
    Total: 1155s
    #458959
  • Pipeline finished with Failed
    4 months ago
    Total: 1568s
    #459057
  • Pipeline finished with Failed
    4 months ago
    Total: 620s
    #459109
  • Pipeline finished with Failed
    4 months ago
    Total: 325s
    #459123
  • Pipeline finished with Failed
    4 months ago
    Total: 1544s
    #459131
  • Pipeline finished with Failed
    4 months ago
    Total: 624s
    #459145
  • Pipeline finished with Failed
    4 months ago
    Total: 341s
    #459160
  • Pipeline finished with Failed
    4 months ago
    Total: 969s
    #459155
  • Pipeline finished with Failed
    4 months ago
    Total: 1302s
    #459169
  • Pipeline finished with Failed
    4 months ago
    Total: 1361s
    #459213
  • Pipeline finished with Failed
    3 months ago
    Total: 1893s
    #462820
  • Pipeline finished with Failed
    3 months ago
    #463441
  • Pipeline finished with Failed
    3 months ago
    #463507
  • Pipeline finished with Failed
    3 months ago
    Total: 1370s
    #463695
  • Pipeline finished with Failed
    3 months ago
    Total: 1473s
    #464518
  • Pipeline finished with Failed
    3 months ago
    Total: 1292s
    #464950
  • Pipeline finished with Failed
    3 months ago
    Total: 1552s
    #464963
  • 🇺🇸United States effulgentsia

    Changing tag from "sprint" to "spike", because we're not planning on completing this issue this sprint, we're just trying to get a handle on what Claude Code prompted by @grasmash created here.

  • 🇬🇧United Kingdom catch

    Why is this re-implementing responsive images and image styles almost from scratch?

  • 🇬🇧United Kingdom catch

    This desperately needs an issue summary update with the following:

    1. What is the requirement for XB?

    2. How far does core's existing image style and responsive image support go towards meeting #1. If there are gaps, what are the specific gaps and do they already have existing issues - and is it technical capabiities, usability etc.

  • 🇫🇮Finland lauriii Finland
  • 🇬🇧United Kingdom catch

    Responding to the new issue summary (thanks for writing it up):

    Image styles and responsive images are configured in config. This means that using the existing system requires introducing dependencies on the image style and responsive image config entities. This goes against the SDC principle where everything a component needs is supposed to be contained by the component.

    The SDC principle is that it should receive the props, but the responsive image style config itself doesn't need to be a prop - instead the SDC component could receive the image URLs + dimensions + alt text, which can be built in advance based on the data stored in Experience Builder itself and passed in - just enough information to generate the img + srcset (or picture element) from.

    Since the URL or URLs contain all the necessary information for the image style callback to render the image, the SDC doesn't need to know anything about anything else - it can render the markup/attributes based on what's passed in.

    At the moment, any component library would have to re-implement all the various configuration options in the SDC added here in order to support responsive images, whereas really they should just be able to take the minimum set of information to build the markup.

    Unless the only way to render a responsive image in XB is going to be nesting this component in a slot? Also what happens if a component wants to accept either a responsive image or an external one (which iirc is a requirement)?

    The current implementation is shoe-horning half a dozen configuration options into the component, including whether AVIF or webp should be used. In general, avif or webp feels much more like a site-building task than a content editor one - why add the load of choosing this every time? There could eventually be even more formats available, as well as composite formats, like avif with webp fallback, potentially even with a file size check if we do 📌 Consider falling back to webp based on filesize Active . Is every combination of those going to have to be provided in the SDC as they're added? This really doesn't seem to be something to configure for every XB component.

    On top of this, the existing image style system provides a lot more options than are mentioned here: cropping which is necessary when uploads can be different aspect ratios, focal point crop etc. I imagine Experience Builder would also want to support these - are they going to need to be baked into the SDC component too?

    The responsive image module in Drupal Core is very flexible and from that perspective works well. However, it is very hard and tedious to use.

    This is a reason to improve the usability of responsive images in core, not to build an entirely parallel system that may or may not be able to do the same things and can only be used by experience builder.

  • 🇫🇮Finland lauriii Finland

    The SDC principle is that it should receive the props, but the responsive image style config itself doesn't need to be a prop - instead the SDC component could receive the image URLs + dimensions + alt text, which can be built in advance based on the data stored in Experience Builder itself and passed in - just enough information to generate the img + srcset (or picture element) from.

    But where would you choose the responsive image style in this scenario? This should not be up to the content creator to decide. The component should always render with the same (responsive) image style.

    Since the URL or URLs contain all the necessary information for the image style callback to render the image, the SDC doesn't need to know anything about anything else - it can render the markup/attributes based on what's passed in.

    I looked at \Drupal\image\Controller\ImageStyleDownloadController again and it looks like we may be able to re-use the download parts of image styles. I think this is something that @justafish indicated she would be looking into. There's already a PoC in https://git.drupalcode.org/project/image_style_dynamic/-/tree/8.x-1.x?re... for this.

    I somehow had the impression that the garbage collection of images was relying on file entities but after reviewing this again, it doesn't seem to be the case. Based on this, we should be able to use some of the lower level APIs from image styles. I removed this challenge from the issue summary.

    At the moment, any component library would have to re-implement all the various configuration options in the SDC added here in order to support responsive images, whereas really they should just be able to take the minimum set of information to build the markup.

    Based on above, we may be able to provide additional components for loading image styles and responsive images from components as a fallback.

    The current implementation is shoe-horning half a dozen configuration options into the component, including whether AVIF or webp should be used. In general, avif or webp feels much more like a site-building task than a content editor one - why add the load of choosing this every time? There could eventually be even more formats available, as well as composite formats, like avif with webp fallback, potentially even with a file size check if we do #3516434: Consider falling back to webp from AVIF based on filesize. Is every combination of those going to have to be provided in the SDC as they're added? This really doesn't seem to be something to configure for every XB component.

    The component is not necessarily designed to be exposed in XB – it's a developer tool for whoever is building the components. Also, there are settings that should not be in the component itself. For example, the image types that are generated should be a global configuration. See https://nextjs.org/docs/pages/api-reference/components/image#advanced for how Next.js does this.

    On top of this, the existing image style system provides a lot more options than are mentioned here: cropping which is necessary when uploads can be different aspect ratios, focal point crop etc. I imagine Experience Builder would also want to support these - are they going to need to be baked into the SDC component too?

    We should support modules like focal point with the component. I'm not sure if that's the case for the MR as it currently stands but it is mentioned in the issue summary.

    This is a reason to improve the usability of responsive images in core, not to build an entirely parallel system that may or may not be able to do the same things and can only be used by experience builder.

    It would be very hard to iteratively address this in the responsive image module. I think it would be better to build an alternative module in contrib, and battle test it before putting it to core.

  • 🇬🇧United Kingdom catch

    But where would you choose the responsive image style in this scenario? This should not be up to the content creator to decide. The component should always render with the same (responsive) image style.

    This seems like something for a site builder to configure. Even if not using responsive image styles as such, there needs to be settings for things like whether to crop to specific aspect ratios etc. as discussed above, which should never be exposed to content creators on a per-image basis either. So there has to be some kind of configuration step for the component not just within the component at the content creator level, and then it can happen there.

    I somehow had the impression that the garbage collection of images was relying on file entities but after reviewing this again, it doesn't seem to be the case.

    Yes it has nothing to do with file entities, this is why it's good to verify requirements before thousands of lines of code are written, even if an LLM is writing the code.

    Also, there are settings that should not be in the component itself. For example, the image types that are generated should be a global configuration.

    If the image type is a global setting, then why is the MR passing it to the component as a prop in the first place? And why is the component controlling which types are available? I think the answer to this is 'because the MR was written by Claude and posted without human review without clear instructions' but that's why I asked for an issue summary here defining the actual requirements.

    Also if for some reason the format (or anything else) is taken from global configuration, then translated to a string to pass to the component, how is this translation layer different to what would be necessary to use a responsive image style to generate the relevant attributes and URL from to pass to the component?

    It would be very hard to iteratively address this in the responsive image module. I think it would be better to build an alternative module in contrib,

    Whether the work was done directly in core or in contrib, it would still be a coherent module in its own right, and not an entire subsystem hidden in experience builder.

  • 🇬🇧United Kingdom catch

    There's already a PoC in https://git.drupalcode.org/project/image_style_dynamic/-/tree/8.x-1.x?re... for this.

    OK but from that page,

    In order to protect sites from DDOS you can configure the allowed query strings inside
    image_styles_dynamic.settings

    this is not very encouraging.

  • 🇺🇸United States effulgentsia

    Adding important info to the issue title per #7.

  • 🇫🇮Finland lauriii Finland

    This seems like something for a site builder to configure. Even if not using responsive image styles as such, there needs to be settings for things like whether to crop to specific aspect ratios etc. as discussed above, which should never be exposed to content creators on a per-image basis either. So there has to be some kind of configuration step for the component before it's available to site builders, not just within the component at the content creator level, and then it can happen at that step.

    The proposal is for the "(responsive) image style" to be able to live inside component code.

    For example, the card component may do the following:

    {% include 'image' with image|merge({ width: 250 })}) %}
    

    This would automatically generate the image with the width 250px.

    The hero component might do the following:

    {% include 'image' with image|merge({ width: 2500 })}) %}
    

    This would automatically generate a responsive image style where the highest width would be 2500px.

    In future, we could add support to configure these using a config entity so that they could be used in the UI.

    Yes it has nothing to do with file entities, this is why it's good to verify requirements before thousands of lines of code are written, even if an LLM is writing the code.

    Looking at this again, it does look like the garbage collection only works for file entities because the only place where \image_path_flush is called is \Drupal\image\Hook\ImageHooks::filePredelete and \Drupal\image\Hook\ImageHooks::fileMove. So if we use image styles for non-file entities, we'd have to add another way of handling garbage collection.

    Also if for some reason the format (or anything else) is taken from global configuration, then translated to a string to pass to the component, how is this translation layer different to what would be necessary to use a responsive image style to generate the relevant attributes and URL from to pass to the component?

    There's nothing wrong in having global settings in configuration so long as the set of configurations is predictable, so that we are not introducing a dependency from components to a specific config entity which may or may not exist on a site. We can also provide sensible defaults for the configured image types. The problem with image styles and responsive images is dependency on specific config entities that may or may not exist, and the fact that they are extremely difficult to configure. Even if they exist, they may or may not have the configuration expected by a specific component because how would a component know if e.g., "small" image style has the configuration that the component expects?

    this is not very encouraging.

    This is one of the problems solved in the current MR. I think it would be better to wait for an actual proposal until doing further evaluation on this level.

  • 🇬🇧United Kingdom catch

    Looking at this again, it does look like the garbage collection only works for file entities because the only place where

    Image styles as a whole can be flushed, which will clean anything remaining out, however also any new system would also have to add garbage collection both for styles and individual images.

    But... I don't see how that can be achieved with the current proposal.

    If a component defines a 2500px responsive image style which generates say five different image derivatives, then the hmac in the MR will ensure that no-one can generate a 2499px responsive image style arbitrarily. However, if a site stops using that component (swaps it out for one that's 1800px instead), how will the system know that it needs to delete all of those images (or not if the component is used elsewhere in a different layout)? This could be hundreds of thousands of derivative images on disk to worry about.

    Equally, because the hmac only validates that the request is a genuine Drupal request and doesn't rely on configuration, even if there is some kind of event that deletes images when the last usage of a component is removed, nothing stops cached HTML requests from regenerating those images again because the URL is always going to be valid. You might not be able to regenerate lots of URLs for a DDOS attack but they might be referenced from internet archive or similar (or still in a CDN). Both image styles and asset aggregates have explicit protection against this kind of stale file disk filling situation.

  • 🇬🇧United Kingdom tonypaulbarker Leeds

    Hi folks. Here's an outline of my thoughts based on the discovery and feedback I have been involved with since last summer.

    There are a lot of things on the wishlist in the description. Not all of these should be configurable per instance by editors. We will arrive at a difficult to use UX. They shouldn't be concerned with the filetype, lazy loading and so on. Most of these things should be in other settings away from the edit screen (or at least tucked away in 'advanced').

    I am not concerning myself with how one would go about deleting files but - from the UX end in the editor - the important things for editors to be able to control in the content pane are:

    Select size (max width or height) - grids and containers the image is positioned in are ideally known by the system from the layout to set an upper limit.
    Select orientation from portrait, square or landscape (to limit the aspect ratio list)
    Select aspect ratio
    Crop (automatic with focal point, then tick a box to manually cropping controlled by the selected aspect ratio + allow freeform)
    Brightness, contrast adjustments
    Rotation / angle using a dial widget
    Override alt text (once we have made adjustments like cropping, the description of our image changes from that stored in the library).

    I don't think it's enough to only set these things for all instances of a component but defaults for the component are good. Administrators should be able to have some control to toggle these being available - in some cases, for example, the aspect ratio should be locked but cropping to that aspect ratio enabled. Editors really want to be able to make adjustments specific to their image for the context it is being used in without affecting other instances.

    I don't believe that the 'legacy Drupal' image style system can accommodate this. But live adjustments could be generated with CSS to hand over to be generated with PHP based on the parameters on save.

  • 🇬🇧United Kingdom justafish London, UK
  • 🇬🇧United Kingdom justafish London, UK
  • 🇬🇧United Kingdom justafish London, UK

    justafish changed the visibility of the branch image-optimization to hidden.

  • 🇬🇧United Kingdom catch

    Another question about requirements - there's no mention of art direction here. Core responsive images use the picture element, which makes it possible to use different aspect ratios at different breakpoints. This is part of what makes core responsive images complicated to set up (just using srcset would be a lot simpler and works for most cases, and it'd be good if the responsive images UI made that more of an obvious choice) - but, has there been an explicit decision not to support picture and art direction here? Or has it been deferred to a later phase? Or has it not been discussed yet?

  • 🇫🇮Finland lauriii Finland

    #22: Add automated image optimization to image component Active picture is like another layer on top of the image component. See https://nextjs.org/docs/app/api-reference/components/image#getimageprops for how Next.js manages this and some other use cases with their API.

  • Pipeline finished with Failed
    2 months ago
    #485944
  • 🇬🇧United Kingdom tonypaulbarker Leeds

    Some more ramblings for consideration.

    We have precedent for sending parameters to image styles without building images with a URL, which is Crop API.
    I'm thinking something along the lines of 'Adjustments API' similar to Crop API that could handle other types of adjustments like brightness, contrast, masks or a parameter for detecting subjects and cutting them out.

    I came across Media Contextual Crop group of modules but I haven't taken them for a spin yet, https://www.drupal.org/project/media_contextual_crop . I think there is a distinction between content images where contextual information is available and more control is desirable and meta images that may be used for things like teasers and the context is not known by the content.

    A reason to steer away from generating images with URLs is that even if width and height can be handled effectively we see from problems with facets https://www.drupal.org/forum/support/post-installation/2025-02-20/drupal... how the permutations multiply rapidly and it would leave us unable to extend the system.

  • 🇦🇹Austria fago Vienna

    Just found stumbled over this (very interesting) issue.

    We've been working on solving the same challenge for our projects at drunomics, where we use Vue/Nuxt on the frontend side. The solution we settled is having Drupal generate a high-res image in the right aspect-ratio using editorial controlled cropping, e.g. focal point + using nuxt-image for frontend-controlled, responsive image size generation.

    That said, the approach taken by nuxt-image might be worth a look here: It features providers for the image resizing, i.e. it's doing the width/size calcuation based upon the given "sizes" attribute what is convenient for responsive images. Then, by having pluggable providers one can easily swap out a local/drupal-powered resizing resolution with a CDN-provided service.

  • Assigned to justafish
  • 🇺🇸United States effulgentsia

    I like the main concept of this MR, which is to have "dynamic image styles", which I see as embodying three things:

    • The "image style" part of the name implying that they behave just like regular image styles with respect to when, how, and where the image derivatives are generated, stored, and flushed (see further down in this comment about flushing which isn't implemented in the MR yet).
    • The "dynamic" part of the name implying that each variation (e.g., different widths) does not get saved as a separate config entity, so as not to clutter the config management and image style listing and selection UIs. The MR currently doesn't store any config entity for these dynamic image styles, but further in this comment I propose saving one for a group of variations, just not one per variation.
    • It's not implemented in this MR yet, but I think another desired meaning of "dynamic" here is that all variations within a group produce the same itok, thereby allowing the front-end to pick the variation by just varying a part of the URL without needing to also worry about getting a different itok for each one. This broadening of itok from giving access to a single derivative of a single image to giving access to a set of derivatives of a single image would make this easier to integrate into common front-end image components like the ones in Next or Nuxt. However, to prevent DDOS attacks, this means the set of variations within a group (e.g., the set of widths) needs to be reasonably bounded. This MR bounds to 8 hard-coded widths matching the default deviceSizes in Next.js. We might want this to be configurable in some way, whether we add that configurability here or in a follow-up.

    With the above preamble out of the way, some thoughts about the details...

    These URLs are very long.

    I agree, and I don't think we need the full genericism of being able to specify any combination of effects within the image style name. I think our use-case for now is just about widths. It's conceivable we'll want additional things to be dynamic in the future (format, quality, focal point, etc.), but I don't think we need to solve for that now. I think it's equally possible that those settings don't actually need to be dynamic in the same way that width does.

    In other words, I think XB's use case could be addressed with short image style names like xb--WIDTH (e.g., xb--640) or whatever other delimiter we want to pick if we think there's something preferable over --.

    The dynamic image styles are cool - but if the device sizes are hard-coded - do we need them - can't we just ship N image styles in the modules config/install?

    That is indeed one option, but I don't think it's optimal, because it would add clutter to image style listing and selection UIs, and image styles don't implement locked or no_ui, so we'd need to add code to prevent them from being edited or else let them be edited but figure out how to deal with them getting out of sync (e.g., their name not matching the width setting). Plus we'd still need to implement itok sharing, so they'd be in this in-between concept of sort of dynamic and sort of static. Overall, I think there's more downside than upside to have a stored config entity for each width.

    This is dynamically creating the image style, but never saving it. How will it handle image style flushes when one stops being used?
    Less important than flushes but also relevant here, how will it handle garbage collection when source images are deleted from disk?

    One option would be for XB to implement hook_cache_flush(), hook_file_move(), and hook_file_predelete(). However, that wouldn't exactly provide parity with regular image styles, because regular image styles are not flushed by a regular cache clear and can be flushed on their own via the UI or a Drush command. So if we want parity with that, I propose that we create an image style that represents the group. For example, we ship with an xb image style, which then exists as a stored config entity, that xb--* image styles are "derived" from (but not individually stored). Then we can implement hook_image_style_flush() to flush all the --* ones whenever the base one is flushed.

    "target_id": {
      "title": "file id",
      "type": "integer"
    }
    

    I don't like this being added to the json schema definition of the Image prop type. Prop types should be independent from Drupal's data model. What I recommend instead of this is adding a srcsetTemplate property that's of type uri-template. So its value for an image foo.jpg would be /sites/default/files/styles/xb--{width}/public/2025-06/foo.jpg.webp?itok=.... Client code could then generate a srcsset from this by replacing {width}. Or, if integrating with a Next or Nuxt image component, you'd configure a loader/provider function that does that replacement.

    Ensure that the module can function with security tokens (i.e. itok)

    Per the top of this comment, we need the same itok for each width. This MR already implements a param converter that instantiates the image style even though it's a dynamic image style that doesn't exist in config storage. I think the only addition needed for this is to instead of instantiating it as an ImageStyle to instantiate a subclass (e.g., DynamicImageStyle), where the subclass overrides getPathToken() from hashing $this->id() to instead hashing just the part of $this->id() before --.

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Discussed this in a call with @effulgentsia, particularly the need for the dynamic nature rather than static image styles.

    The desire for this is coming from feedback collated by Lauri that configuring this correctly is difficult and arduous.

    I pointed out that some of our sites have 100+ image styles and @penyaskito pointed out that Drupal CMS has 55. I was trying to illustrate that having a lot of image styles is common. Alex rightly pointed out that this illustrates the pain point - which I agree. We have cli tooling to get around but this is a attempting a simpler approach.

    I think the only addition needed for this is to instead of instantiating it as an ImageStyle to instantiate a subclass (e.g., DynamicImageStyle), where the subclass overrides getPathToken() from hashing $this->id() to instead hashing just the part of $this->id() before --

    that sounds like a good idea, this class could also override ::flush and deal with the GC/cleanup. I wonder if we could explore storing third-party settings on the image style rather than relying on magic xb-- naming. This could even hold the dynamic widths so that in theory we could have an extension point. Doesn't need to happen now, but I think trying to do this in a way that could have a path back to core that solves the N image styles proliferation issue should be a long term goal.

    So +1 for going down the subclass approach.

  • Pipeline finished with Failed
    23 days ago
    Total: 674s
    #526347
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
    • Automatic responsive image handling - Generate appropriate srcsets for different device sizes

    Won't this mean this will only be available for server-side rendered components? So it won't be available for code components (JavaScriptComponents) or really just … any component that also happens to use images? Doesn't that violate ?
    → ah explained later in the issue summary: , but no plan in place yet.

    It should additionally provide a Twig function for easily generating URLs to processed images.

    This sounds wrong to me; if it must be a Twig function, then we're essentially saying we're deviating from https://nextjs.org/docs/pages/api-reference/components/image, which contradicts the earlier statements in the issue summary. Next.js' "Image" component declares everything statically and auto-generates an appropriate URL.

    (Which means I'm apparently stating almost the same as @catch >2 months ago in #10.)

    @catch in #16 on April 17:

    If a component defines a 2500px responsive image style which generates say five different image derivatives, then the hmac in the MR will ensure that no-one can generate a 2499px responsive image style arbitrarily. However, if a site stops using that component (swaps it out for one that's 1800px instead), how will the system know that it needs to delete all of those images (or not if the component is used elsewhere in a different layout)? This could be hundreds of thousands of derivative images on disk to worry about.

    Here I have some good news:

    1. 📌 Version component prop definitions for SDC and Code components Active added a new tool to our toolbox: per-Component versions. The metadata that impacts the version hash (or put differentlY: when the hash changes) could be updated (current implementation stems from 🐛 Deterministic Component version hash should depend not only on source-specific settings, but also slots + explicit input schema Active ) could be updated to include the image restrictions imposed by the component developer.
    2. As of 📌 Calculate field and component dependencies on save and store them in an easy to retrieve format Active and its follow-ups, and especially 📌 Revisit storage of dependencies in separate table now we have separate deltas per component Active , we can determine which Component version is used in which component tree (in both config entities and in which revision of which content entity's XB field component tree).

    @tonypaulbarker in #17: first you write

    Not all of these should be configurable per instance by editors. We will arrive at a difficult to use UX.

    which I think everybody would agree with. But then: , after which you list 7 important things content editors must be able to control.

    To me this reads like a contradiction. Especially without designs, wireframes or even just links to prior art elsewhere. Could you please elaborate?

    @justafish in #18: the issue summary was updated to be more concrete, which is great, but it seems to have descoped 3 items ("we should also consider the following"): CDN support, focal point and DDoS protection. Was that the intent?

    @tonypaulbarker in #24: → wouldn't this steer us away from the basic premise of the issue summary, aka striving for "similar to or even reusing" Next.js' Image? The fundamental premise there is the fact that the URL is generated by the component:
    https://nextjs.org/docs/app/api-reference/config/next-config-js/images#e...
    Also, why is this not essentially the same as DDoS protection which @catch already talked about in detail over at #16?

    @fago in #25: AFAICT https://image.nuxt.com/get-started/providers is basically the same as https://nextjs.org/docs/app/api-reference/config/next-config-js/images#e...? Both are basically "generate a URL and let whichever server that points to parse its instructions from the URL".

    @effulgentsia in #26: Unsure about many things in that comment, but very strongly agreed with your opposition against adding a file ID to the "image" prop shape, and even more pleasantly surprised and intrigued by your uri-template proposal! 🤩

    I don't understand yet what the proposed "group of variations" means/contains. It sounds a lot like a ResponsiveImageStyle (i.e. with a bunch of predefined ImageStyles), perhaps named DynamicResponsiveImageStyle (or ImageStyleGroup or … ), but:
    without the ImageStyle config entities; instead with the contents of those config entities stored directly in this new config entity type — these are what determine what derivatives can be generated
    with a single itok-esque anti-DDoS URL query arg value per DynamicResponsiveImageStyle, i.e. the same itok for all derivatives

    But this too appears to contradict what the issue summary wrote about doing something similar to or even re-using https://nextjs.org/docs/pages/api-reference/components/image? 🤔 How could a pure client-side component generate such a URL? It can neither know what "groups" (DynamicResponsiveImageStyles) exist, nor could it generate an appropriate itok-esque value?

    Or is the idea that just like for Next images, this functionality would provide configuration options that each image-consuming component must respect — so that could then be:

    1. when rendering a (responsive) image using a SDC, use some Twig function we provide (in the current MR: xb_srcset())
    2. when rendering a (responsive) image using a code component, write the JS logic necessary to read + respect the settings in a JSON blob XB passes to the client side in <head>
    3. when rendering a (responsive) image using Next.js' image component, write a custom loader that contains the JS logic to read + respect the settings in a JSON blob XB passes to the client side in <head>

    (That was perhaps obvious to all of you, but it wasn't to me 😇)

  • 🇬🇧United Kingdom catch

    The metadata that impacts the version hash (or put differentlY: when the hash changes) could be updated (current implementation stems from #3528362: Deterministic Component version hash should depend not only on source-specific settings, but also slots + explicit input schema) could be updated to include the image restrictions imposed by the component developer.

    If the rendered image size is changed by an SDC - say it changes from image and description side by side to top and bottom and then the image becomes full width instead of half width, then I would assume that's a purely presentational change that should be immediately reflected in all cases that the component is rendered? Otherwise you could potentially have mismatching versions shown even on the same page depending on when they were last saved?

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    #30: correct, that is the current reality. See \Drupal\experience_builder\ComponentSource\ComponentSourceBase::generateVersionHash(). That only looks at:

        $normalized_data = [
          'settings' => $typed_source_specific_settings->toArray(),
          'slot_definitions' => $this instanceof ComponentSourceWithSlotsInterface
            ? self::normalizeSlotDefinitions($this->getSlotDefinitions())
            : [],
          'schema' => $this->getExplicitInputDefinitions(),
        ];
    

    No Twig/markup changes can affect it.

    I was just contemplating that it would be possible to either:

    1. put some image restriction-y metadata in the explicit input definitions ("JSON schema for the SDC props in *.component.yml" in SDC terminology)
    2. use infrastructure similar to how we currently generate version hashes to be able to track what image restrictions are in place, to help us purge when needed
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    AFAICT, this is almost same problem space as https://www.lullabot.com/articles/decoupled-drupal-hard-problems-image-s....

    Quoting from @e0ipso's article:

    In a decoupled application your back-end service and your front-end consumer are separated. […]

    In this situation, we can see how our back-end doesn't know anything about the front-end(s) design(s).

    we need to replace "front-end consumer" with "component (SDC, code component …)" and we actually do know which components are present on a site.

    The way that was solved:

    After some research about how other systems tackle this, we established that we need a way for consumers to declare their presentation dependencies. In particular, we want to provide a way to express the image styles that consumer developers want for their application.

    Which I believe applies here, too. But instead of the consumer/client (for XB: "component") needing to express all image styles they need, here we'd only need the subset of image styles up to a width that may be smaller than the maximum width configured for the site.

    See @lauriii's in #5. Combined with Next.js' Image's deviceSizes, that'd then result in

        deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    

    being limited to

        deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2500],
    

    or, if the image was limited to 800, then it'd have been limited to:

        deviceSizes: [640, 750, 800],
    

    If we apply @effulgentsia's #26, then I think Alex meant that:

    1. every "image-shaped prop" can benefit from automatically optimized images (i.e. anything using type: object, $ref: json-schema-definitions://experience_builder.module/image right now — proposal to improve that at 📌 Decouple image (URI) shape matching from specific image file types/extensions Active )
    2. for each "image-shaped prop" for each component instance, there can be at most 8 different derivatives generated on disk
    3. all component instances that reference the same file on disk (no matter whether it's a File entity for an image field, a File entity for or for an image media entity, or the default image shipped with an SDC) would result in the same 8 derivatives (not necessarily the same URLs, but presumably)
    4. each "image-shaped prop" in a component can have its own set of image operations ("image effect plugins" in Drupal) specified, because for example a "product spotlight" component might want to have front, left, right, back props, and make the front one have a much bigger/more prominent image.
    5. I think this is what he was getting at with type: string, format: uri-template near the end of #26; so that you would for a component with limitations imposed by the component developer you'd end up not with
              "src": {
                "title": "Image URL",
                "$ref": "json-schema-definitions://experience_builder.module/image-uri"
              },
              "alt": {
                "title": "Alternative text",
                "type": "string"
              },
              "width": {
                "title": "Image width",
                "type": "integer"
              },
              "height": {
                "title": "Image height",
                "type": "integer"
              }
      

      but with something like this (still figuring out how to use URI templates in JSON Schema, but there is a great package for PHP: https://uri.thephpleague.com/uri/7.0/uri-template/ — which I worked on with @gabesullice for AM:A at https://git.drupalcode.org/search?search=uri-template&project_id=63588&s...):

              "src": {
                "title": "Transformed image URL, transforming any input image to square aspect ratio and grayscale",
                "type": "string",
                "format": "uri-template",
                "$ref": "/xb_dynamic_image_style{/path_to_image}?effect_ratio=square&reduce_colors=grayscale&scale[width]={width}&itok={itok}",
                "x-image-max-width": 250
              },
              "alt": {
                "title": "Alternative text",
                "type": "string"
              },
              "width": {
                "title": "Image width",
                "type": "integer"
              },
              "height": {
                "title": "Image height",
                "type": "integer"
              }
      

      … which would:

      1. give the component 1 URL with most but not all placeholders replaced: {path_to_image} would be replaced (pointing to the selected image) and {itok} (as described by @effulgentsia in #26, with one itok for ALL 8 ), but NOT {width}
      2. For {width}, the client side (or the Twig equivalent) receives the set of deviceSize (or however we name our equivalent), which then enables the client to trivially generate all URLs pointing to all <=8 optimized image URLs.
      3. IOW: the server side would expand all bits only the server side can (itok + the path to the selected image)

    This means:

    1. that the server can validate widths against the itok, and refuse any widths that are NEITHER in the (for now hardcoded) global/default deviceSizes NOR the max of 250 (x-image-max-width)
    2. that the server can compute which image derivatives to purge [……… more thinking needed, need to post this this …………]

    P.S.: @justafish indicated that the image style infrastructure only supports File entities. But we know we'll have to make non-File entity files work as well, for example default images included in SDCs. I know for sure that this is technically possible, because I had to do this too in my https://drupal.org/project/cdn module, and there I have a route with this path:/cdn/ff/{security_token}/{mtime}/{scheme}. But we'll need be really careful: it's easy to make mistakes — core got it wrong too, see https://www.drupal.org/sa-core-2023-005 .

  • 🇬🇧United Kingdom tonypaulbarker Leeds

    To me this reads like a contradiction. Especially without designs, wireframes or even just links to prior art elsewhere. Could you please elaborate?

    @wim leers

    In the absence of some designs and wireframes just for now I would recommend looking at Wix Studio image editing tools (see https://support.wix.com/en/article/studio-editor-editing-and-customizing... ), iPhone native image editing for UI of image adjustments. These UIs are intuitive and easy to use even if the Wix optimisation is poor on the front end. iPhone gives us an easy way to make adjustments and then save adjustments to the original 'entity' or to create a clone.

    Cropping is the real pain point. In a recent focus group, several people told us that they use Photoshop or similar to pre-crop their images instead of using Image Widget Crop because they find Drupal too confusing and they can't tell what crop will be used in what context.

    Adjusting brightness and contrast in an image style is of little use to an editor - these operations should be able to be set on a per image basis within the media library.

    I think that there are some operations where we should be able to generate a clone of a media entity, for example @marcus_johansson wonderful work on image to image AI transformations https://www.drupal.org/project/ai/issues/3531212 Create Image-To-Image operation type Active https://www.youtube.com/watch?v=ekyu52ARPdU - given the operation is available to our site, we should be able to click to process the image at the point of upload and save to a clone as well as the original if we want.

    Images used in the pages of content are different to teasers used in views. Giving people control of instances where views is used is much more difficult. But, if we know that a content context requires a 16:9 image we need a way for editors to intuitively be able to control a 16:9 crop at the point of placing it in the content.

    What I envisage is that we give the impression of transforming by leaving the original media entity intact and referencing the entity and / or file from a transformation entity that contains the transformation data and can in some cases - like the AI transformation where processing is too heavy to do on the fly - also save the derivative as a file.

    If combined with developers setting expected / allowed aspect ratios (or other transformations) on media fields this could make it easy for editors to crop, maintain consistency and also help with the views teasers scenario.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Just had a ~45 min call with @balintbrews & @effulgentsia about this, and a LOT has been cleared up by it! Much in my last comment #33 is wrong, because I didn't know about 2 key decisions/assumptions not explicitly stated in this issue yet. Witht hem #26 makes a lot more sense:

    1. we don't need to support the image_desaturate (grayscale), image_rotate etc. effects, because we just do them in CSS nowadays
    2. in fact, for the scope of this issue, we want to focus on the ~95% use case, and we do only one transformation: we scale the image down to fit the requested width

    On top of this, per @effulgentsia, we can just hardcode the conversion to .webp, because that is (sufficiently) universally supported.

    Then, we modify the existing image shape like this:

    diff --git a/schema.json b/schema.json
    index 004279ce0..ede37e558 100644
    --- a/schema.json
    +++ b/schema.json
    @@ -205,6 +205,11 @@
             "height": {
               "title": "Image height",
               "type": "integer"
    +        },
    +        "candidateTemplate": {
    +          "type": "string",
    +          "title": "Image candidate string URL template",
    +          "format": "uri-template"
             }
           }
         },
    

    … to convey that the server should provide a string like /sites/default/files/styles/xb--{width}/public/2025-06/foo.jpg.webp?itok=1Rl59WAb (which is the image URL template for an image candidate string for srcset), where the only further information needed on the client side is the deviceSizes (which can be passed via drupalSettings).

    Then the computed property that you and I discussed earlier today, @justafish (which you haven't yet pushed at this time), does start to make sense (unlike what you and I thought earlier today).

    That means all other considerations in the issue summary are descoped per @effulgentsia. Including cropping and everything else listed in #34.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Retitling for narrowed scope.

  • 🇬🇧United Kingdom tonypaulbarker Leeds

    @wim leers

    A lot of things are encouraging here but this I'm not so sure about.

    we don't need to support the image_desaturate (grayscale), image_rotate etc. effects, because we just do them in CSS nowadays

    A consideration is how will this appear in search engines like Google shopping and Google images?

    What's in that ~5% and does it matter?

  • 🇬🇧United Kingdom catch

    On webp I don't think it needs to be configurable for editors, but we just added AVIF conversion with WEBP fallback Active to core which uses avif if you have it (server side, all browsers that Drupal supports support it already), webp if you don't.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Can't we use only AVIF? The browser support difference is negligible: http://caniuse.com/avif (93,57%) vs https://caniuse.com/webp (95,62%).

  • 🇬🇧United Kingdom catch

    @wim browser support is fine, but server-side support is not. https://www.drupal.org/node/3348348 and the related issue has some details. AVIF conversion with WEBP fallback Active falls back to webp based on whether the environment supports AVIF or not. This is the opposite problem to what we had when we adopted webp when server support was fine but browser support wasn't for a long time (mostly due to old OSX versions).

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    wim leers changed the visibility of the branch image-optimization to active.

  • 🇳🇱Netherlands balintbrews Amsterdam, NL

    I created Image component for code components Active for the frontend counterpart and drafted the initial implementation based on our discussion yesterday.

  • 🇳🇱Netherlands balintbrews Amsterdam, NL
  • Pipeline finished with Failed
    18 days ago
    Total: 779s
    #530213
  • Pipeline finished with Failed
    18 days ago
    Total: 801s
    #530228
  • Pipeline finished with Failed
    18 days ago
    Total: 919s
    #530233
  • Pipeline finished with Failed
    18 days ago
    Total: 759s
    #530248
  • Pipeline finished with Failed
    18 days ago
    Total: 1042s
    #530331
  • Pipeline finished with Failed
    18 days ago
    Total: 1891s
    #530374
  • Pipeline finished with Failed
    18 days ago
    Total: 1316s
    #530391
  • Pipeline finished with Failed
    18 days ago
    Total: 1038s
    #530416
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    I've now implemented most of @effulgentsia's #26, @larowlan's #27 and my #35.

    Remaining:

    1. update the experience_builder:image SDC — @justafish is handling that
    2. architectural review — assigned to @larowlan for that
    3. making remaining tests pass — hoping @larowlan can get this to green while I sleep 😅😇
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Forgot to show it works:

    (This is from before the 404 in case of an arbitrary width being passed in.)

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    BTW, this is essentially hardcoding a single Adapter plugin into the image field type as a computed property.

    It is feasible to do an update path later that switches existing component trees over to that.

    This is a pragmatic step feasible today; neither the XB UI on the client nor the “component instance form” in the server currently have sufficient adapter support.

  • Pipeline finished with Failed
    17 days ago
    Total: 996s
    #530996
  • 🇦🇺Australia rikki_iki Melbourne
  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10
  • Pipeline finished with Failed
    16 days ago
    Total: 759s
    #532180
  • 🇺🇸United States effulgentsia

    What is srcSetCandidateTemplate?

    @lauriii and I discussed this, and based on our discussion I wrote up 📌 Improve the front-end DX of Active . I don't think we should hold up this issue on that though: that issue can be a followup.

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Will pick up the remaining tasks here tomorrow

  • 🇺🇸United States effulgentsia

    Tagging this as a beta blocker because currently in 0.x, the src property of an image prop is always set to the original image with no style applied, which means multi-MB images if uploaded from a typical camera or phone.

    This issue isn't the only way to solve that problem. Other options include: providing a separate image style selector field along with the media field, or using the image style referenced by the media image type's default view mode's display, or creating an XB view mode for that. Any of those could be potentially good things to add in the future, especially to support "interesting" image styles (ones that do something other than scale an image), but the MR on this issue is close, and solves the basic use case of just wanting responsive widths in the simplest way possible from the user's perspective. Which lets us defer integrating "proper" image style configuration until we've thought through the UX of that more thoroughly.

  • Pipeline finished with Failed
    11 days ago
    Total: 725s
    #536254
  • Pipeline finished with Failed
    11 days ago
    Total: 1132s
    #536329
  • 🇦🇹Austria fago Vienna

    @fago in #25: AFAICT https://image.nuxt.com/get-started/providers is basically the same as https://nextjs.org/docs/app/api-reference/config/next-config-js/images#e...? Both are basically "generate a URL and let whichever server that points to parse its instructions from the URL".

    yes, exactly. I think introducing this concept of pluggable providers/loaders makes sense, since generating all image-size variations is often taken quite some load/compuation-time and many variations on disk of image-heavy sites quickly become huge, would it make sense to add a pugin-type for that? (follow-up material).

    Also not sure XB is the right home for this, this seems to be a generally very useful addition.

  • 🇫🇮Finland lauriii Finland

    I think we should move this to core (or a separate module) eventually. We can experiment with this in XB until that alternative solution exist.

  • Pipeline finished with Failed
    10 days ago
    Total: 738s
    #536631
  • Pipeline finished with Failed
    10 days ago
    Total: 1375s
    #536667
  • Pipeline finished with Failed
    10 days ago
    Total: 849s
    #536706
  • Pipeline finished with Failed
    10 days ago
    Total: 2756s
    #536774
  • Pipeline finished with Canceled
    10 days ago
    Total: 188s
    #536820
  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Down to 4 open threads and the new responsiveImage.spec.ts playwright test is failing.

    Need some answers on some of the threads

  • Pipeline finished with Failed
    10 days ago
    Total: 1055s
    #536821
  • Pipeline finished with Failed
    10 days ago
    Total: 928s
    #536983
  • Pipeline finished with Failed
    10 days ago
    Total: 512s
    #537250
  • Pipeline finished with Failed
    10 days ago
    Total: 679s
    #537277
  • Pipeline finished with Failed
    10 days ago
    Total: 591s
    #537401
  • Pipeline finished with Success
    10 days ago
    Total: 5276s
    #537404
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
    1. Resolved conflict with 📌 [PP-1] Enforce conflict enforcement outside of tests and e2e tests Postponed that landed yesterday: the validation of auto-saves that was being adjusted for tests was moved. Initial attempt is very blunt: let's see if we still need the tweaks this was making, if we do, I'll restore them.
    2. Resolved conflict with the first MR for Define JSON Schema $refs for linking/embedding videos and linking documents Active that landed last night.
  • Pipeline finished with Failed
    9 days ago
    Total: 3699s
    #538245
  • Pipeline finished with Failed
    8 days ago
    Total: 707s
    #539253
  • Pipeline finished with Failed
    8 days ago
    Total: 934s
    #539265
  • Pipeline finished with Failed
    8 days ago
    Total: 434s
    #539285
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    One major change I made: the way interesting order of operations that differs between Recipes and regular config importing/module installing 👹👻 See the rabbit hole thread with a lot of detail about how I ended up with something IMHO better, and @larowlan +1'd 😊

    In case you're wondering why I named it ParametrizedImageStyle: https://git.drupalcode.org/project/experience_builder/-/merge_requests/9...

    @jessebaker is fixing the last (Playwright) test failure due to a conflict with something he just landed, which I resolved incorrectly 🙈

    See you in the follow-up: 📌 [PP-1] Store allowed widths for xb_parametrized_width in third party settings Postponed .

  • Pipeline finished with Canceled
    8 days ago
    Total: 69s
    #539290
  • Pipeline finished with Failed
    8 days ago
    Total: 428s
    #539291
  • Pipeline finished with Failed
    8 days ago
    Total: 854s
    #539296
  • First commit to issue fork.
  • Pipeline finished with Failed
    8 days ago
    Total: 495s
    #539309
  • Pipeline finished with Success
    8 days ago
    Total: 1616s
    #539314
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Crediting Jesse :)

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
  • 🇺🇸United States mglaman WI, USA

    This may have fixed a bug but also broke a nice feature with Recipes using Code Components.

       // @todo Fix upstream core bug in Recipes: it inconsistently claims to be
        // syncing when installing modules, but not when installing configuration.
        // Even though it is listed under `import`, and that should hence match the
        // behavior of the /admin/config/development/configuration/single/import UI.
        if (in_array('installRecipeConfig', array_column(debug_backtrace(), 'function'), TRUE)) {
          // Assert the bug is still present. This will start failing as soon as the
          // upstream bug is fixed.
          assert(!$this->configInstaller->isSyncing());
          return;
        }
    

    It was nice not having to provide the component config for code components within a recipe.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    @mglaman shared in chat that he found this work-around:

      actions:
        # A fix was made to fix how Recipes do not mark config as syncing when its
        # config is imported. This "broke" component generation. Here we call
        # `enable` and save to automatically generate the components.
        # @see https://git.drupalcode.org/project/experience_builder/-/merge_requests/956#note_545724
        experience_builder.js_component.*:
          enable: []
    
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Somehow 📌 [PP-1] Store allowed widths for xb_parametrized_width in third party settings Postponed was not yet a related issue here, odd.

  • Tested changes on branch 0.x, for following scenarios:

    • Create page
    • Drag and Drop image component to page

    Reported : Automated generation not generated for images of smaller dimensions 🐛 Automated generation not generated for images of smaller dimensions Active for failing test cases

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    So #64 says this caused a regression, clarified it now: 🐛 Automated generation not generated for images of smaller dimensions Active .

    Thanks!

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    See #3532044-30: Image component for code components , there's a silly thing we overlooked here 😇

Production build 0.71.5 2024