-
-
Notifications
You must be signed in to change notification settings - Fork 427
Add Symfony UX Image component (Slim v1) #3549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| * export-ignore | ||
| .gitattributes export-ignore | ||
| .gitignore export-ignore | ||
| .php-cs-fixer.dist.php export-ignore | ||
| phpunit.dist.xml export-ignore | ||
| tests/ export-ignore | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /vendor/ | ||
| /composer.lock | ||
| /phpunit.xml | ||
| /.phpunit.result.cache | ||
| .phpunit.cache | ||
| /var/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * This file is part of the Symfony package. | ||
| * | ||
| * (c) Fabien Potencier <fabien@symfony.com> | ||
| * | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| use PhpCsFixer\Finder; | ||
| use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; | ||
|
|
||
| return (new PhpCsFixer\Config()) | ||
| ->setParallelConfig(ParallelConfigFactory::detect()) | ||
| ->setRules([ | ||
| '@Symfony' => true, | ||
| '@Symfony:risky' => true, | ||
| ]) | ||
| ->setRiskyAllowed(true) | ||
| ->setFinder( | ||
| (new Finder()) | ||
| ->in(__DIR__.'/src') | ||
| ->in(__DIR__.'/tests') | ||
| ) | ||
| ; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| branches: ['2.x', '3.x'] | ||
| maintained_branches: ['3.x'] | ||
| doc_dir: 'doc' |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||
| # CHANGELOG | ||||||
|
|
||||||
| ## 2.32.0 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| - Add Symfony UX Image component | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file is empty; you need the associated content. You can find it on other components. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # Symfony UX Image | ||
|
|
||
| Optimized responsive image components with automatic format conversion and Core Web Vitals optimization. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| composer require symfony/ux-image | ||
| ``` | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Basic Usage | ||
|
|
||
| ```twig | ||
| {# Simple responsive image #} | ||
| <twig:ux:img | ||
| src="/images/hero.jpg" | ||
| alt="Hero image" | ||
| width="100vw md:80vw" | ||
| /> | ||
| ``` | ||
|
|
||
| ### Art Direction | ||
|
|
||
| ```twig | ||
| {# Different aspect ratios per breakpoint #} | ||
| <twig:ux:picture | ||
| src="/images/banner.jpg" | ||
| alt="Banner" | ||
| width="100vw md:80vw" | ||
| ratio="sm:1:1 md:16:9" | ||
| /> | ||
| ``` | ||
|
|
||
| ### Without Twig Components | ||
|
|
||
| ```twig | ||
| {{ ux_image('/images/hero.jpg', 'Hero image', { width: '100vw md:80vw' }) }} | ||
| ``` | ||
|
|
||
| ## Features | ||
|
|
||
| - **Automatic WebP conversion** — via configurable providers | ||
| - **Responsive srcset/sizes** — viewport-based width syntax (`100vw md:80vw`) | ||
| - **Density support** — Retina displays (`densities="x1 x2"`) | ||
| - **Art direction** — different crops per breakpoint with `<twig:ux:picture>` | ||
| - **Provider-based** — works with local files, CDN, or any image service | ||
| - **No hard dependencies** — no image processing library required | ||
|
|
||
| ## Configuration | ||
|
|
||
| ```yaml | ||
| # config/packages/ux_image.yaml | ||
| ux_image: | ||
| default_provider: passthrough | ||
| breakpoints: | ||
| sm: 640 | ||
| md: 768 | ||
| lg: 1024 | ||
| xl: 1280 | ||
| 2xl: 1536 | ||
| defaults: | ||
| format: webp | ||
| quality: 80 | ||
| loading: lazy | ||
| fit: cover | ||
| ``` | ||
|
|
||
| ### Using a CDN | ||
|
|
||
| ```yaml | ||
| ux_image: | ||
| providers: | ||
| cloudflare: | ||
| pattern: '/cdn-cgi/image/w={width},f={format},q={quality}/{src}' | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| MIT License — see [LICENSE](LICENSE) for details. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| { | ||
| "name": "symfony/ux-image", | ||
| "type": "symfony-bundle", | ||
| "description": "Optimized responsive image components with automatic format conversion and Core Web Vitals optimization.", | ||
| "keywords": [ | ||
| "symfony-ux", | ||
| "symfony", | ||
| "image", | ||
| "responsive", | ||
| "webp", | ||
| "optimization", | ||
| "core web vitals" | ||
| ], | ||
| "homepage": "https://symfony.com", | ||
| "license": "MIT", | ||
| "authors": [ | ||
| { | ||
| "name": "Aleksey Razbakov", | ||
| "email": "aleksey@razbakov.com" | ||
| }, | ||
| { | ||
| "name": "Symfony Community", | ||
| "homepage": "https://symfony.com/contributors" | ||
| } | ||
| ], | ||
| "autoload": { | ||
| "psr-4": { | ||
| "Symfony\\UX\\Image\\": "src/" | ||
| } | ||
| }, | ||
| "autoload-dev": { | ||
| "psr-4": { | ||
| "Symfony\\UX\\Image\\Tests\\": "tests/" | ||
| } | ||
| }, | ||
| "require": { | ||
| "php": ">=8.4", | ||
| "symfony/framework-bundle": "^7.4|^8.0", | ||
| "symfony/twig-bundle": "^7.4|^8.0", | ||
| "symfony/config": "^7.4|^8.0", | ||
| "symfony/dependency-injection": "^7.4|^8.0", | ||
| "symfony/http-kernel": "^7.4|^8.0", | ||
| "symfony/options-resolver": "^7.4|^8.0", | ||
| "twig/twig": "^3.0" | ||
| }, | ||
| "require-dev": { | ||
| "friendsofphp/php-cs-fixer": "^3.65", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be deleted here. |
||
| "phpunit/phpunit": "^11.1|^12.0", | ||
| "symfony/browser-kit": "^7.4|^8.0", | ||
| "symfony/css-selector": "^7.4|^8.0", | ||
| "symfony/debug-bundle": "^7.4|^8.0", | ||
| "symfony/phpunit-bridge": "^7.4|^8.0", | ||
| "symfony/yaml": "^7.4|^8.0", | ||
| "symfony/ux-twig-component": "^2.21|^3.0" | ||
| }, | ||
| "suggest": { | ||
| "symfony/ux-twig-component": "For using the Twig component syntax (<twig:ux:img>)" | ||
| }, | ||
| "config": { | ||
| "sort-packages": true | ||
| }, | ||
| "conflict": { | ||
| "symfony/flex": "<1.13" | ||
| }, | ||
| "extra": { | ||
| "thanks": { | ||
| "name": "symfony/ux", | ||
| "url": "https://github.com/symfony/ux" | ||
| } | ||
| }, | ||
| "minimum-stability": "dev" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| services: | ||
| _defaults: | ||
| autowire: true | ||
| autoconfigure: true | ||
|
|
||
| Symfony\UX\Image\Provider\ProviderRegistry: | ||
| public: true | ||
|
|
||
| Symfony\UX\Image\Service\Transformer: | ||
| arguments: | ||
| $breakpoints: '%ux_image.breakpoints%' | ||
|
|
||
| Symfony\UX\Image\Twig\Img: | ||
| tags: | ||
| - { name: 'twig.component' } | ||
|
|
||
| Symfony\UX\Image\Twig\Picture: | ||
| tags: | ||
| - { name: 'twig.component' } | ||
|
|
||
| # Auto-register providers | ||
| Symfony\UX\Image\Provider\ProviderInterface: | ||
| abstract: true | ||
| tags: | ||
| - { name: 'ux_image.provider' } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review the file to make it look something like this: https://github.com/symfony/ux/blob/3.x/src/Native/phpunit.xml.dist. |
||
| <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd" | ||
| bootstrap="vendor/autoload.php" | ||
| colors="true" | ||
| failOnRisky="true" | ||
| failOnWarning="true" | ||
| > | ||
| <testsuites> | ||
| <testsuite name="Symfony UX Image Test Suite"> | ||
| <directory>./tests/</directory> | ||
| </testsuite> | ||
| </testsuites> | ||
| <source> | ||
| <include> | ||
| <directory suffix=".php">./src</directory> | ||
| </include> | ||
| </source> | ||
| </phpunit> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * This file is part of the Symfony package. | ||
| * | ||
| * (c) Fabien Potencier <fabien@symfony.com> | ||
| * | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace Symfony\UX\Image\DependencyInjection\Compiler; | ||
|
|
||
| use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
| use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
| use Symfony\Component\DependencyInjection\Reference; | ||
| use Symfony\UX\Image\Provider\ProviderRegistry; | ||
|
|
||
| /** | ||
| * Registers all tagged providers into the ProviderRegistry. | ||
| * | ||
| * @author Aleksey Razbakov <aleksey@razbakov.com> | ||
| */ | ||
| class ProviderPass implements CompilerPassInterface | ||
| { | ||
| public function process(ContainerBuilder $container): void | ||
| { | ||
| if (!$container->hasDefinition(ProviderRegistry::class)) { | ||
| return; | ||
| } | ||
|
|
||
| $registry = $container->getDefinition(ProviderRegistry::class); | ||
| $taggedServices = $container->findTaggedServiceIds('ux_image.provider'); | ||
|
|
||
| foreach ($taggedServices as $id => $tags) { | ||
| foreach ($tags as $attributes) { | ||
| $name = $attributes['name'] ?? $id; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| $registry->addMethodCall('addProvider', [new Reference($id)]); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * This file is part of the Symfony package. | ||
| * | ||
| * (c) Fabien Potencier <fabien@symfony.com> | ||
| * | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace Symfony\UX\Image\DependencyInjection; | ||
|
|
||
| use Symfony\Component\Config\Definition\Builder\TreeBuilder; | ||
| use Symfony\Component\Config\Definition\ConfigurationInterface; | ||
|
|
||
| /** | ||
| * @author Aleksey Razbakov <aleksey@razbakov.com> | ||
| */ | ||
| class Configuration implements ConfigurationInterface | ||
| { | ||
| public function getConfigTreeBuilder(): TreeBuilder | ||
| { | ||
| $treeBuilder = new TreeBuilder('ux_image'); | ||
| $rootNode = $treeBuilder->getRootNode(); | ||
|
|
||
| $rootNode | ||
| ->children() | ||
| ->scalarNode('default_provider') | ||
| ->defaultValue('passthrough') | ||
| ->info('The default provider to use when none is specified.') | ||
| ->end() | ||
| ->arrayNode('providers') | ||
| ->useAttributeAsKey('name') | ||
| ->arrayPrototype() | ||
| ->children() | ||
| ->scalarNode('pattern')->end() | ||
| ->scalarNode('assets_path')->end() | ||
| ->arrayNode('config') | ||
| ->useAttributeAsKey('key') | ||
| ->scalarPrototype()->end() | ||
| ->end() | ||
| ->end() | ||
| ->end() | ||
| ->end() | ||
| ->arrayNode('breakpoints') | ||
| ->useAttributeAsKey('name') | ||
| ->integerPrototype()->end() | ||
| ->defaultValue([ | ||
| 'sm' => 640, | ||
| 'md' => 768, | ||
| 'lg' => 1024, | ||
| 'xl' => 1280, | ||
| '2xl' => 1536, | ||
| ]) | ||
| ->end() | ||
| ->arrayNode('defaults') | ||
| ->addDefaultsIfNotSet() | ||
| ->children() | ||
| ->scalarNode('format')->defaultValue('webp')->end() | ||
| ->integerNode('quality')->defaultValue(80)->end() | ||
| ->scalarNode('loading')->defaultValue('lazy')->end() | ||
| ->scalarNode('fit')->defaultValue('cover')->end() | ||
| ->scalarNode('focal')->defaultValue('center')->end() | ||
| ->scalarNode('fallback')->defaultValue('lg')->end() | ||
| ->scalarNode('fallback_format')->defaultValue('auto')->end() | ||
| ->end() | ||
| ->end() | ||
| ->end() | ||
| ; | ||
|
|
||
| return $treeBuilder; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.