From db0fd0e3af483e9aa09e6bc5492c7d94d2090234 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 30 Sep 2025 10:54:58 -0400 Subject: [PATCH 1/2] fix: add cache busting to web component extractor --- .../include/web-components-extractor.php | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php index 8d214bfb94..7f2eda5c53 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php @@ -35,6 +35,16 @@ private function sanitizeForId(string $input): string return preg_replace('/[^a-zA-Z0-9-]/', '-', $input); } + private function appendVersionQuery(string $path, string $version): string + { + if ($version === '') { + return $path; + } + + $separator = strpos($path, '?') !== false ? '&' : '?'; + return $path . $separator . 'v=' . rawurlencode($version); + } + public function getManifestContents(string $manifestPath): array { $contents = @file_get_contents($manifestPath); @@ -62,7 +72,18 @@ private function processManifestFiles(): string $subfolder = $this->getRelativePath($manifestPath); // Process each entry in the manifest + $version = ''; + if (isset($manifest['ts'])) { + $versionValue = $manifest['ts']; + if (is_string($versionValue) || is_numeric($versionValue)) { + $version = (string) $versionValue; + } + } + foreach ($manifest as $key => $entry) { + if ($key === 'ts') { + continue; + } // Skip if not an array with a 'file' key if (!is_array($entry) || !isset($entry['file']) || empty($entry['file'])) { continue; @@ -70,7 +91,7 @@ private function processManifestFiles(): string // Build the file path $filePath = ($subfolder ? $subfolder . '/' : '') . $entry['file']; - $fullPath = $this->getAssetPath($filePath); + $fullPath = $this->appendVersionQuery($this->getAssetPath($filePath), $version); // Determine file type and generate appropriate tag $extension = pathinfo($entry['file'], PATHINFO_EXTENSION); @@ -91,7 +112,7 @@ private function processManifestFiles(): string foreach ($entry['css'] as $cssFile) { if (!is_string($cssFile) || $cssFile === '') continue; $cssPath = ($subfolder ? $subfolder . '/' : '') . $cssFile; - $cssFull = $this->getAssetPath($cssPath); + $cssFull = $this->appendVersionQuery($this->getAssetPath($cssPath), $version); $cssId = htmlspecialchars( 'unraid-' . $sanitizedSubfolder . $sanitizedKey . '-css-' . $this->sanitizeForId(basename($cssFile)), ENT_QUOTES, From 5ad58f1b31ec5a8e6a49f33575fd7da77d6e3ead Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 30 Sep 2025 11:48:12 -0400 Subject: [PATCH 2/2] refactor: remove version query appending from WebComponentsExtractor and update tests for standalone assets - Removed the `appendVersionQuery` method from `WebComponentsExtractor` to simplify asset path generation. - Updated test cases to reflect changes in asset naming, ensuring correct handling of standalone JS and CSS files with hashed names. - Adjusted Vite configuration to generate hashed filenames for standalone assets, improving cache busting. --- .../include/web-components-extractor.php | 22 ++---------- plugin/tests/test-extractor.php | 35 +++++++++++-------- web/vite.config.ts | 5 ++- 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php index 7f2eda5c53..aaab7375ac 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php @@ -35,16 +35,6 @@ private function sanitizeForId(string $input): string return preg_replace('/[^a-zA-Z0-9-]/', '-', $input); } - private function appendVersionQuery(string $path, string $version): string - { - if ($version === '') { - return $path; - } - - $separator = strpos($path, '?') !== false ? '&' : '?'; - return $path . $separator . 'v=' . rawurlencode($version); - } - public function getManifestContents(string $manifestPath): array { $contents = @file_get_contents($manifestPath); @@ -72,14 +62,6 @@ private function processManifestFiles(): string $subfolder = $this->getRelativePath($manifestPath); // Process each entry in the manifest - $version = ''; - if (isset($manifest['ts'])) { - $versionValue = $manifest['ts']; - if (is_string($versionValue) || is_numeric($versionValue)) { - $version = (string) $versionValue; - } - } - foreach ($manifest as $key => $entry) { if ($key === 'ts') { continue; @@ -91,7 +73,7 @@ private function processManifestFiles(): string // Build the file path $filePath = ($subfolder ? $subfolder . '/' : '') . $entry['file']; - $fullPath = $this->appendVersionQuery($this->getAssetPath($filePath), $version); + $fullPath = $this->getAssetPath($filePath); // Determine file type and generate appropriate tag $extension = pathinfo($entry['file'], PATHINFO_EXTENSION); @@ -112,7 +94,7 @@ private function processManifestFiles(): string foreach ($entry['css'] as $cssFile) { if (!is_string($cssFile) || $cssFile === '') continue; $cssPath = ($subfolder ? $subfolder . '/' : '') . $cssFile; - $cssFull = $this->appendVersionQuery($this->getAssetPath($cssPath), $version); + $cssFull = $this->getAssetPath($cssPath); $cssId = htmlspecialchars( 'unraid-' . $sanitizedSubfolder . $sanitizedKey . '-css-' . $this->sanitizeForId(basename($cssFile)), ENT_QUOTES, diff --git a/plugin/tests/test-extractor.php b/plugin/tests/test-extractor.php index 43286bdf0e..8662b2a819 100755 --- a/plugin/tests/test-extractor.php +++ b/plugin/tests/test-extractor.php @@ -14,6 +14,8 @@ class ExtractorTest { private $passed = 0; private $failed = 0; private $verbose = false; + private $standaloneJsFile = 'standalone-apps-AbCdEf12.js'; + private $standaloneCssFile = 'standalone-apps-ZyXwVuTs.css'; // Color codes for terminal output const RED = "\033[0;31m"; @@ -46,13 +48,13 @@ private function setup() { // Create test manifest files file_put_contents($this->componentDir . '/standalone-apps/standalone.manifest.json', json_encode([ - 'standalone-apps-RlN0czLV.css' => [ - 'file' => 'standalone-apps-RlN0czLV.css', - 'src' => 'standalone-apps-RlN0czLV.css' + $this->standaloneCssFile => [ + 'file' => $this->standaloneCssFile, + 'src' => $this->standaloneCssFile ], - 'standalone-apps.js' => [ - 'file' => 'standalone-apps.js', - 'src' => 'standalone-apps.js', + $this->standaloneJsFile => [ + 'file' => $this->standaloneJsFile, + 'src' => $this->standaloneJsFile, 'css' => ['app-styles.css', 'theme.css'] ], 'ts' => 1234567890 @@ -144,8 +146,8 @@ private function runTests() { echo "Test: Script Tag Generation\n"; echo "----------------------------\n"; $this->test( - "Generates script tag for standalone-apps.js", - strpos($output, 'script id="unraid-standalone-apps-standalone-apps-js"') !== false + "Generates script tag for hashed standalone JS", + strpos($output, 'script id="unraid-standalone-apps-' . $this->sanitizeForExpectedId($this->standaloneJsFile) . '"') !== false ); $this->test( "Generates script tag for components.mjs", @@ -160,8 +162,8 @@ private function runTests() { echo "\nTest: CSS Link Generation\n"; echo "--------------------------\n"; $this->test( - "Generates link tag for standalone CSS", - strpos($output, 'link id="unraid-standalone-apps-standalone-apps-RlN0czLV-css"') !== false + "Generates link tag for hashed standalone CSS", + strpos($output, 'link id="unraid-standalone-apps-' . $this->sanitizeForExpectedId($this->standaloneCssFile) . '"') !== false ); $this->test( "Generates link tag for UI styles", @@ -209,7 +211,7 @@ private function runTests() { echo "------------------------\n"; $this->test( "Correctly constructs standalone-apps path", - strpos($output, '/plugins/dynamix.my.servers/unraid-components/standalone-apps/standalone-apps.js') !== false + strpos($output, '/plugins/dynamix.my.servers/unraid-components/standalone-apps/' . $this->standaloneJsFile) !== false ); $this->test( "Correctly constructs ui-components path", @@ -274,11 +276,11 @@ private function runTests() { echo "--------------------------------\n"; $this->test( "Loads CSS from JS entry css array (app-styles.css)", - strpos($output, 'id="unraid-standalone-apps-standalone-apps-js-css-app-styles-css"') !== false + strpos($output, 'id="unraid-standalone-apps-' . $this->sanitizeForExpectedId($this->standaloneJsFile) . '-css-app-styles-css"') !== false ); $this->test( "Loads CSS from JS entry css array (theme.css)", - strpos($output, 'id="unraid-standalone-apps-standalone-apps-js-css-theme-css"') !== false + strpos($output, 'id="unraid-standalone-apps-' . $this->sanitizeForExpectedId($this->standaloneJsFile) . '-css-theme-css"') !== false ); $this->test( "CSS from manifest has correct href path (app-styles.css)", @@ -344,6 +346,11 @@ private function removeDirectory($dir) { } rmdir($dir); } + + private function sanitizeForExpectedId(string $input): string + { + return preg_replace('/[^a-zA-Z0-9-]/', '-', $input); + } private function reportResults() { echo "\n"; @@ -366,4 +373,4 @@ private function reportResults() { // Run tests $test = new ExtractorTest(); -exit($test->run()); \ No newline at end of file +exit($test->run()); diff --git a/web/vite.config.ts b/web/vite.config.ts index 452afef640..945e6ae123 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -128,12 +128,11 @@ export default defineConfig({ rollupOptions: { output: { format: 'es', - entryFileNames: 'standalone-apps.js', + entryFileNames: 'standalone-apps-[hash].js', chunkFileNames: '[name]-[hash].js', assetFileNames: (assetInfo) => { - // Keep CSS files with predictable names if (assetInfo.name?.endsWith('.css')) { - return 'standalone-apps.css'; + return 'standalone-apps-[hash][extname]'; } return '[name]-[hash][extname]'; },