diff --git a/src/Asset/EntrypointRenderer.php b/src/Asset/EntrypointRenderer.php index 08972e9..68feb82 100644 --- a/src/Asset/EntrypointRenderer.php +++ b/src/Asset/EntrypointRenderer.php @@ -6,10 +6,10 @@ class EntrypointRenderer { - private $entrypointsLookup; - private $tagRenderer; - private $router; - private $useAbsoluteUrl; + private EntrypointsLookupCollection $entrypointsLookupCollection; + private TagRendererCollection $tagRendererCollection; + private bool $useAbsoluteUrl; + private RouterInterface $router; private $returnedViteClients = []; private $returnedReactRefresh = []; @@ -18,63 +18,76 @@ class EntrypointRenderer private $hasReturnedViteLegacyScripts = false; public function __construct( - EntrypointsLookup $entrypointsLookup, - TagRenderer $tagRenderer, + EntrypointsLookupCollection $entrypointsLookupCollection, + TagRendererCollection $tagRendererCollection, bool $useAbsoluteUrl, RouterInterface $router = null ) { - $this->entrypointsLookup = $entrypointsLookup; - $this->tagRenderer = $tagRenderer; + $this->entrypointsLookupCollection = $entrypointsLookupCollection; + $this->tagRendererCollection = $tagRendererCollection; $this->useAbsoluteUrl = $useAbsoluteUrl; $this->router = $router; } - public function renderScripts(string $entryName, array $options = [], $buildName = null): string + private function getEntrypointsLookup(string $configName = null): EntrypointsLookup { - if (!$this->entrypointsLookup->hasFile($buildName)) { + return $this->entrypointsLookupCollection->getEntrypointsLookup($configName); + } + + private function getTagRenderer(string $configName = null): TagRenderer + { + return $this->tagRendererCollection->getTagRenderer($configName); + } + + public function renderScripts(string $entryName, array $options = [], $configName = null): string + { + $entrypointsLookup = $this->getEntrypointsLookup($configName); + $tagRenderer = $this->getTagRenderer($configName); + + if (!$entrypointsLookup->hasFile()) { return ''; } - $useAbsoluteUrl = $this->shouldUseAbsoluteURL($options, $buildName); + $useAbsoluteUrl = $this->shouldUseAbsoluteURL($options, $configName); $content = []; - $viteServer = $this->entrypointsLookup->getViteServer($buildName); - $isBuild = $this->entrypointsLookup->isBuild($buildName); + $viteServer = $entrypointsLookup->getViteServer(); + $isBuild = $entrypointsLookup->isBuild(); if (false !== $viteServer) { // vite server is active - if (!isset($this->returnedViteClients[$buildName])) { - $content[] = $this->tagRenderer->renderTag('script', [ + if (!isset($this->returnedViteClients[$configName])) { + $content[] = $tagRenderer->renderTag('script', [ 'type' => 'module', 'src' => $viteServer['origin'].$viteServer['base'].'@vite/client', ]); - $this->returnedViteClients[$buildName] = true; + $this->returnedViteClients[$configName] = true; } - if (!isset($this->returnedReactRefresh[$buildName]) && isset($options['dependency']) && 'react' === $options['dependency']) { - $content[] = $this->tagRenderer->renderReactRefreshInline($viteServer['origin'].$viteServer['base']); - $this->returnedReactRefresh[$buildName] = true; + if (!isset($this->returnedReactRefresh[$configName]) && isset($options['dependency']) && 'react' === $options['dependency']) { + $content[] = $tagRenderer->renderReactRefreshInline($viteServer['origin'].$viteServer['base']); + $this->returnedReactRefresh[$configName] = true; } } elseif ( - $this->entrypointsLookup->isLegacyPluginEnabled($buildName) + $entrypointsLookup->isLegacyPluginEnabled() && !$this->hasReturnedViteLegacyScripts ) { /* legacy section when vite server is inactive */ /* set or not the __vite_is_modern_browser variable */ - $content[] = $this->tagRenderer::DETECT_MODERN_BROWSER_CODE; + $content[] = $tagRenderer::DETECT_MODERN_BROWSER_CODE; /* if your browser understands the modules but not dynamic import, * load the legacy entrypoints */ - $content[] = $this->tagRenderer::DYNAMIC_FALLBACK_INLINE_CODE; + $content[] = $tagRenderer::DYNAMIC_FALLBACK_INLINE_CODE; /* Safari 10.1 supports modules, but does not support the `nomodule` attribute * it will load '.PHP_EOL; } - public function renderScriptFile($extraAttributes = [], $content = '', $buildName = null, $isBuild = true): string + public function renderScriptFile($extraAttributes = [], $content = '', $isBuild = true): string { - if (is_null($buildName)) { - $buildName = $this->defaultBuild; - } - $event = new RenderAssetTagEvent( RenderAssetTagEvent::TYPE_SCRIPT, - array_merge($this->builds[$buildName]['script_attributes'], $extraAttributes), + array_merge($this->scriptAttributes, $extraAttributes), $isBuild ); if (null !== $this->eventDispatcher) { @@ -82,12 +79,8 @@ public function renderScriptFile($extraAttributes = [], $content = '', $buildNam return $this->renderTag('script', $event->getAttributes(), $content); } - public function renderLinkStylesheet($fileName, $extraAttributes = [], $buildName = null, $isBuild = true): string + public function renderLinkStylesheet($fileName, $extraAttributes = [], $isBuild = true): string { - if (is_null($buildName)) { - $buildName = $this->defaultBuild; - } - $attributes = [ 'rel' => 'stylesheet', 'href' => $fileName, @@ -95,7 +88,7 @@ public function renderLinkStylesheet($fileName, $extraAttributes = [], $buildNam $event = new RenderAssetTagEvent( RenderAssetTagEvent::TYPE_LINK, - array_merge($attributes, $this->builds[$buildName]['link_attributes'], $extraAttributes), + array_merge($attributes, $this->linkAttributes, $extraAttributes), $isBuild ); if (null !== $this->eventDispatcher) { @@ -105,12 +98,8 @@ public function renderLinkStylesheet($fileName, $extraAttributes = [], $buildNam return $this->renderTag('link', $event->getAttributes()); } - public function renderLinkPreload($fileName, $extraAttributes = [], $buildName = null, $isBuild = true): string + public function renderLinkPreload($fileName, $extraAttributes = [], $isBuild = true): string { - if (is_null($buildName)) { - $buildName = $this->defaultBuild; - } - $attributes = [ 'rel' => 'modulepreload', 'href' => $fileName, @@ -118,7 +107,7 @@ public function renderLinkPreload($fileName, $extraAttributes = [], $buildName = $event = new RenderAssetTagEvent( RenderAssetTagEvent::TYPE_PRELOAD, - array_merge($attributes, $this->builds[$buildName]['link_attributes'], $extraAttributes), + array_merge($attributes, $this->linkAttributes, $extraAttributes), $isBuild ); if (null !== $this->eventDispatcher) { diff --git a/src/Asset/TagRendererCollection.php b/src/Asset/TagRendererCollection.php new file mode 100644 index 0000000..ccd6163 --- /dev/null +++ b/src/Asset/TagRendererCollection.php @@ -0,0 +1,33 @@ +tagRendererLocator = $tagRendererLocator; + $this->defaultConfigName = $defaultConfigName; + } + + public function getTagRenderer(string $configName = null): TagRenderer + { + if (is_null($configName)) { + $configName = $this->defaultConfigName; + } + + if (!$this->tagRendererLocator->has($configName)) { + throw new UndefinedConfigNameException(sprintf('The config "%s" is not set.', $configName)); + } + + return $this->tagRendererLocator->get($configName); + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index ccd665c..ab02df2 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -50,8 +50,38 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->scalarNode('default_build') ->defaultValue(null) + ->setDeprecated('pentatrion/vite-bundle', '6.0.0', 'The "%node%" option is deprecated. Use "default_config" instead.') ->end() ->arrayNode('builds') + ->setDeprecated('pentatrion/vite-bundle', '6.0.0', 'The "%node%" option is deprecated. Use "configs" instead.') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('build_directory') + ->defaultValue('build') + ->end() + ->arrayNode('script_attributes') + ->info('Key/value pair of attributes to render on all script tags') + ->example('{ defer: true, referrerpolicy: "origin" }') + ->normalizeKeys(false) + ->scalarPrototype() + ->end() + ->end() + ->arrayNode('link_attributes') + ->info('Key/value pair of attributes to render on all CSS link tags') + ->example('{ referrerpolicy: "origin" }') + ->normalizeKeys(false) + ->scalarPrototype() + ->end() + ->end() + ->end() + ->end() + ->end() + ->scalarNode('default_config') + ->defaultValue(null) + ->end() + ->arrayNode('configs') + ->setDeprecated('pentatrion/vite-bundle', '6.0.0', 'The "%node%" option is deprecated. Use "configs" instead.') ->useAttributeAsKey('name') ->arrayPrototype() ->children() diff --git a/src/DependencyInjection/PentatrionViteExtension.php b/src/DependencyInjection/PentatrionViteExtension.php index 86164d3..6562ec9 100644 --- a/src/DependencyInjection/PentatrionViteExtension.php +++ b/src/DependencyInjection/PentatrionViteExtension.php @@ -2,74 +2,141 @@ namespace Pentatrion\ViteBundle\DependencyInjection; +use Pentatrion\ViteBundle\Asset\EntrypointsLookup; +use Pentatrion\ViteBundle\Asset\TagRenderer; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class PentatrionViteExtension extends Extension { - public static function prepareBase($base) - { - $base = '/' !== substr($base, 0, 1) ? '/'.$base : $base; - $base = '/' !== substr($base, -1) ? $base.'/' : $base; - - return $base; - } - - public static function preparePublicDirectory($publicDir) - { - $publicDir = '/' !== substr($publicDir, 0, 1) ? '/'.$publicDir : $publicDir; - $publicDir = rtrim($publicDir, '/'); - - return $publicDir; - } - - public function load(array $configs, ContainerBuilder $container): void + public function load(array $bundleConfigs, ContainerBuilder $container): void { $loader = new YamlFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); $loader->load('services.yaml'); - $config = $this->processConfiguration( - $this->getConfiguration($configs, $container), - $configs + $bundleConfig = $this->processConfiguration( + $this->getConfiguration($bundleConfigs, $container), + $bundleConfigs ); + if (isset($bundleConfig['builds']) && !isset($bundleConfig['configs'])) { + $bundleConfig['configs'] = $bundleConfig['builds']; + } + if (isset($bundleConfig['default_build']) && !isset($bundleConfig['default_config'])) { + $bundleConfig['default_config'] = $bundleConfig['default_build']; + } + if ( - count($config['builds']) > 0) { - if (is_null($config['default_build']) || !isset($config['builds'][$config['default_build']])) { - throw new \Exception('Invalid default_build, choose between : '.join(', ', array_keys($config['builds']))); + count($bundleConfig['configs']) > 0) { + if (is_null($bundleConfig['default_config']) || !isset($bundleConfig['configs'][$bundleConfig['default_config']])) { + throw new \Exception('Invalid default_config, choose between : '.join(', ', array_keys($bundleConfig['configs']))); } - $defaultBuild = $config['default_build']; - $builds = []; - foreach ($config['builds'] as $buildName => $build) { - $builds[$buildName] = [ - 'base' => self::prepareBase($build['build_directory']), - 'script_attributes' => $build['script_attributes'], - 'link_attributes' => $build['link_attributes'], - ]; + $defaultConfigName = $bundleConfig['default_config']; + $lookupFactories = []; + $tagRendererFactories = []; + $configs = []; + + foreach ($bundleConfig['configs'] as $configName => $config) { + $configs[$configName] = $configPrepared = self::prepareConfig($config); + $lookupFactories[$configName] = $this->entrypointsLookupFactory($container, $configName, $configPrepared); + $tagRendererFactories[$configName] = $this->tagRendererFactory($container, $configName, $configPrepared); } } else { - $defaultBuild = 'default'; - $builds = [ - 'default' => [ - 'base' => self::prepareBase($config['build_directory']), - 'script_attributes' => $config['script_attributes'], - 'link_attributes' => $config['link_attributes'], - ], + $defaultConfigName = '_default'; + $configs[$defaultConfigName] = $configPrepared = self::prepareConfig($bundleConfig); + + $lookupFactories = [ + '_default' => $this->entrypointsLookupFactory($container, $defaultConfigName, $configPrepared), + ]; + $tagRendererFactories = [ + '_default' => $this->tagRendererFactory($container, $defaultConfigName, $configPrepared), ]; } - $container->setParameter('pentatrion_vite.public_directory', self::preparePublicDirectory($config['public_directory'])); - $container->setParameter('pentatrion_vite.default_build', $defaultBuild); - $container->setParameter('pentatrion_vite.builds', $builds); + $container->setParameter('pentatrion_vite.public_directory', self::preparePublicDirectory($bundleConfig['public_directory'])); + $container->setParameter('pentatrion_vite.default_config', $defaultConfigName); + $container->setParameter('pentatrion_vite.configs', $configs); + + $container->setParameter('pentatrion_vite.absolute_url', $bundleConfig['absolute_url']); + $container->setParameter('pentatrion_vite.proxy_origin', $bundleConfig['proxy_origin']); + $container->setParameter('pentatrion_vite.throw_on_missing_entry', $bundleConfig['throw_on_missing_entry']); - $container->setParameter('pentatrion_vite.absolute_url', $config['absolute_url']); - $container->setParameter('pentatrion_vite.proxy_origin', $config['proxy_origin']); - $container->setParameter('pentatrion_vite.throw_on_missing_entry', $config['throw_on_missing_entry']); + $container->getDefinition('pentatrion_vite.entrypoints_lookup_collection') + ->addArgument(ServiceLocatorTagPass::register($container, $lookupFactories)) + ->addArgument($defaultConfigName); - $container->getDefinition('vite.tag_renderer') - ->replaceArgument(0, $defaultBuild) - ->replaceArgument(1, $builds); + $container->getDefinition('pentatrion_vite.tag_renderer_collection') + ->addArgument(ServiceLocatorTagPass::register($container, $tagRendererFactories)) + ->addArgument($defaultConfigName); + + // $container->getDefinition('pentatrion_vite.tag_renderer') + // ->replaceArgument(0, $defaultConfigName) + // ->replaceArgument(1, $configs); + } + + private function entrypointsLookupFactory( + ContainerBuilder $container, + string $configName, + array $config + ): Reference { + $id = $this->getServiceId('entrypoints_lookup', $configName); + $arguments = [ + '%kernel.project_dir%%pentatrion_vite.public_directory%', + $config, + '%pentatrion_vite.throw_on_missing_entry%', + ]; + $definition = new Definition(EntrypointsLookup::class, $arguments); + $container->setDefinition($id, $definition); + + return new Reference($id); + } + + private function tagRendererFactory( + ContainerBuilder $container, + string $configName, + array $config + ): Reference { + $id = $this->getServiceId('tag_renderer', $configName); + $arguments = [ + $config['script_attributes'], + $config['link_attributes'], + new Reference('event_dispatcher', ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]; + $definition = new Definition(TagRenderer::class, $arguments); + $container->setDefinition($id, $definition); + + return new Reference($id); + } + + private function getServiceId(string $prefix, string $configName): string + { + return sprintf('pentatrion_vite.%s[%s]', $prefix, $configName); + } + + public static function prepareConfig(array $config): array + { + $base = $config['build_directory']; + $base = '/' !== substr($base, 0, 1) ? '/'.$base : $base; + $base = '/' !== substr($base, -1) ? $base.'/' : $base; + + return [ + 'base' => $base, + 'script_attributes' => $config['script_attributes'], + 'link_attributes' => $config['link_attributes'], + ]; + } + + public static function preparePublicDirectory($publicDir) + { + $publicDir = '/' !== substr($publicDir, 0, 1) ? '/'.$publicDir : $publicDir; + $publicDir = rtrim($publicDir, '/'); + + return $publicDir; } } diff --git a/src/Exception/UndefinedConfigNameException.php b/src/Exception/UndefinedConfigNameException.php new file mode 100644 index 0000000..31fea71 --- /dev/null +++ b/src/Exception/UndefinedConfigNameException.php @@ -0,0 +1,7 @@ +