Have a way to implement the a preprocess function per each component (ideally in the same folder)

Created on 14 November 2022, about 2 years ago
Updated 17 January 2023, about 2 years ago

Problem/Motivation

I think it could be interesting to allow some data preprocessing (validation, setting default values, etc.) via a preprocess function inside the component's folder.

I have not found any way to do it with the current codebase.

✨ Feature request
Status

Needs work

Version

1.0

Component
single-directory componentsΒ  β†’

Last updated about 5 hours ago

Created by

πŸ‡ͺπŸ‡ΈSpain unstatu

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

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡©πŸ‡ͺGermany geek-merlin Freiburg, Germany

    FTR: I agree and disagree. I'd like to have directory-local preprocess, AND altering by components.

    Thought about it a while and the real problem is HOW we do preprocessing, that formatting is done in advance.
    I'll think a bit more about it and write that down in a separate issue, with a title sth like "Allow templates to request formatters and other services".

  • πŸ‡©πŸ‡ͺGermany geek-merlin Freiburg, Germany

    Here we go!

  • πŸ‡ΊπŸ‡ΈUnited States ctrladel North Carolina, USA

    @e0ipso is there an extension point that would allow another module to add preprocess hooks or the preprocess approach I described to SDC? Perhaps that'd be a decent compromise.

    I don't see custom twig functions and filters as being a good enough stand in for preprocessing on the backend.

    • It's not any more approachable than plugins to a frontend developer. In some ways it's worse since all the functions and filters will likely end up in one file. Taking twig tweaks class as an sample of what could be needed on a project https://git.drupalcode.org/project/twig_tweak/-/blob/3.x/src/TwigTweakEx..., I wouldn't call this super approachable for a non backend dev.
    • As I mentioned in #3346715-5 🌱 Make twig sandboxed again, and empower themers by injecting services Closed: won't fix direct access of values in during render creates a lot of nuance and possibility for mistakes.
    • Custom functions and filters reduce the portability of twig templates. The custom functions/filters have to be stubbed or fully reimplemented to render the template in a system other then Drupal. I'm mainly thinking if you're trying to render the template in a js tool like Storybook.
  • πŸ‡ΊπŸ‡ΈUnited States dave reid Nebraska USA
  • Status changed to Active almost 2 years ago
  • e0ipso Can Picafort

    Let's continue this discussion in the Core issue queue. Since we don't have preprocess for components now, this is not a beta blocker, but a new feature request.

  • πŸ‡ͺπŸ‡ΈSpain idiaz.roncero Madrid

    I was initially convinced against the need for a preprocess function for components, as I agreed with this:

    We want to keep the principle of "everything to render your component is in a single directory". Hooks will defeat that.

    Nevertheless, porting my first component (an implementation of Bootstrap 5 button for Radix theme), i've found that a little bit of preprocessing might be ok, at least for components where some props might have dependencies on others.

    For example, this (simplified) Button component might render as a <button> or a <a> HTML element.

    {#
    /**
     * @file
     * Template for a button component.
     *
     * Available config:
     * - type: primary | secondary | success | danger | warning | info | light | dark
     * - tag: button, a
     * - url: string
     * - attributes: Attributes array.
     */
    #}
    
    {% if tag == 'a' and url is not empty %}
      {% set attributes = attributes.setAttribute('href', url) %}
    {% endif %}
    
      <{{ tag }}{{ attributes.addClass(button_classes) }}>
        {% block button_content %}
          {{ button_content }}
        {% endblock %}
      </{{ tag }}>
    
    

    When rendered as an <a> link, you can pass a url prop... and this prop won't be needed if you are using a <button>

    The code works, but we are missing the following features to make it perfect:

    • Passing tag: a and url: null should throw an error. Right now it will render a link with no href.
    • On the opposite, passing tag: button and url: whathever should at least give a friendly warning.
    • Ideally, this component could benefit from some checks on wether url string is actually a URL.

    To summarize, I think there is room for some back-end preprocessing of components, at least when it comes to establishing dependencies between props and performing some basic checks, sanitizations and validations. Many of these things can be done directly on twig but they are cumbersome or verbose, and many others might not even be possible (can Twig throw exceptions / warnings?)

    Maybe the middle ground could be a preprocess function for components that doesn't let you alter the $variables array (is not passed by reference) but still let you perform checks, validations, and inform the user about any potential misgiving?

  • πŸ‡ͺπŸ‡ΈSpain idiaz.roncero Madrid

    Of course another solution for the before mentioned problem is to increase the *.components.yml API, but maybe that's increasing the complexity a bit too much:

    name: Button
    props:
      type: object
      properties:
        url:
          type: string
          title: Url
          description: A URL target for link buttons. 
          # New entries for declaring dependencies between props and matching values
          dependencies:
            tag: a
          match: /regexp-for-URL/g
    
  • e0ipso Can Picafort

    I think most of these are solved in JSON-Schema.

    Perhaps you need to define your prop with a more complex schema, if you want to leverage these advanced features.

    Pseudocode:

    props:
      element:
        oneOf:
          -
            # Defines a Button element with its attributes.
            type: object
            additionalProperties: false
            required:
              - tag
            properties:
              tag:
                type: string
                enum:
                  - button
          -
            type: object
            additionalProperties: false
            required:
              - tag
              - attributes
            properties:
              tag:
                type: string
                enum:
                  - a
              attributes:
                type: object
                additionalProperties: false
                required:
                  - href
                properties:
                  href:
                    type: string
                    format: uri
    

    Would that work? You tell me :-P

  • e0ipso Can Picafort

    However. There was some chatter at Drupal con to augment the JSON-Schema further to allow referencing constraints. Much in the way config validation is doing it in kwalify.

  • πŸ‡§πŸ‡ͺBelgium dieterholvoet Brussels
  • πŸ‡©πŸ‡ͺGermany gooddev

    If someone find it useful: One simple approach could be to write your custom twig function and call that inside of the SDC component. e.g. for breakpoint or similar.

    {% set breakpoints = get_breakpoints() %}

  • πŸ‡ΊπŸ‡ΈUnited States xjm
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Based on #9 and #13, I think we can change this issue to a documentation issue, where we document why this is not supported?

  • πŸ‡«πŸ‡·France pdureau Paris

    If someone find it useful: One simple approach could be to write your custom twig function and call that inside of the SDC component. e.g. for breakpoint or similar.

    {% set breakpoints = get_breakpoints() %}

    Careful with such an approach. The component must act as a "sandbox", getting the props & slots and rendering them, without any calls to Drupal API, and hiding such a call in a custom Twig function is not the solution. Can you send the breakpoints as a prop instead?

  • Hi all,
    Sorry for joining in this late onto the party! I've gone through all the threads, but unfortunately seems like its a bit inconclusive till now.
    I've echoed with some of the pointers mentioned above, along with that I'm a bit clouded on some other pointers as well!
    First and foremost, I agree with @e0ipso's argument of not having preprocess hooks as

    Hooks allow you to create spooky effects from far away.

    and that'll certainly defeat the purpose of SDC where we'll have all the files wrapped inside a "Single Directory." With that being said several downsides can be as follows IMO:

    1. On traditional way of theming that we're used to till now, have a clear sort of MVC structure having clarity on the separation of concern, where we pre-process all(mostly) the logics from a hook and just pass that values through some variables onto our twig template which helps to render it efficiently. Agglomerating all the processing and computation will blur out that separation of concern by clubbing 'V' and 'C' of MVC. Which in turn defeats the purpose of SDC, to incorporate better separation of concern.

    2. On moving all pre-processing/computation on twig might affect its rendering time as well as more prone towards layout shifts in case of delayed output, as twig might not be that much efficient for handling complex computation as that of Php. Hence it'll negatively impact performance and UX.

    3. And as @ctrladel mentioned

    direct access of values in during render creates a lot of nuance and possibility for mistakes.

    in terms of input sanitization, efficient caching issues for frequently changing data, which otherwise might lead to rendering stale data and security concerns.

    4. Along with @ctrladel's pointer:

    I don't see custom twig functions and filters as being a good enough stand in for preprocessing on the backend.

    I'd like to add, Incorporating custom twig functions and filters for every complex custom operations will be a tedious task for FE devs as well as that'll require a steep learning curve,otherwise it'll rather increase the dependency towards BE devs, which will also defeat one of the main aspect of SDC which promises to empower FE devs and fasten their workflow by independently developing and building components irrespective of the BE architecture and intervention of BE devs.

    5. Putting all such preprocess logics inside twigs will not only reduce its portability but also hampers the readability. Though it was not that much great from the beginning itself IMO, but now with more complex logic and custom implementation the template files are intended to grow enormously, leading to very less readability. Perhaps that can be mitigated by integrating Storybook like tools for our SDC components, then we can use autodocs like feature to prepare nice and easy documentation for our components which can be readable by end users also, though that integration is quite a bit tricky (I had to scratch my head for couple of days to integrate SB with SDC properly :( )

    6. I'm a bit curious on the aspect, if and when we need to build quite a few components which apparently seems to be specific to that of our BE architecture and pulls values that can only be computed by making DB queries in a custom fashion, how SDC will bring independence to FE devs in that scenario?
    Even on the previous ways, we had to be dependent to BE devs to pass in those values, so I won't mind hearing that as of now that sort of decoupling is not possible through SDC :)

    Please feel free to correct me if I'm having any wrong perception about these, I'm pretty new to SDC.

    TIA :)

  • My 2 cents, replying to the original use case explained in #2 (e.g. preprocess things for an external url).

    Whenever I need a preprocess I use a custom Twig function / Twig filter.

    Example below shows a custom filter is_external.

    <a{{ attributes }}>
      {{ text }}
      {% if url|is_external %}
        {# β‡— Print an icon here β‡— #}
      {% endif %}
    </a>
    

    See this post for reference about how to create a custom Twig filter/function.

    Unless I'm wrong, this resolves the needs of providing a preprocess function.

    We can 1) redeclare the template of a SDC in our theme and 2) use a custom filter in this new Twig for the specific use case.

  • πŸ‡ΊπŸ‡ΈUnited States adamzimmermann

    I'm working on a new project where they have built their own version of SDC with a slightly different feature set, but a similar ideology. When I saw it I thought of this issue and wanted to share the approach.

    We are integrating with generic Twig templates from a component library, and needed a way to map data to specific variables with specific formatting/values in a way that kept that mapping connected to the Twig template it was used with.

    The use of a custom plugin system was the solution they architected. Each component/Twig template has an associated plugin class that does the mapping, but it could be used for preprocessing too.

    I agree with @CtrlADel here. We should not have unrelated files change how the component renders, we'd be violating the Single in SDC.

    I would second this, and it seems that I'm sharing something similar to the plugin idea shared above in #3321203-18: Have a way to implement the a preprocess function per each SDC component (ideally in the same folder) β†’ .

    Hope this helps.

  • Hi @adamzimmermann,
    Incorporating a custom plugin system apparently sounds good but I'm afraid that it'll be too complex to do so for each and every component individually, and certainly be an overkill for small to medium scale projects.
    FE devs might not be that much familiar with creation and implementation of custom plugin system and its corresponding management and discovery methods, hence they've to defer to BE devs in such scenarios, which nullifies the independence!

    Though I've seen that being implemented on certain themes like Civic theme or so, but those are huge in scale. And its quite difficult for FE devs to start off with such implementation and extending the same on their projects in custom way.

    I was actually looking for a simpler solution, stressing upon the independence factor!

    Thanks :)

  • πŸ‡«πŸ‡·France pdureau Paris

    Whenever I need a preprocess I use a custom Twig function / Twig filter.
    Example below shows a custom filter is_external.

    This is_external Twig function may be OK because it doesn't seem to need to call Drupal DB, or API, or config... Just parsing the URL string (with parse_url or other) may be enough.

    Is it really the case, Matthieu?

  • Yes and no, Pierre.

    I use the requestContext to grab the base URL - as per this snippet as example - but I could certainly get it differently and thus avoid any dependencies.

  • πŸ‡ΊπŸ‡ΈUnited States sharkbaitdc
Production build 0.71.5 2024