symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
812 stars 295 forks source link

Sharing Twig components #2003

Closed javiereguiluz closed 3 weeks ago

javiereguiluz commented 1 month ago

Hi folks,

I've been using Twig components for some time, but I mostly used them to code some dynamic UI features.

Lately I'm using more and more Twig components to create UI "building blocks" that later I use to create interfaces. I mostly learned this from this nice blog series by @WebMamba: How to integrate Component Architecture into Symfony?

Now, I need to share those UI components with other applications. But, it looks like this is not possible yet.

The use cases:

(1) ACME company uses the same UI in many apps and want to share these Twig components (2) A third-party bundle (e.g. SonataAdmin or EasyAdmin) defines components to ease the building of custom features with the same UI

For your consideration, this is how I imagine this feature in practice. Let's use the first use case about ACME company:

(1) Create a new AcmeUiBundle (2) Define your PHP-based and anonymous Twig components as usual:

acme-ui-bundle/
    src/
        Search/
            SearchComponent.php
    templates/
        Alert/
            Success.html.twig
            Warning.html.twig
        UserAvatar.html.twig
        Search.html.twig

(3) Configure the bundle to tell Symfony that it provides Twig components:

namespace Acme\Bundle\AcmeUiBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
// ...

final class AcmeUiBundle extends Bundle
{
    // ...

    public function addTwigComponents(SomeConfigurator $configurator): void
    {
        $configurator
            // these arguments are the same as in:
            // https://symfony.com/bundles/ux-twig-component/current/index.html#configuration
            ->add(fqcn: __DIR__.'/src', templatesDir: 'templates/', namePrefix: 'Acme')
        ;
    }
}

(4) When you install this bundle in a Symfony app with Symfony UX Twig Component installed, you can start using those imported components like this:

<twig:Acme:Search />
<twig:Acme:Alert:Success> ... </twig:Acme:Alert:Success>

Open questions:

Thanks!

dkarlovi commented 1 month ago

Use the Symfony\Component\DependencyInjection\Extension\ExtensionInterface::getAlias to select the prefix, it's already forced to be unique in the app so bundles make sure to make it unique.

JoppeDC commented 1 month ago

Seems like a solid proposal.

Usecase also looks valid.

Extra example: We currently have a shared component lib with Vue components used in all our apps. This would be the Symfony/Twig alternative to that :)

smnandre commented 1 month ago

So, here we are right now:

☑️ Key points:

☑️ Several aditional points:


😃 Good news: we can do almost all of this for anonymous components, easily (and with some form of configuration for PHP-class-based ones)

And make things work really smootly like this

{{ ux_component('@EasyAdmin:User:Avatar') }}

⚠️ The only thing missing right now is some way to indicate "namespaces" in HTML syntax

<twig:@EasyAdmin:User:Avatar /> 

Something like this ? But it's not valid in the IDE's


📑 Some resources:

yceruto commented 1 month ago

the namespace must be the bundle name (like bundle namespaces today...)

it's actually the bundle extension alias; the bundle name is just the fallback if not specified.

smnandre commented 1 month ago

Regarding assets, any assets provided by a bundle is installed in the public directory during install https://symfony.com/doc/current/bundles.html#bundle-directory-structure

And with AssetMapper, the path of your bundle is automatically mapped: https://symfony.com/doc/current/frontend/asset_mapper.html#third-party-bundles-custom-asset-paths

smnandre commented 1 month ago

Twig components usually require their own CSS/JS assets ... how can we include them automatically when using a component?

Before we start to handle this in UX, i think this should start with some adjustments into AssetMapper, no ?

As of today, TwigComponent have no concept of "page" or "body", so that would be a entirerly new feature i guess.

First idea: a lazy stimulus controller + CSS imports ?

smnandre commented 1 month ago

