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.
This commit is contained in:
Eli Bosley
2025-08-30 22:06:25 -04:00
parent f7ad582436
commit 4a39cd9862
4 changed files with 33 additions and 21 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 '';
};