UNPKG

@zenithcore/core

Version:

Core functionality for ZenithKernel framework

295 lines (253 loc) 8.18 kB
/** * Vite Plugin for Hydra Islands * * Handles discovery, transformation, and registration of island components */ import { Plugin } from 'vite'; import { glob } from 'glob'; import * as fs from 'fs/promises'; import * as path from 'path'; interface HydraPluginOptions { islandsDir?: string; islandPattern?: string; enableZKFiles?: boolean; generateRegistry?: boolean; outDir?: string; hmr?: boolean; } export function hydraPlugin(options: HydraPluginOptions = {}): Plugin { const defaultOptions = { islandsDir: 'src/islands', islandPattern: '**/*Island.{ts,tsx,zk}', enableZKFiles: true, generateRegistry: true, outDir: 'dist', hmr: true }; const resolvedOptions = { ...defaultOptions, ...options }; let projectRoot: string; let islands: Map<string, any> = new Map(); return { name: 'vite-plugin-hydra-islands', configResolved(config) { projectRoot = config.root; }, async buildStart() { try { // Discover islands const islandsPath = path.resolve(projectRoot, resolvedOptions.islandsDir); const pattern = path.join(islandsPath, resolvedOptions.islandPattern); const files = await glob(pattern); // Process each island file for (const file of files) { const content = await fs.readFile(file, 'utf-8'); const isZKFile = file.endsWith('.zk'); if (isZKFile && resolvedOptions.enableZKFiles) { const parsed = this.parseZKFile(content); islands.set(file, { type: 'zk', content: parsed, metadata: this.extractMetadata(parsed.script) }); } else { islands.set(file, { type: 'ts', content, metadata: this.extractMetadata(content) }); } } // Generate registry if enabled if (resolvedOptions.generateRegistry) { await this.generateRegistry(); } } catch (error) { console.error('Failed to discover islands:', error); } }, configureServer(server) { // Add API endpoints for island discovery server.middlewares.use('/api/hydra/islands', (req, res) => { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ islands: Array.from(islands.keys()) })); }); server.middlewares.use('/api/hydra/islands/:name', (req, res) => { const islandPath = req.url?.split('/').pop(); const island = Array.from(islands.entries()) .find(([path]) => path.includes(islandPath || '')); if (island) { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(island[1])); } else { res.statusCode = 404; res.end(JSON.stringify({ error: 'Island not found' })); } }); }, async handleHotUpdate({ file, server }) { if (!resolvedOptions.hmr) return; const isIsland = file.includes(resolvedOptions.islandsDir) && (file.endsWith('.zk') || file.match(/Island\.(ts|tsx)$/)); if (isIsland) { const content = await fs.readFile(file, 'utf-8'); const isZKFile = file.endsWith('.zk'); const relativePath = path.relative(projectRoot, file); server.ws.send({ type: 'custom', event: 'hydra:island-updated', data: { islandPath: relativePath, type: isZKFile ? 'zk' : 'ts', content: isZKFile ? this.parseZKFile(content) : content } }); } }, async transform(code: string, id: string) { // Only transform .zk files if (!id.endsWith('.zk')) { return null; } try { const parsed = this.parseZKFile(code); if (parsed.error) { throw new Error(parsed.error); } // Generate transformed code const transformedCode = ` // ZenithKernel Single File Component export const __ZK_SFC__ = true; export const __ZK_TEMPLATE__ = ${JSON.stringify(parsed.template)}; export const __ZK_SCRIPT__ = ${JSON.stringify(parsed.script)}; export const __ZK_STYLES__ = ${JSON.stringify(parsed.style)}; // Re-export script content ${parsed.script} // Default export for component export default { template: __ZK_TEMPLATE__, script: __ZK_SCRIPT__, styles: __ZK_STYLES__ }; `.trim(); return { code: transformedCode, map: null }; } catch (error) { console.error('Failed to transform .zk file:', error); throw error; } }, async transform(code: string, id: string) { // Only transform .zk files if (!id.endsWith('.zk')) { return null; } try { const parsed = this.parseZKFile(code); if (parsed.error) { throw new Error(parsed.error); } // Generate transformed code const transformedCode = ` // ZenithKernel Single File Component export const __ZK_SFC__ = true; export const __ZK_TEMPLATE__ = ${JSON.stringify(parsed.template)}; export const __ZK_SCRIPT__ = ${JSON.stringify(parsed.script)}; export const __ZK_STYLES__ = ${JSON.stringify(parsed.style)}; // Re-export script content ${parsed.script} // Default export for component export default { template: __ZK_TEMPLATE__, script: __ZK_SCRIPT__, styles: __ZK_STYLES__ }; `.trim(); return { code: transformedCode, map: null }; } catch (error) { console.error('Failed to transform .zk file:', error); throw error; } }, generateBundle() { if (!resolvedOptions.generateRegistry) return; const registry = Array.from(islands.entries()).map(([file, data]) => ({ path: file, ...data })); this.emitFile({ type: 'asset', fileName: 'hydra/island-registry.js', source: `export const ISLANDS = ${JSON.stringify(registry, null, 2)};` }); }, parseZKFile(content: string | undefined | null) { if (!content) { return { template: '', script: '', style: '', error: 'Invalid or empty content' }; } try { const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/); const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/); const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/); return { template: templateMatch ? templateMatch[1].trim() : '', script: scriptMatch ? scriptMatch[1].trim() : '', style: styleMatch ? styleMatch[1].trim() : '', error: !templateMatch ? 'Missing template section' : undefined }; } catch (error) { return { template: '', script: '', style: '', error: error instanceof Error ? error.message : 'Failed to parse ZK file' }; } }, extractMetadata(content: string) { try { const metadataMatch = content.match(/export\s+const\s+metadata\s*=\s*({[\s\S]*?});/); if (metadataMatch) { return JSON.parse(metadataMatch[1]); } } catch (error) { console.warn('Failed to parse metadata:', error); } return {}; }, async generateRegistry() { try { const outDir = path.join(projectRoot, resolvedOptions.outDir, 'hydra'); await fs.mkdir(outDir, { recursive: true }); const registry = Array.from(islands.entries()).map(([file, data]) => ({ path: file, ...data })); await fs.writeFile( path.join(outDir, 'island-registry.js'), `export const ISLANDS = ${JSON.stringify(registry, null, 2)};` ); await fs.writeFile( path.join(outDir, 'island-registry.d.ts'), `export declare const ISLANDS: Array<{ path: string; type: 'zk' | 'ts'; content: any; metadata: any; }>;` ); } catch (error) { console.error('Failed to generate island registry:', error); } } }; }