From bdb710e746ffad49722662b752b4b30dec2cd3a7 Mon Sep 17 00:00:00 2001 From: Hugues Tavernier Date: Thu, 2 Nov 2023 12:03:18 +0100 Subject: [PATCH] cache implementation for manifest.json --- MiGRATION.md | 5 ++ src/Asset/EntrypointsLookup.php | 12 +-- src/Asset/ViteAssetVersionStrategy.php | 85 +++++++++++++------ src/CacheWarmer/EntrypointsCacheWarmer.php | 33 +++++-- .../PentatrionViteExtension.php | 10 +-- src/Resources/config/services.yaml | 20 +++-- tests/Asset/EntrypointRendererTest.php | 1 + tests/Asset/EntrypointsLookupTest.php | 1 + 8 files changed, 110 insertions(+), 57 deletions(-) create mode 100644 MiGRATION.md diff --git a/MiGRATION.md b/MiGRATION.md new file mode 100644 index 0000000..bc4815a --- /dev/null +++ b/MiGRATION.md @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/Asset/EntrypointsLookup.php b/src/Asset/EntrypointsLookup.php index aba1958..f52bede 100644 --- a/src/Asset/EntrypointsLookup.php +++ b/src/Asset/EntrypointsLookup.php @@ -16,7 +16,7 @@ class EntrypointsLookup public function __construct( string $basePath, - string $configName = '_default', + ?string $configName, // for cache to retrieve content : configName is cache key bool $throwOnMissingEntry = false, CacheItemPoolInterface $cache = null ) { @@ -44,10 +44,10 @@ public function hasFile(): bool private function getFileContent(): array { if ($this->cache) { - $cached = $this->cache->getItem($this->configName); + $entrypointsCacheItem = $this->cache->getItem("{$this->configName}.entrypoints"); - if ($cached->isHit()) { - $this->fileInfos['content'] = $cached->get(); + if ($entrypointsCacheItem->isHit()) { + $this->fileInfos['content'] = $entrypointsCacheItem->get(); } } @@ -63,8 +63,8 @@ private function getFileContent(): array throw new \Exception($this->fileInfos['entrypointsPath'].' : entryPoints, base or viteServer not exists'); } - if (isset($cached)) { - $this->cache->save($cached->set($content)); + if (isset($entrypointsCacheItem)) { + $this->cache->save($entrypointsCacheItem->set($content)); } $this->fileInfos['content'] = $content; diff --git a/src/Asset/ViteAssetVersionStrategy.php b/src/Asset/ViteAssetVersionStrategy.php index 8815268..55445af 100644 --- a/src/Asset/ViteAssetVersionStrategy.php +++ b/src/Asset/ViteAssetVersionStrategy.php @@ -2,6 +2,7 @@ namespace Pentatrion\ViteBundle\Asset; +use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Asset\Exception\AssetNotFoundException; use Symfony\Component\Asset\Exception\RuntimeException; use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface; @@ -11,44 +12,46 @@ class ViteAssetVersionStrategy implements VersionStrategyInterface { private string $publicPath; private array $configs; + private string $configName; private $useAbsoluteUrl; - private $router; + private ?CacheItemPoolInterface $cache; + private ?RouterInterface $router; + private bool $strictMode; - private string $manifestPath; - private string $entrypointsPath; + private ?string $viteMode = null; + private string $basePath; private $manifestData; private $entrypointsData; - private ?array $config = null; - private bool $strictMode; - private ?string $mode = null; public function __construct( string $publicPath, array $configs, string $defaultConfigName, bool $useAbsoluteUrl, + CacheItemPoolInterface $cache = null, RouterInterface $router = null, bool $strictMode = true ) { $this->publicPath = $publicPath; $this->configs = $configs; - $this->strictMode = $strictMode; + $this->configName = $defaultConfigName; $this->useAbsoluteUrl = $useAbsoluteUrl; + $this->cache = $cache; $this->router = $router; + $this->strictMode = $strictMode; - $this->setConfig($defaultConfigName); + $this->setConfig($this->configName); - if (($scheme = parse_url($this->manifestPath, \PHP_URL_SCHEME)) && 0 === strpos($scheme, 'http')) { + if (($scheme = parse_url($this->basePath.'manifest.json', \PHP_URL_SCHEME)) && 0 === strpos($scheme, 'http')) { throw new \Exception('You can\'t use a remote manifest with ViteAssetVersionStrategy'); } } public function setConfig(string $configName): void { - $this->mode = null; - $this->config = $this->configs[$configName]; - $this->manifestPath = $this->publicPath.$this->config['base'].'manifest.json'; - $this->entrypointsPath = $this->publicPath.$this->config['base'].'entrypoints.json'; + $this->viteMode = null; + $this->configName = $configName; + $this->basePath = $this->publicPath.$this->configs[$configName]['base']; } /** @@ -78,39 +81,65 @@ private function completeURL(string $path) private function getassetsPath(string $path): ?string { - if (null === $this->mode) { - if (!is_file($this->entrypointsPath)) { - throw new RuntimeException(sprintf('assets entrypoints file "%s" does not exist. Did you forget configure your `build_dir` in pentatrion_vite.yml?', $this->entrypointsPath)); + if (null === $this->viteMode) { + $manifestPath = $this->basePath.'manifest.json'; + $entrypointsPath = $this->basePath.'entrypoints.json'; + + $this->entrypointsData = null; + $this->manifestData = null; + + $this->viteMode = is_file($manifestPath) ? 'build' : 'dev'; + + if (!is_file($entrypointsPath)) { + throw new RuntimeException(sprintf('assets entrypoints file "%s" does not exist. Did you forget configure your `build_dir` in pentatrion_vite.yml?', $entrypointsPath)); } - if (is_file($this->manifestPath)) { - // when vite server is running manifest file doesn't exists - $this->mode = 'build'; + if ('build' === $this->viteMode && $this->cache) { + $entrypointsCacheItem = $this->cache->getItem("{$this->configName}.entrypoints"); + $manifestCacheItem = $this->cache->getItem("{$this->configName}.manifest"); + + if ($entrypointsCacheItem->isHit()) { + $this->entrypointsData = $entrypointsCacheItem->get(); + } + if ($manifestCacheItem->isHit()) { + $this->manifestData = $manifestCacheItem->get(); + } + } + + if ('build' === $this->viteMode && is_null($this->manifestData)) { try { - $this->manifestData = json_decode(file_get_contents($this->manifestPath), true, 512, \JSON_THROW_ON_ERROR); + $this->manifestData = json_decode(file_get_contents($manifestPath), true, 512, \JSON_THROW_ON_ERROR); } catch (\JsonException $e) { - throw new RuntimeException(sprintf('Error parsing JSON from entrypoints file "%s": ', $this->manifestPath).$e->getMessage(), 0, $e); + throw new RuntimeException(sprintf('Error parsing JSON from entrypoints file "%s": ', $manifestPath).$e->getMessage(), 0, $e); } - } else { - $this->mode = 'dev'; + } + + if (is_null($this->entrypointsData)) { try { - $this->entrypointsData = json_decode(file_get_contents($this->entrypointsPath), true, 512, \JSON_THROW_ON_ERROR); + $this->entrypointsData = json_decode(file_get_contents($entrypointsPath), true, 512, \JSON_THROW_ON_ERROR); } catch (\JsonException $e) { - throw new RuntimeException(sprintf('Error parsing JSON from entrypoints file "%s": ', $this->manifestPath).$e->getMessage(), 0, $e); + throw new RuntimeException(sprintf('Error parsing JSON from entrypoints file "%s": ', $manifestPath).$e->getMessage(), 0, $e); } } + + if (isset($entrypointsCacheItem)) { + $this->cache->save($entrypointsCacheItem->set($this->entrypointsData)); + } + if (isset($manifestCacheItem)) { + $this->cache->save($manifestCacheItem->set($this->manifestData)); + } } - if ('build' === $this->mode) { + if ('build' === $this->viteMode) { if (isset($this->manifestData[$path])) { - return $this->completeURL($this->config['base'].$this->manifestData[$path]['file']); + return $this->completeURL($this->basePath.$this->manifestData[$path]['file']); } } else { return $this->entrypointsData['viteServer'].$this->entrypointsData['base'].$path; } if ($this->strictMode) { - $message = sprintf('assets "%s" not found in manifest file "%s".', $path, $this->manifestPath); + $message = sprintf('assets "%s" not found in manifest file "%s".', $path, $manifestPath); $alternatives = $this->findAlternatives($path, $this->manifestData); if (\count($alternatives) > 0) { $message .= sprintf(' Did you mean one of these? "%s".', implode('", "', $alternatives)); diff --git a/src/CacheWarmer/EntrypointsCacheWarmer.php b/src/CacheWarmer/EntrypointsCacheWarmer.php index ebf24fb..a26d1ff 100644 --- a/src/CacheWarmer/EntrypointsCacheWarmer.php +++ b/src/CacheWarmer/EntrypointsCacheWarmer.php @@ -4,35 +4,50 @@ use Exception; use Pentatrion\ViteBundle\Asset\EntrypointsLookup; -use Pentatrion\ViteBundle\Exception\EntrypointsFileNotFoundException; +use Pentatrion\ViteBundle\Asset\ViteAssetVersionStrategy; use Symfony\Bundle\FrameworkBundle\CacheWarmer\AbstractPhpFileCacheWarmer; use Symfony\Component\Cache\Adapter\ArrayAdapter; class EntrypointsCacheWarmer extends AbstractPhpFileCacheWarmer { - private $basePaths; + private string $publicPath; + private array $configs; - public function __construct(array $basePaths, string $phpCacheFile) + public function __construct( + string $publicPath, + array $configs, + string $phpCacheFile) { - $this->basePaths = $basePaths; + $this->publicPath = $publicPath; + $this->configs = $configs; parent::__construct($phpCacheFile); } protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool { - foreach ($this->basePaths as $basePath) { - $entrypointsPath = $basePath.'entrypoints.json'; + foreach ($this->configs as $configName => $config) { + $entrypointsPath = $this->publicPath.$this->configs[$configName]['base'].'entrypoints.json'; if (!file_exists($entrypointsPath)) { continue; } - $entrypointsLookup = new EntrypointsLookup($entrypointsPath); + $viteAssetVersionStrategy = new ViteAssetVersionStrategy( + $this->publicPath, + $this->configs, + $configName, + false, + $arrayAdapter, + null, + false + ); + // $entrypointsLookup = new EntrypointsLookup($basePath, $configName, false, $arrayAdapter); try { // any method that will call getFileContent and generate // the file in cache. - $entrypointsLookup->getBase(); - } catch (EntrypointsFileNotFoundException $e) { + // $entrypointsLookup->getBase(); + $viteAssetVersionStrategy->applyVersion('/some-dummy-path'); + } catch (\Exception $e) { // ignore exception } } diff --git a/src/DependencyInjection/PentatrionViteExtension.php b/src/DependencyInjection/PentatrionViteExtension.php index 9383e5b..d92554a 100644 --- a/src/DependencyInjection/PentatrionViteExtension.php +++ b/src/DependencyInjection/PentatrionViteExtension.php @@ -47,9 +47,12 @@ public function load(array $bundleConfigs, ContainerBuilder $container): void $lookupFactories = []; $tagRendererFactories = []; $configs = []; - $cacheKeys = []; foreach ($bundleConfig['configs'] as $configName => $config) { + if (!preg_match('/^[a-zA-Z_]+$/', $configName)) { + throw new \Exception('Invalid config name, you should use only a-z A-Z and _ characters.'); + } + $configs[$configName] = $configPrepared = self::prepareConfig($config); $lookupFactories[$configName] = $this->entrypointsLookupFactory( $container, @@ -58,7 +61,6 @@ public function load(array $bundleConfigs, ContainerBuilder $container): void $bundleConfig['cache'] ); $tagRendererFactories[$configName] = $this->tagRendererFactory($container, $configName, $configPrepared); - $cacheKeys[] = $this->resolveBasePath($container, $configPrepared); } } else { $defaultConfigName = '_default'; @@ -75,7 +77,6 @@ public function load(array $bundleConfigs, ContainerBuilder $container): void $tagRendererFactories = [ '_default' => $this->tagRendererFactory($container, $defaultConfigName, $configPrepared), ]; - $cacheKeys = [$this->resolveBasePath($container, $configPrepared)]; } if ('link-header' === $bundleConfig['preload']) { @@ -96,9 +97,6 @@ public function load(array $bundleConfigs, ContainerBuilder $container): void $container->getDefinition('pentatrion_vite.tag_renderer_collection') ->addArgument(ServiceLocatorTagPass::register($container, $tagRendererFactories)) ->addArgument($defaultConfigName); - - $container->getDefinition('pentatrion_vite.cache_warmer') - ->replaceArgument(0, $cacheKeys); } private function entrypointsLookupFactory( diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 478e4d9..3d1bb16 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -68,29 +68,33 @@ services: - "%pentatrion_vite.configs%" - "%pentatrion_vite.default_config%" - "%pentatrion_vite.absolute_url%" + - "@?pentatrion_vite.cache" - "@?router" - true + pentatrion_vite.preload_assets_event_listener: class: Pentatrion\ViteBundle\EventListener\PreloadAssetsEventListener tags: ["kernel.event_subscriber"] arguments: - "@pentatrion_vite.entrypoint_renderer" - pentatrion_vite.cache_warmer: - class: Pentatrion\ViteBundle\CacheWarmer\EntrypointsCacheWarmer - tags: ["kernel.cache_warmer"] - arguments: - - [] - - '%kernel.cache_dir%/pentatrion_vite.cache.php' pentatrion_vite.cache: class: Symfony\Component\Cache\Adapter\PhpArrayAdapter factory: [Symfony\Component\Cache\Adapter\PhpArrayAdapter, create] arguments: - '%kernel.cache_dir%/pentatrion_vite.cache.php' - - '@cache.pentatrion_vite' + - '@cache.pentatrion_vite_fallback' + + pentatrion_vite.cache_warmer: + class: Pentatrion\ViteBundle\CacheWarmer\EntrypointsCacheWarmer + tags: ["kernel.cache_warmer"] + arguments: + - "%kernel.project_dir%%pentatrion_vite.public_directory%" + - "%pentatrion_vite.configs%" + - '%kernel.cache_dir%/pentatrion_vite.cache.php' - cache.pentatrion_vite: + cache.pentatrion_vite_fallback: tags: ["cache.pool"] parent: cache.system \ No newline at end of file diff --git a/tests/Asset/EntrypointRendererTest.php b/tests/Asset/EntrypointRendererTest.php index 7ccbe0b..9d419b2 100644 --- a/tests/Asset/EntrypointRendererTest.php +++ b/tests/Asset/EntrypointRendererTest.php @@ -48,6 +48,7 @@ private function getEntrypointsLookup($prefix) { return new EntrypointsLookup( __DIR__.'/../fixtures/entrypoints/'.$prefix.'/', + '_default', true ); } diff --git a/tests/Asset/EntrypointsLookupTest.php b/tests/Asset/EntrypointsLookupTest.php index 451d744..0693298 100644 --- a/tests/Asset/EntrypointsLookupTest.php +++ b/tests/Asset/EntrypointsLookupTest.php @@ -12,6 +12,7 @@ private function getEntrypointsLookup($prefix) { return new EntrypointsLookup( __DIR__.'/../fixtures/entrypoints/'.$prefix.'/', + '_default', true ); }