(personal note: this is precisely one of the reason i'd like to release a UXBundle .. make the link between framework bundle and the different ux components, handle configuration, check dependencies / enabled stuff, ...)

CMH-Benny commented 1 month ago

(personal note: this is precisely one of the reason i'd like to release a UXBundle .. make the link between framework bundle and the different ux components, handle configuration, check dependencies / enabled stuff, ...)

This sounds like a cool idea, it could build the bridge to AssetMapper and WebpackEncore by offering the needed tools to make the asset imports easier, I think automatic handling is cool, but a very simple installation step could also be fine, that then could be automated by flex :)

The only thing missing right now is some way to indicate "namespaces" in HTML syntax

Personally I would love to see the twig: prefix replacable at some point and especially with bundles, which brings a unique Identifier anyway, it would improve readability so much if you could just do:

<MyUXBundle:Button>MyButton</MyUXBundle:Button>

Not sure if this is possible, but if I imagine a quite complex component structure we will otherwise see code like this:

<twig:MyUXBundleBundle:Table:ActionColumn :foo="bar">MyAction</twig:MyUXBundleBundle:Table:ActionColumn>
smnandre commented 1 month ago
<MyUXBundle:Button>MyButton</MyUXBundle:Button>

This is not planned right now, and i'm personally not in favor to do so right now.. as it would be mixed with HTML, custom tags, etc.

t if I imagine a quite complex component structure we will otherwise see code like this [...]

Couple of reactions:

As I see it, common IRL usage would look like this:

<twig:Bootstrap:Alert message="{{ message }}" type="error" />

Or

<twig:Bootstrap:Modal title="Update Post">

    {#  Defining default block exposed by this component #}
    {{ form(update_form) }}

</twig:Bootstrap:Modal>
yceruto commented 1 month ago

Now, I need to share those UI components with other applications. But, it looks like this is not possible yet.

I gave it a try, and while there are some improvements to be made, everything proposed (in the initial description) is currently possible. No hacks needed—perhaps a bit of magic! The analysis and explanation are too lengthy for a single comment, so I decided to cover it in a blog post.

CMH-Benny commented 1 month ago
<MyUXBundle:Button>MyButton</MyUXBundle:Button>

This is not planned right now, and i'm personally not in favor to do so right now.. as it would be mixed with HTML, custom tags, etc.

Yeah, I agree on one hand, on the other hand it would be nice to kinda mimic having custom tags, but of course that is a matter of taste and I can follow your argument. I also see that it's not an easy task, it was just something I thought to make it even more immersive when working with symfony ux, it's not the end of the world if people have to learn this <twig:{whatever} ...> syntax

t if I imagine a quite complex component structure we will otherwise see code like this [...]

Couple of reactions:

* it would be "@myux" or "MyUX", not "MyUxBundleBundle"

* not sure when a user would need to call from their template an `Foo:Table:ActionColum` ... this would probably be in a embeded / overwritten template no ?
  • Yes, that was a typo, scrap one of the "Bundle" of the names, still quite long. But if Bundle will be omitted anyway it probably isn't a big issue then 👍
  • Probably yes, but inside of those overridden templates it will still look kinda crazy, but you're right form themes are also not straigt forward when it comes to al its blocks and hirachy, so having longer tag names isn't the biggest deal. Good point 👍

As I see it, common IRL usage would look like this:

<twig:Bootstrap:Alert message="{{ message }}" type="error" />

Or

<twig:Bootstrap:Modal title="Update Post">

    {#  Defining default block exposed by this component #}
    {{ form(update_form) }}

</twig:Bootstrap:Modal>

Okay, in that case I think it looks pretty cool, thanks for clarifying <3

smnandre commented 1 month ago

I also see that it's not an easy task, it was just something I thought to make it even more immersive when working with symfony ux

That's something i've been thinking a looot about these last months too.. but i really think we first need to make some internal changes in the Twig/Live implementation. To summarize very roughly, Twig component do things "for" LiveComponents that make them less agile/performant when used "massively" in a template.

So let's focus here on the short-term "sharing component" objective, and open another issue for this with a more "middle/long term" goal ?

yceruto commented 1 month ago

See https://github.com/symfony/ux/pull/2019 as the first step. Loading anonymous components from the bundle following a convention