Improve the way to detect if the code component has drupalSettings

Created on 1 July 2025, about 2 months ago

Overview

In πŸ“Œ Populate data to drupalSettings to enable Dynamic Code Components Active we've added support to drupalSettings.

The way we're detecting if there are requirements of drupalSettings is with regular expressions and can be improved.

  public static function getRequiredXbDataLibraries(string $jsCode): array {
    // @todo improve the way to detect if the code component has drupalSettings.
    $map = [
      '_drupalSettings_xbData_v0_baseUrl' => 'experience_builder/xbData.v0.baseUrl',
      '_drupalSettings_xbData_v0_branding' => 'experience_builder/xbData.v0.branding',
      '_drupalSettings_xbData_v0_breadcrumbs' => 'experience_builder/xbData.v0.breadcrumbs',
      '_drupalSettings_xbData_v0_pageTitle' => 'experience_builder/xbData.v0.pageTitle',
    ];
    $libraries = [];
    foreach ($map as $var => $library) {
      if (str_contains($jsCode, $var)) {
        $libraries[] = $library;
      }
    }
    if (!empty($libraries)) {
      return $libraries;
    }

    // There is only drupalSettings.xbData?.v0 to load the whole library
    if (preg_match('/_drupalSettings_xbData(_v0)?/', $jsCode)) {
      return ['experience_builder/xbData.v0'];
    }

    return [];
  }

Proposed resolution

TBD

User interface changes

πŸ“Œ Task
Status

Active

Version

0.0

Component

Code

Created by

πŸ‡ͺπŸ‡ΈSpain isholgueras

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

Merge Requests

