diff --git a/.gitignore b/.gitignore index 13c01ef..4e9c2f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /.local /.idea /vendor -/.php-cs-fixer.cache \ No newline at end of file +/.php-cs-fixer.cache +/.phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 5afdd76..0000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testRenderTag":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #0":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #5":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateScript with data set #0":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateScript with data set #1":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkStylesheet with data set #0":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkStylesheet with data set #1":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testSpecialTag":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkPreload with data set #0":3,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkPreload with data set #1":3},"times":{"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testRenderTagBasic":2.477,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testRenderTagBasic1":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testRenderTag":0.004,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes":0.004,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #0":0.003,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #1":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #2":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #3":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #4":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithCustomAttributes with data set #5":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateTagWithGlobalAttributes with data set #0":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateScript with data set #0":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateScript with data set #1":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateScript with data set #2":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkStylesheet with data set #0":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkStylesheet with data set #1":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testSpecialTag":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkPreload with data set #0":0,"Pentatrion\\ViteBundle\\Tests\\Asset\\TagRendererTest::testGenerateLinkPreload with data set #1":0}} \ No newline at end of file diff --git a/src/Asset/EntrypointRenderer.php b/src/Asset/EntrypointRenderer.php index 5f1b244..38b6dfd 100644 --- a/src/Asset/EntrypointRenderer.php +++ b/src/Asset/EntrypointRenderer.php @@ -121,13 +121,13 @@ public function renderScripts( $tags[] = $tagRenderer->createDynamicFallbackScript(); $tags[] = $tagRenderer->createSafariNoModuleScript(); - foreach ($entrypointsLookup->getJSFiles('polyfills-legacy') as $fileWithHash) { + foreach ($entrypointsLookup->getJSFiles('polyfills-legacy') as $filePath) { // normally only one js file $tags[] = $tagRenderer->createScriptTag( [ 'nomodule' => true, 'crossorigin' => true, - 'src' => $this->completeURL($fileWithHash['path'], $useAbsoluteUrl), + 'src' => $this->completeURL($filePath, $useAbsoluteUrl), 'id' => 'vite-legacy-polyfill', ] ); @@ -136,13 +136,13 @@ public function renderScripts( } /* normal js scripts */ - foreach ($entrypointsLookup->getJSFiles($entryName) as $fileWithHash) { + foreach ($entrypointsLookup->getJSFiles($entryName) as $filePath) { $tags[] = $tagRenderer->createScriptTag( array_merge( [ 'type' => 'module', - 'src' => $this->completeURL($fileWithHash['path'], $useAbsoluteUrl), - 'integrity' => $fileWithHash['hash'], + 'src' => $this->completeURL($filePath, $useAbsoluteUrl), + 'integrity' => $entrypointsLookup->getFileHash($filePath), ], $options['attr'] ?? [] ) @@ -157,11 +157,11 @@ public function renderScripts( $tags[] = $tagRenderer->createScriptTag( [ 'nomodule' => true, - 'data-src' => $this->completeURL($file['path'], $useAbsoluteUrl), + 'data-src' => $this->completeURL($file, $useAbsoluteUrl), 'id' => $id, 'crossorigin' => true, 'class' => 'vite-legacy-entry', - 'integrity' => $file['hash'], + 'integrity' => $entrypointsLookup->getFileHash($file), ], InlineContent::getSystemJSInlineCode($id) ); @@ -188,33 +188,33 @@ public function renderLinks( $tags = []; - foreach ($entrypointsLookup->getCSSFiles($entryName) as $fileWithHash) { + foreach ($entrypointsLookup->getCSSFiles($entryName) as $filePath) { $tags[] = $tagRenderer->createLinkStylesheetTag( - $this->completeURL($fileWithHash['path'], $useAbsoluteUrl), - array_merge(['integrity' => $fileWithHash['hash']], $options['attr'] ?? []) + $this->completeURL($filePath, $useAbsoluteUrl), + array_merge(['integrity' => $entrypointsLookup->getFileHash($filePath)], $options['attr'] ?? []) ); } if ($isBuild) { - foreach ($entrypointsLookup->getJavascriptDependencies($entryName) as $fileWithHash) { - if (false === \in_array($fileWithHash['path'], $this->returnedPreloadedScripts, true)) { + foreach ($entrypointsLookup->getJavascriptDependencies($entryName) as $filePath) { + if (false === \in_array($filePath, $this->returnedPreloadedScripts, true)) { $tags[] = $tagRenderer->createModulePreloadLinkTag( - $this->completeURL($fileWithHash['path'], $useAbsoluteUrl), - ['integrity' => $fileWithHash['hash']] + $this->completeURL($filePath, $useAbsoluteUrl), + ['integrity' => $entrypointsLookup->getFileHash($filePath)] ); - $this->returnedPreloadedScripts[] = $fileWithHash['path']; + $this->returnedPreloadedScripts[] = $filePath; } } } if ($isBuild && isset($options['preloadDynamicImports']) && true === $options['preloadDynamicImports']) { - foreach ($entrypointsLookup->getJavascriptDynamicDependencies($entryName) as $fileWithHash) { - if (false === \in_array($fileWithHash['path'], $this->returnedPreloadedScripts, true)) { + foreach ($entrypointsLookup->getJavascriptDynamicDependencies($entryName) as $filePath) { + if (false === \in_array($filePath, $this->returnedPreloadedScripts, true)) { $tags[] = $tagRenderer->createModulePreloadLinkTag( - $this->completeURL($fileWithHash['path'], $useAbsoluteUrl), - ['integrity' => $fileWithHash['hash']] + $this->completeURL($filePath, $useAbsoluteUrl), + ['integrity' => $entrypointsLookup->getFileHash($filePath)] ); - $this->returnedPreloadedScripts[] = $fileWithHash['path']; + $this->returnedPreloadedScripts[] = $filePath; } } } diff --git a/src/Asset/EntrypointsLookup.php b/src/Asset/EntrypointsLookup.php index 0db6964..5ee3472 100644 --- a/src/Asset/EntrypointsLookup.php +++ b/src/Asset/EntrypointsLookup.php @@ -2,6 +2,8 @@ namespace Pentatrion\ViteBundle\Asset; +use Pentatrion\ViteBundle\Exception\EntrypointNotFoundException; + class EntrypointsLookup { private bool $throwOnMissingEntry; @@ -35,8 +37,8 @@ private function getFileContent(): array throw new \Exception('entrypoints.json not found at '.$this->fileInfos['entrypointsPath']); } $content = json_decode(file_get_contents($this->fileInfos['entrypointsPath']), true); - if (!isset($content['isBuild'], $content['entryPoints'], $content['viteServer'])) { - throw new \Exception($this->fileInfos['entrypointsPath'].' : isBuild, entryPoints or viteServer not exists'); + if (!isset($content['entryPoints'], $content['viteServer'])) { + throw new \Exception($this->fileInfos['entrypointsPath'].' : entryPoints or viteServer not exists'); } $this->fileInfos['content'] = $content; @@ -45,6 +47,17 @@ private function getFileContent(): array return $this->fileInfos['content']; } + public function getFileHash(string $filePath): ?string + { + $infos = $this->getFileContent(); + + if (is_null($infos['metadatas']) || !array_key_exists($filePath, $infos['metadatas'])) { + return null; + } + + return $infos['metadatas'][$filePath]['hash']; + } + public function isLegacyPluginEnabled(): bool { $infos = $this->getFileContent(); @@ -54,7 +67,7 @@ public function isLegacyPluginEnabled(): bool public function isBuild(): bool { - return $this->getFileContent()['isBuild']; + return false === $this->getFileContent()['viteServer']; } public function getViteServer() @@ -119,7 +132,7 @@ private function throwIfEntrypointIsMissing(string $entryName): void if (!array_key_exists($entryName, $this->getFileContent()['entryPoints'])) { $keys = array_keys($this->getFileContent()['entryPoints']); $entryPointKeys = join(', ', array_map(function ($key) { return "'$key'"; }, $keys)); - throw new \Exception("Entry '$entryName' not present in the entrypoints file. Defined entrypoints are $entryPointKeys"); + throw new EntrypointNotFoundException(sprintf("Entry '%s' not present in the entrypoints file. Defined entrypoints are %s", $entryName, $entryPointKeys)); } } } diff --git a/src/Exception/EntrypointNotFoundException.php b/src/Exception/EntrypointNotFoundException.php new file mode 100644 index 0000000..a0b0792 --- /dev/null +++ b/src/Exception/EntrypointNotFoundException.php @@ -0,0 +1,7 @@ + '/'.$prefix.'/'], + true + ); + } + + public function testExtra() + { + $entrypointsLookupFileNotExists = $this->getEntrypointsLookup('not-found'); + $entrypointsLookupBasicBuild = $this->getEntrypointsLookup('basic-build'); + + $this->assertEquals( + false, + $entrypointsLookupFileNotExists->hasFile() + ); + + $this->assertEquals( + true, + $entrypointsLookupBasicBuild->hasFile() + ); + } + + public function testExceptionOnMissingEntry() + { + $entrypointsLookupBasicBuild = $this->getEntrypointsLookup('basic-build'); + $this->expectException(EntrypointNotFoundException::class); + + $entrypointsLookupBasicBuild->getJSFiles('unknown-entrypoint'); + } + + public function testViteServer() + { + $entrypointsLookupBasicDev = $this->getEntrypointsLookup('basic-dev'); + $entrypointsLookupBasicBuild = $this->getEntrypointsLookup('basic-build'); + + $this->assertEquals( + ['origin' => 'http://127.0.0.1:5173', 'base' => '/build/'], + $entrypointsLookupBasicDev->getViteServer() + ); + + $this->assertEquals( + false, + $entrypointsLookupBasicBuild->getViteServer() + ); + + $this->assertEquals( + false, + $entrypointsLookupBasicDev->isBuild() + ); + + $this->assertEquals( + true, + $entrypointsLookupBasicBuild->isBuild() + ); + } + + public function devfilesProvider() + { + return [ + ['app', [ + 'assets' => [], + 'css' => [], + 'dynamic' => [], + 'js' => ['http://127.0.0.1:5173/build/assets/app.js'], + 'preload' => [], + ]], + ['theme', [ + 'assets' => [], + 'css' => ['http://127.0.0.1:5173/build/assets/theme.scss'], + 'dynamic' => [], + 'js' => [], + 'preload' => [], + ]], + ]; + } + + /** + * @dataProvider devfilesProvider + */ + public function testGetDevFiles($entryName, $files) + { + $entrypointsLookupBasicDev = $this->getEntrypointsLookup('basic-dev'); + + $this->assertEquals($files['css'], $entrypointsLookupBasicDev->getCSSFiles($entryName)); + $this->assertEquals($files['dynamic'], $entrypointsLookupBasicDev->getJavascriptDynamicDependencies($entryName)); + $this->assertEquals($files['js'], $entrypointsLookupBasicDev->getJSFiles($entryName)); + $this->assertEquals($files['preload'], $entrypointsLookupBasicDev->getJavascriptDependencies($entryName)); + } + + public function buildfilesProvider() + { + return [ + ['app', [ + 'assets' => [], + 'css' => [], + 'dynamic' => [], + 'js' => ['/build/assets/pageImports-53eb9fd1.js'], + 'preload' => [], + ]], + ['theme', [ + 'assets' => [], + 'css' => ['/build/assets/theme-62617963.css'], + 'dynamic' => [], + 'js' => [], + 'preload' => [], + ]], + ['with-dep', [ + 'assets' => [], + 'css' => ['/build/assets/main-76fa9059.css'], + 'dynamic' => [], + 'js' => ['/build/assets/main-e664f4b5.js'], + 'preload' => ['/build/assets/vue-2d05229a.js', '/build/assets/react-2d05228c.js'], + ]], + ['with-async', [ + 'assets' => [], + 'css' => ['/build/assets/main-76fa9059.css'], + 'dynamic' => ['/build/assets/async-script-12324565.js'], + 'js' => ['/build/assets/main-e664f4b5.js'], + 'preload' => ['/build/assets/vue-2d05229a.js', '/build/assets/react-2d05228c.js'], + ]], + ]; + } + + /** + * @dataProvider buildfilesProvider + */ + public function testGetBuildFiles($entryName, $files) + { + $entrypointsLookupBasicBuild = $this->getEntrypointsLookup('basic-build'); + + $this->assertEquals($files['css'], $entrypointsLookupBasicBuild->getCSSFiles($entryName)); + $this->assertEquals($files['dynamic'], $entrypointsLookupBasicBuild->getJavascriptDynamicDependencies($entryName)); + $this->assertEquals($files['js'], $entrypointsLookupBasicBuild->getJSFiles($entryName)); + $this->assertEquals($files['preload'], $entrypointsLookupBasicBuild->getJavascriptDependencies($entryName)); + } + + public function buildLegacyProvider() + { + return [ + ['app', [ + 'assets' => [], + 'css' => [], + 'dynamic' => [], + 'js' => ['/build/assets/app-23802617.js'], + 'preload' => [], + 'legacy_js' => '/build/assets/app-legacy-59951366.js', + ]], + ['theme', [ + 'assets' => [], + 'css' => ['/build/assets/theme-5cd46aed.css'], + 'dynamic' => [], + 'js' => [], + 'preload' => [], + 'legacy_js' => '/build/assets/theme-legacy-de9eb869.js', + ]], + ]; + } + + /** + * @dataProvider buildLegacyProvider + */ + public function testGetBuildLegacyFiles($entryName, $files) + { + $entrypointsLookupLegacyBuild = $this->getEntrypointsLookup('legacy-build'); + + $this->assertEquals($files['css'], $entrypointsLookupLegacyBuild->getCSSFiles($entryName)); + $this->assertEquals($files['dynamic'], $entrypointsLookupLegacyBuild->getJavascriptDynamicDependencies($entryName)); + $this->assertEquals($files['js'], $entrypointsLookupLegacyBuild->getJSFiles($entryName)); + $this->assertEquals($files['preload'], $entrypointsLookupLegacyBuild->getJavascriptDependencies($entryName)); + $this->assertEquals($files['legacy_js'], $entrypointsLookupLegacyBuild->getLegacyJSFile($entryName)); + } + + public function testHashOfFiles() + { + $entrypointsLookupBasicBuild = $this->getEntrypointsLookup('basic-build'); + $this->assertEquals( + null, + $entrypointsLookupBasicBuild->getFileHash('/build/assets/pageImports-53eb9fd1.js') + ); + + $entrypointsLookupMetadataBuild = $this->getEntrypointsLookup('metadata-build'); + $this->assertEquals( + 'sha256-qABtt8+MbhDq8dts7DSJOnBqCO1QbV2S6zg24ylLkKY=', + $entrypointsLookupMetadataBuild->getFileHash('http://cdn.with-cdn.symfony-vite-dev.localhost/assets/pageVue-bda8ac3b.js') + ); + + $entrypointsLookupMetadataBuild = $this->getEntrypointsLookup('metadata-build'); + $this->assertEquals( + null, + $entrypointsLookupMetadataBuild->getFileHash('/build-file-without-metadata.js') + ); + } +} diff --git a/tests/Asset/TagRendererTest.php b/tests/Asset/TagRendererTest.php index d38fe47..55eed62 100644 --- a/tests/Asset/TagRendererTest.php +++ b/tests/Asset/TagRendererTest.php @@ -123,7 +123,7 @@ public function linkPreloadProvider() '/dependency.js', [], '', - 'global link/script attribute are not added', + 'global link/script attributes are not added', ], ]; } diff --git a/tests/fixtures/entrypoints-app.json b/tests/fixtures/entrypoints-app.json deleted file mode 100644 index a202822..0000000 --- a/tests/fixtures/entrypoints-app.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "entryPoints": { - "app": { - "assets": [], - "css": [], - "dynamic": [], - "js": [ - { - "path": "/build/assets/app-dc399f15.js", - "hash": null - } - ], - "legacy": false, - "preload": [] - } - }, - "isBuild": true, - "legacy": false, - "viteServer": false -} \ No newline at end of file diff --git a/tests/fixtures/entrypoints/basic-build/entrypoints.json b/tests/fixtures/entrypoints/basic-build/entrypoints.json new file mode 100644 index 0000000..799a1ef --- /dev/null +++ b/tests/fixtures/entrypoints/basic-build/entrypoints.json @@ -0,0 +1,59 @@ +{ + "entryPoints": { + "app": { + "assets": [], + "css": [], + "dynamic": [], + "js": [ + "/build/assets/pageImports-53eb9fd1.js" + ], + "legacy": false, + "preload": [] + }, + "theme": { + "assets": [], + "css": [ + "/build/assets/theme-62617963.css" + ], + "dynamic": [], + "js": [], + "legacy": false, + "preload": [] + }, + "with-dep": { + "assets": [], + "css": [ + "/build/assets/main-76fa9059.css" + ], + "dynamic": [], + "js": [ + "/build/assets/main-e664f4b5.js" + ], + "legacy": false, + "preload": [ + "/build/assets/vue-2d05229a.js", + "/build/assets/react-2d05228c.js" + ] + }, + "with-async": { + "assets": [], + "css": [ + "/build/assets/main-76fa9059.css" + ], + "dynamic": [ + "/build/assets/async-script-12324565.js" + ], + "js": [ + "/build/assets/main-e664f4b5.js" + ], + "legacy": false, + "preload": [ + "/build/assets/vue-2d05229a.js", + "/build/assets/react-2d05228c.js" + ] + } + }, + "legacy": false, + "metadatas": {}, + "viteServer": false +} \ No newline at end of file diff --git a/tests/fixtures/entrypoints/basic-dev/entrypoints.json b/tests/fixtures/entrypoints/basic-dev/entrypoints.json new file mode 100644 index 0000000..9e27575 --- /dev/null +++ b/tests/fixtures/entrypoints/basic-dev/entrypoints.json @@ -0,0 +1,20 @@ +{ + "entryPoints": { + "app": { + "js": [ + "http://127.0.0.1:5173/build/assets/app.js" + ] + }, + "theme": { + "css": [ + "http://127.0.0.1:5173/build/assets/theme.scss" + ] + } + }, + "legacy": false, + "metadatas": {}, + "viteServer": { + "origin": "http://127.0.0.1:5173", + "base": "/build/" + } +} \ No newline at end of file diff --git a/tests/fixtures/entrypoints/legacy-build/entrypoints.json b/tests/fixtures/entrypoints/legacy-build/entrypoints.json new file mode 100644 index 0000000..a9dcdb9 --- /dev/null +++ b/tests/fixtures/entrypoints/legacy-build/entrypoints.json @@ -0,0 +1,59 @@ +{ + "entryPoints": { + "app-legacy": { + "assets": [], + "css": [], + "dynamic": [], + "js": [ + "/build/assets/app-legacy-59951366.js" + ], + "legacy": false, + "preload": [] + }, + "app": { + "assets": [], + "css": [], + "dynamic": [], + "js": [ + "/build/assets/app-23802617.js" + ], + "legacy": "app-legacy", + "preload": [] + }, + "theme-legacy": { + "assets": [ + "/build/assets/topography-303b8303.svg" + ], + "css": [], + "dynamic": [], + "js": [ + "/build/assets/theme-legacy-de9eb869.js" + ], + "legacy": false, + "preload": [] + }, + "theme": { + "assets": [], + "css": [ + "/build/assets/theme-5cd46aed.css" + ], + "dynamic": [], + "js": [], + "legacy": "theme-legacy", + "preload": [] + }, + "polyfills-legacy": { + "assets": [], + "css": [], + "dynamic": [], + "js": [ + "/build/assets/polyfills-legacy-40963d34.js" + ], + "legacy": false, + "preload": [] + } + }, + "legacy": true, + "metadatas": {}, + "viteServer": false +} \ No newline at end of file diff --git a/tests/fixtures/entrypoints/metadata-build/entrypoints.json b/tests/fixtures/entrypoints/metadata-build/entrypoints.json new file mode 100644 index 0000000..d3aacf6 --- /dev/null +++ b/tests/fixtures/entrypoints/metadata-build/entrypoints.json @@ -0,0 +1,21 @@ +{ + "entryPoints": { + "pageVue": { + "assets": [], + "css": [], + "dynamic": [], + "js": [ + "http://cdn.with-cdn.symfony-vite-dev.localhost/assets/pageVue-bda8ac3b.js" + ], + "legacy": false, + "preload": [] + } + }, + "legacy": false, + "metadatas": { + "http://cdn.with-cdn.symfony-vite-dev.localhost/assets/pageVue-bda8ac3b.js": { + "hash": "sha256-qABtt8+MbhDq8dts7DSJOnBqCO1QbV2S6zg24ylLkKY=" + } + }, + "viteServer": false +} \ No newline at end of file