From 4a39cd986247f854bee6845d003e33ea02b4b12a Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 30 Aug 2025 22:06:25 -0400 Subject: [PATCH] refactor: enhance manifest validation and Vue app mounting logic - Improved validation in `WebComponentsExtractor` to log errors for missing standalone apps entries and file keys, ensuring better error handling during manifest processing. - Updated CSS content retrieval in `vite.standalone.config.ts` to include a fallback mechanism for missing Nuxt CSS files, enhancing robustness. - Simplified modal component mounting in `standalone-mount.ts` by utilizing a dedicated function for better readability and maintainability. - Refined `mountVueApp` logic in `vue-mount-app.ts` to differentiate between the main app and clones, optimizing the mounting process for multiple targets. --- .../include/web-components-extractor.php | 10 +++++--- web/components/Wrapper/vue-mount-app.ts | 23 +++++++++++-------- web/components/standalone-mount.ts | 14 ++++------- web/vite.standalone.config.ts | 7 ++++++ 4 files changed, 33 insertions(+), 21 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 61295ff4c..01ae5590e 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 @@ -113,14 +113,18 @@ class WebComponentsExtractor $manifest = $this->getManifestContents($manifestPath); $subfolder = $this->getRelativePath($manifestPath); - // Check if STANDALONE_APPS_ENTRY exists and has a valid 'file' key + // Check if STANDALONE_APPS_ENTRY exists if (!isset($manifest[self::STANDALONE_APPS_ENTRY])) { - continue; // Skip this manifest if entry doesn't exist + error_log("Standalone apps manifest at '{$manifestPath}' is missing the '" . self::STANDALONE_APPS_ENTRY . "' entry key"); + return ''; } $entry = $manifest[self::STANDALONE_APPS_ENTRY]; + + // Check if 'file' key exists if (!isset($entry['file']) || empty($entry['file'])) { - continue; // Skip if 'file' key is missing or empty + error_log("Standalone apps manifest at '{$manifestPath}' has entry '" . self::STANDALONE_APPS_ENTRY . "' but is missing the 'file' field"); + return ''; } // Build the JS file path diff --git a/web/components/Wrapper/vue-mount-app.ts b/web/components/Wrapper/vue-mount-app.ts index 9d6816100..549b5d4b0 100644 --- a/web/components/Wrapper/vue-mount-app.ts +++ b/web/components/Wrapper/vue-mount-app.ts @@ -164,7 +164,7 @@ export function mountVueApp(options: MountOptions): VueApp | null { // Mount to all targets const clones: VueApp[] = []; const containers: HTMLElement[] = []; - targets.forEach((target) => { + targets.forEach((target, index) => { const mountTarget = target as HTMLElement; // Add unraid-reset class to ensure webgui styles don't leak in @@ -186,20 +186,25 @@ export function mountVueApp(options: MountOptions): VueApp | null { // Inject styles into shadow root injectStyles(mountTarget.shadowRoot!); - // Clone and mount the app to this container - const clonedApp = createApp(component, props); - clonedApp.use(i18n); - clonedApp.use(globalPinia); - clonedApp.provide(DefaultApolloClient, client); - clonedApp.mount(container); - clones.push(clonedApp); + // For the first target, use the main app, otherwise create clones + if (index === 0) { + app.mount(container); + } else { + const targetProps = { ...parsePropsFromElement(mountTarget), ...props }; + const clonedApp = createApp(component, targetProps); + clonedApp.use(i18n); + clonedApp.use(globalPinia); + clonedApp.provide(DefaultApolloClient, client); + clonedApp.mount(container); + clones.push(clonedApp); + } } else { // Direct mount without shadow root injectStyles(document); // For multiple targets, we need to create separate app instances // but they'll share the same Pinia store - if (Array.from(targets).indexOf(mountTarget) === 0) { + if (index === 0) { // First target, use the main app app.mount(mountTarget); } else { diff --git a/web/components/standalone-mount.ts b/web/components/standalone-mount.ts index d001775d7..015c8e106 100644 --- a/web/components/standalone-mount.ts +++ b/web/components/standalone-mount.ts @@ -60,15 +60,11 @@ componentMappings.forEach(({ component, selector, appId }) => { }); }); -// Special handling for Modals - also mount to #modals if it exists -if (document.querySelector('#modals')) { - mountVueApp({ - component: Modals, - selector: '#modals', - appId: 'modals-direct', - useShadowRoot: false, - }); -} +// Special handling for Modals - also mount to #modals +autoMountComponent(Modals, '#modals', { + appId: 'modals-direct', + useShadowRoot: false, +}); // Expose functions globally for testing and dynamic mounting declare global { diff --git a/web/vite.standalone.config.ts b/web/vite.standalone.config.ts index 568ddf2f9..69858556d 100644 --- a/web/vite.standalone.config.ts +++ b/web/vite.standalone.config.ts @@ -25,6 +25,13 @@ const getCssContent = () => { } } + // Fallback to source asset + const fallback = path.resolve(__dirname, 'assets/main.css'); + if (fs.existsSync(fallback)) { + console.warn('Nuxt CSS not found; falling back to assets/main.css'); + return fs.readFileSync(fallback, 'utf-8'); + } + console.warn('No CSS file found, using empty string'); return ''; };