Comments & Activities

  • Issue created by @isholgueras
  • πŸ‡ΊπŸ‡ΈUnited States effulgentsia

    I think what we'll want to do here is something similar to how we do first-party imports, where the XB front-end parses the JS to determine the dependencies and includes that information in the request payload when saving the component. We might need to add a new key to the js_component config schema to store the info if dependencies doesn't work for depending on Drupal libraries.

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    #2++

    That's literally what I wrote at https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...

    We'll need something similar for code components fetching data via HTTP (either local aka Drupal-served URLs or remote URLs), because that is what needed to in the future add server-side prefetching of data without breaking existing code components or leaving them behind.

    All of this is basically to say: dependency information is crucial! Without dependency information, the backwards compatibility break risk assessment is "unavoidable, will be painful and require manual intervention", whereas otherwise it can be "avoidable, will either be transparent or precise instructions can be provided".

    IOW: it's a matter of future DX for code component creators and update path feasibility for XB maintainers. See point 3 of my comment #12 in the issue that landed this β†’ .

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Step 1 here is the front end providing that dependency information, because what πŸ“Œ Populate data to drupalSettings to enable Dynamic Code Components Active introduced (and what's quoted in the issue summary) is the best the back end can do, since it cannot parse JS.

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    So, my concern in one sentence: we must be able to provide a long-term update path for code components, which includes not breaking any code components created on beta1.

    Implementation outline

    My interpretation of what @effulgentsia and I are saying:

    1. client-side to pass drupalSettings.xbData.v0 dependency information however it sees fit β€” note this COULD be all of the data initially, and just more granularity later! What matters is that we know which code components use this at all, and which version they're on. Which is exactly what the pragmatic approach in #3531249 (i.e. in HEAD did)
    2. update \Drupal\experience_builder\Entity\JavaScriptComponent::updateFromClientSide() to consume it
    3. remove the parsing logic in ::getRequiredXbDataLibraries() and it with a new config entity property on the JavaScriptComponent config entity type to store it instead
    4. update the logic in ::getAssetLibraryDependencies() to use that new config entity property

    IOW: the exact same pattern that πŸ› Components sourced from JsComponent are missing source component cacheable metadata Active did, just not stored as an enforced config entity dependency.

    Choice: beta blocker πŸ†š stable blocker + update path by relying on heuristics

    This means that this can indeed be a , not a , but only if the logic in getRequiredXbDataLibraries() can be updated to work with reasonable reliability, because then we can transform whatever that logic computes to explicit dependency information in an update path.

    Per #3531249-18: Populate data in `drupalSettings` to enable simple use cases in dynamic code components β†’ :

    The front-end API will consume this settings with methods (not decided yet) like getPageData() (for title and breadcrumb) and getSiteData() (for base URL and branding).

    That's why I think detecting the presence of such calls in the JS is doable in a saved config entity, which means that an update path after beta1 would be feasible.

    So: I propose to indeed keep this as a , but I might have missed something πŸ˜‡

  • πŸ‡³πŸ‡±Netherlands balintbrews Amsterdam, NL
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Discussed with @balintbrews, he's on board πŸ‘

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    +1 to this plan

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Great!

    So first πŸ“Œ Build Breadcrumb and SiteBranding code components Needs work plus the yet-to-be-created "page title code component" issues should land.

    I provided context about this plan at #3534617-3: Build Breadcrumb and SiteBranding code components β†’ .

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    That's become a single issue, so postponed on a single issue πŸ‘

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Expanding on

    1. remove the parsing logic in ::getRequiredXbDataLibraries() and it with a new config entity property on the JavaScriptComponent config entity type to store it instead

    What we need is dependency information in each code component:

    data_dependencies:
      drupalSettings:
        - baseUrl
      urls:
        - /jsonapi/something
        - https://example.com/something-remote
    

    Once we have that, we can have the server do prefetching, eliminating the flicker visible in πŸ“Œ Build a navigation code component Active .

    We discussed this at DDD Leuven. It just is not in scope for beta1 because so much needs to happen.

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Looking at #12 I think some of this we should ask the component to export this information

    For SSR we will likely need additional exports for components to return information like prefetched data and cache metadata

    Should we consider adding an additional method to allow the component to return preloads? Parsing that from the AST is going to be fraught.

    Imports we can parse with the AST, but things like urls inside fetch calls is going to be brittle.

    export const getPreloadUrls = () => (['/some/url', '/some/json/api']);
    
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    The blocker is in, @effulgentsia asked me to look at this one.

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Just realised there's an addDataFetch reducer and a third tab in the code editor 🀯

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Neat. following the existing pattern I was able to add generic fetch support, how did I not know about that tab until now, amazing

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    So to achieve #12 I think we should probably split `dataFetches` into groups - one for uris and one for utils like getPageData
    But this all seems doable. Have pushed my WIP and will continue on monday.

    Unassigned in case someone else wants to pick this up in the meantime.

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Continuing work here

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Spent some time here trying to make use of the existing dataFetches property in the slice to populate the dataDependencies property on the backend.

    The issue we have is one of timing.

    We clear that property every time the source code changes in collectImportedJsComponents, and then as the code calls the methods, these entries are added back.

    The issue is, at the time we update the autosave, we've just cleared the property and hence it is always empty.

    To try and do this in a non-async fashion so we can get a definitive value without needing to wait for the iframe to post messages I imported @babel/traverse to attempt to gather this from the ast.

    Some of these are easy, like detecting getSiteData and getPageData.

    Where it gets tricky is finding and resolving the value of things we call fetch with - I can use traverse to find all CallExpressions where the callee.name is fetch. We can also look for the getResource call on the JSON API client. But finding the calls and resolving the value are two different beasts.

    fetch('https://example.com') is easy to resolve because we have a string literal.

    But fetch(url); is a bit harder because we have an identifier instead, and then have to resolve those too. e.g. url could be the result of a function... And it just keeps getting more and more complex.

    I'll see if I can find a way to do something that doesn't require clearing the dataFetches values from collectImportedJsComponents but I'm wary of retaining values from the last preview re-render as they could obviously get out of sync as the component changes.

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    After a spike on @babel/traverse, I've gone back to reviewing the original goal of this issue, which is to remove the string matching in CodeComponentDataProvider::getRequiredXbDataLibraries

    But this was expanded in #12 to also allow for identifying URL requirements so we can add <link rel="preload"/>. However this expansion of scope is what is causing the difficulties in #19

    I think we should split off the preloading in #12 off into a separate issue as it is more involved.

    I'm going to focus just on CodeComponentDataProvider::getRequiredXbDataLibraries here

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Created draft change record β†’ and started on update path now we're beta stable

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Opened a draft MR, still to go - e2e test

  • Pipeline finished with Failed
    20 days ago
    Total: 621s
    #558406
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Looking at #12 I think some of this we should ask the component to export this information

    +1

    I think we should split off the preloading in #12 off into a separate issue as it is more involved.

    WFM, but then that needs to become a beta blocker. Made it so in #3538273-3: [PP-1] Use AST to identify resources fetched by CodeComponents so they can be preloaded/prefetched β†’ .

  • Pipeline finished with Failed
    19 days ago
    Total: 791s
    #559166
  • Pipeline finished with Failed
    19 days ago
    Total: 775s
    #559271
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    This is ready for review.
    I _think_ this is the first issue with an update path, because the change here to expect this in the config entity is a breaking one for old components.

    I've added a db dump based on 0.7.2-alpha1 and core 11.2.2 that is minimal + the base recipe + the test_site recipe + some test modules that provide components, an update path and an update path test.

    There is also a change record.

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Pipeline finished with Success
    19 days ago
    Total: 14298s
    #559280
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    So great to have the first update path test coverage! πŸŽ‰ And thanks for the change record, too!

    XB should aim to follow the best practices being proposed for core by @catch at πŸ“Œ Add generic interface + base class for upgrade paths that require config changes Active .

    Complication: the DB dump doesn't work on all DBs due to

          // Only the SQLite driver has this field map to due to a fatal error
          // error caused by this driver's schema on table introspection.
          // @todo Add support to all drivers in https://drupal.org/i/3343634
          'json:normal'     => 'JSON',
    

    (you can see this in the PHPUnit CI jobs I queued for the non-SQLite DBs)

    I don't want to block a commit on that, so will added explicit skips on those databases, and defer fixing that to a follow-up. The most important thing is the presence of an update path; it's only the test fixture that happens to have DB-specific needs.

  • Pipeline finished with Failed
    18 days ago
    #559639
  • Pipeline finished with Running
    18 days ago
    #559672
  • Pipeline finished with Failed
    18 days ago
    #559657
  • πŸ‡¬πŸ‡§United Kingdom catch
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Given this is the very first update path, I prioritized this issue, to start gaining experience for the post-1.0.0-beta1 reality, and to establish best practices.

    So:

    1. compiled with config entity update path best practices as prescribed by @catch's πŸ“Œ Add generic interface + base class for upgrade paths that require config changes Active
    2. to facilitate that, changed the default of dataDependencies in the PHP object to NULL
    3. tightened dataDependencies to only specify key-value pairs it actually has data dependencies on, which required updating the client-side logic + test coverage
    4. the client-side test coverage only tested the 2 extremes (zero data dependencies and ALL of them), nothing in between β€” that's now tested too πŸ‘
    5. added explicit validation test coverage for dataDependencies
    6. thoroughly manually tested the code component editor UI
    7. skipping the update path test on non-SQLite DBs, created πŸ“Œ Automatically transform the `drupal-11.2.2-with-xb-0.7.2-alpha1.filled` fixture when testing non-SQLite databases Active to transform the test fixture automatically when testing anything but SQLite

    Finally: tried reverting the changes to an exported code component and sure enough:

    1) /Users/wim.leers/core/modules/contrib/experience_builder/src/ExperienceBuilderConfigUpdater.php:88
    JavaScriptComponent config entities without "dataDependencies" property is deprecated in experience_builder:1.0.0 and will be removed in experience_builder:1.0.0. See https://www.drupal.org/node/3538276 β†’

    Triggered by:

    * Drupal\Tests\experience_builder\Kernel\EventSubscriber\RecipeSubscriberTest::testComponentsAndDefaultContentAvailableOnRecipeApply

    πŸ‘† That proves stale recipes/modules will now trigger deprecation errors! πŸ₯³

  • Pipeline finished with Skipped
    18 days ago
    #559736
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Pipeline finished with Failed
    18 days ago
    #559717
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Changes look good to me, thanks

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024