@withl5e/l5e 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry-server.js +123 -90
- package/dist/entry-server.js.map +1 -1
- package/dist/{generateMetadata-DLMaI0EO.js → generateMetadata-CIFVlViV.js} +4 -4
- package/dist/{generateMetadata-DLMaI0EO.js.map → generateMetadata-CIFVlViV.js.map} +1 -1
- package/dist/index.js +39 -37
- package/dist/island/runtime.js +9 -8
- package/dist/island/runtime.js.map +1 -1
- package/dist/island.js +36 -23
- package/dist/island.js.map +1 -1
- package/dist/{jsx-runtime-DVQ9eOji.js → jsx-runtime-Bokflh8Q.js} +68 -55
- package/dist/jsx-runtime-Bokflh8Q.js.map +1 -0
- package/dist/jsx-runtime.js +12 -10
- package/dist/{render-BF7iTagJ.js → render-DTXfJu0d.js} +2 -2
- package/dist/{render-BF7iTagJ.js.map → render-DTXfJu0d.js.map} +1 -1
- package/dist/seo.js +2 -2
- package/dist/server.js +1 -1
- package/dist/vite-plugin.js +69 -67
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +17 -1
- package/src/core/entry-server.ts +67 -1
- package/src/core/jsx-runtime.ts +33 -0
- package/src/core/vite-plugin.ts +12 -0
- package/src/island/ClientIsland.ts +36 -1
- package/src/island/runtime.ts +10 -3
- package/src/island/types.ts +1 -0
- package/dist/jsx-runtime-DVQ9eOji.js.map +0 -1
package/dist/vite-plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.js","sources":["../src/core/vite-plugin.ts"],"sourcesContent":["import { transform } from 'esbuild';\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmSync,\n statSync,\n writeFileSync,\n} from 'fs';\nimport { dirname, join, relative, resolve } from 'path';\nimport type { Plugin, UserConfig } from 'vite';\n\nconst VIRTUAL_L5E_VIEWS = 'virtual:l5e-views';\nconst VIRTUAL_L5E_ROUTE = 'virtual:l5e-route';\nconst VIRTUAL_L5E_SSR_ENTRY = 'virtual:l5e-ssr-entry';\nconst VIRTUAL_L5E_GLOBAL_LOADER = 'virtual:l5e-global-loader';\nconst VIRTUAL_L5E_ISLAND_STRATEGIES = 'virtual:l5e-island-strategies';\nconst VIRTUAL_L5E_ACTIONS = 'virtual:l5e-actions';\nconst VIRTUAL_L5E_MIDDLEWARE = 'virtual:l5e-middleware';\n\n/**\n * Vite's built-in env vars on `import.meta.env`. These are statically replaced\n * by Vite itself (DEV/PROD/SSR are booleans, MODE/BASE_URL strings), so the SSR\n * `import.meta.env.* -> process.env.*` rewrite must skip them — otherwise it\n * clobbers Vite defaults (e.g. `import.meta.env.DEV` would become the undefined\n * `process.env.DEV`).\n */\nconst VITE_RESERVED_ENV = new Set(['MODE', 'BASE_URL', 'PROD', 'DEV', 'SSR', 'LEGACY']);\n\n/**\n * Recursively scan directory for .tsx and .ts files\n */\nfunction scanTsFiles(dir: string, fileList: string[] = []): string[] {\n const files = readdirSync(dir);\n\n for (const file of files) {\n const filePath = join(dir, file);\n const stat = statSync(filePath);\n\n if (stat.isDirectory()) {\n // Skip node_modules and dist directories\n if (file === 'node_modules' || file === 'dist' || file === '.git') {\n continue;\n }\n scanTsFiles(filePath, fileList);\n } else if (file.endsWith('.tsx') || file.endsWith('.ts')) {\n fileList.push(filePath);\n }\n }\n\n return fileList;\n}\n\n/**\n * Short hash function for island keys\n */\nfunction shortHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n hash = (hash << 5) - hash + str.charCodeAt(i);\n hash = hash & hash;\n }\n return Math.abs(hash).toString(16).slice(0, 4);\n}\n\n/**\n * Derive component name from path: \"./react/Counter\" → \"Counter\"\n */\nfunction deriveComponentName(fromPath: string): string {\n const segments = fromPath.split('/');\n let filename = segments[segments.length - 1];\n // Remove extension if present\n filename = filename.replace(/\\.(tsx?|jsx?)$/, '');\n return filename;\n}\n\n/**\n * Create island registry key: \"/src/views/test-island/react/Counter\" → \"Counter_a3f2\"\n */\nfunction makeIslandKey(resolvedPath: string): string {\n const name = deriveComponentName(resolvedPath);\n return `${name}_${shortHash(resolvedPath)}`;\n}\n\n/**\n * Resolve file path with extension (.tsx, .ts, .jsx, .js)\n * Returns the relative path with extension (e.g., \"src/views/.../Counter.tsx\")\n * or null if file not found\n */\nfunction resolveWithExtension(resolvedPath: string, rootDir: string): string | null {\n // resolvedPath: \"/src/views/test-island/react/Counter\" (no extension, leading /)\n const relPath = resolvedPath.replace(/^\\//, ''); // \"src/views/.../Counter\"\n const absBase = resolve(rootDir, relPath);\n\n // Check with extensions\n for (const ext of ['.tsx', '.ts', '.jsx', '.js']) {\n if (existsSync(absBase + ext)) {\n return relPath + ext; // \"src/views/.../Counter.tsx\"\n }\n }\n // Check without extension (file might already have one)\n if (existsSync(absBase)) {\n return relPath;\n }\n return null;\n}\n\n/**\n * Extract island entries from file using regex on JSX source\n */\nfunction extractIslandEntries(\n filePath: string,\n rootDir: string,\n): Array<{ component: string; resolvedPath: string; src: string; key: string }> {\n const content = readFileSync(filePath, 'utf-8');\n const entries: Array<{ component: string; resolvedPath: string; src: string; key: string }> = [];\n\n // Regex scan JSX source (NOT post-transform code).\n // Just find <ClientIsland ... from=\"Y\" ...> — component name derived from path.\n const regex = /<ClientIsland\\s[^>]*?from\\s*=\\s*\"([^\"]+)\"/g;\n\n const seen = new Set<string>();\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const fromPath = match[1];\n\n // Resolve relative path → absolute\n let resolvedPath: string;\n if (fromPath.startsWith('~/')) {\n // Alias ~/ → /src/ (project convention)\n resolvedPath = '/src/' + fromPath.substring(2);\n } else if (fromPath.startsWith('/src/')) {\n resolvedPath = fromPath;\n } else if (fromPath.startsWith('./') || fromPath.startsWith('../')) {\n const abs = resolve(filePath, '..', fromPath);\n resolvedPath = '/' + relative(rootDir, abs).replace(/\\\\/g, '/');\n } else {\n resolvedPath = fromPath;\n }\n\n // Resolve file extension for manifest compatibility\n const src = resolveWithExtension(resolvedPath, rootDir);\n if (!src) {\n console.warn(\n `[l5e] Island component not found: ${resolvedPath} (from ${fromPath} in ${filePath})`,\n );\n continue;\n }\n\n const key = makeIslandKey(resolvedPath);\n if (!seen.has(key)) {\n seen.add(key);\n entries.push({\n component: deriveComponentName(resolvedPath),\n resolvedPath,\n src, // \"src/views/.../Counter.tsx\" — matches manifest key format\n key,\n });\n }\n }\n return entries;\n}\n\n/**\n * Extract paths from useCss and useClientJs calls using regex\n */\nfunction extractPathsFromFile(filePath: string, rootDir: string): { css: string[]; js: string[] } {\n const content = readFileSync(filePath, 'utf-8');\n const css: string[] = [];\n const js: string[] = [];\n\n // Regex patterns to match useCss('path') or useCss(\"path\")\n // Handles single and double quotes, and escaped quotes\n const useCssPattern = /useCss\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*,?\\s*\\)/g;\n const useClientJsPattern = /useClientJs\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*,?\\s*\\)/g;\n\n let match;\n\n // Extract CSS paths\n while ((match = useCssPattern.exec(content)) !== null) {\n const path = match[1];\n // Convert relative path to absolute if needed\n if (path.startsWith('/src/')) {\n css.push(path);\n } else if (path.startsWith('./') || path.startsWith('../')) {\n // Resolve relative path\n const absolutePath = resolve(filePath, '..', path);\n const relativePath = '/' + relative(rootDir, absolutePath).replace(/\\\\/g, '/');\n css.push(relativePath);\n } else {\n css.push(path);\n }\n }\n\n // Extract JS paths\n while ((match = useClientJsPattern.exec(content)) !== null) {\n const path = match[1];\n if (path.startsWith('/src/')) {\n js.push(path);\n } else if (path.startsWith('./') || path.startsWith('../')) {\n const absolutePath = resolve(filePath, '..', path);\n const relativePath = '/' + relative(rootDir, absolutePath).replace(/\\\\/g, '/');\n js.push(relativePath);\n } else {\n js.push(path);\n }\n }\n\n return { css, js };\n}\n\n/**\n * Auto-discover rollup input entries from useCss and useClientJs calls\n */\nfunction discoverRollupInput(rootDir: string): {\n input: Record<string, string>;\n islandRegistry: Map<string, string>;\n pathToKey: Map<string, string>;\n keyToSrc: Map<string, string>;\n actionRegistry: Map<string, { modulePath: string; actionName: string }>;\n} {\n const input: Record<string, string> = {};\n const srcDir = join(rootDir, 'src');\n\n // Island registries\n const islandRegistry = new Map<string, string>(); // key → resolvedPath\n const pathToKey = new Map<string, string>(); // resolvedPath → key\n const keyToSrc = new Map<string, string>(); // key → src (manifest-compatible path with extension)\n\n // Action registry: actionKey → { modulePath, actionName }\n const actionRegistry = new Map<string, { modulePath: string; actionName: string }>();\n\n try {\n // Check if src directory exists\n if (!statSync(srcDir).isDirectory()) {\n return { input, islandRegistry, pathToKey, keyToSrc, actionRegistry };\n }\n\n // Check if src/client.global.ts exists and add it as an entry\n const globalTsPath = join(rootDir, 'src', 'client.global.ts');\n if (existsSync(globalTsPath)) {\n input['global'] = globalTsPath;\n console.log('[l5e] Detected src/client.global.ts and added as entry');\n }\n\n // Scan all .tsx and .ts files\n const tsFiles = scanTsFiles(srcDir);\n\n // Extract all paths\n const allCssPaths = new Set<string>();\n const allJsPaths = new Set<string>();\n\n for (const file of tsFiles) {\n const { css, js } = extractPathsFromFile(file, rootDir);\n css.forEach((path) => allCssPaths.add(path));\n js.forEach((path) => allJsPaths.add(path));\n\n // Extract island entries\n const islandEntries = extractIslandEntries(file, rootDir);\n for (const entry of islandEntries) {\n islandRegistry.set(entry.key, entry.resolvedPath);\n pathToKey.set(entry.resolvedPath, entry.key);\n keyToSrc.set(entry.key, entry.src);\n }\n\n // Extract action entries from actions.ts/tsx files\n const normalizedFile = file.replace(/\\\\/g, '/');\n if (/\\/actions\\.(ts|tsx)$/.test(normalizedFile)) {\n const content = readFileSync(file, 'utf-8');\n const actionExportRegex = /export\\s+const\\s+(\\w+)\\s*=\\s*defineAction\\s*\\(/g;\n let actionMatch;\n\n // Compute modulePath: relative path from src/ to parent dir\n const relFromSrc = relative(srcDir, dirname(file)).replace(/\\\\/g, '/');\n const modulePath = relFromSrc || '.';\n\n while ((actionMatch = actionExportRegex.exec(content)) !== null) {\n const actionName = actionMatch[1];\n const actionKey = `${actionName}_${shortHash(modulePath)}`;\n actionRegistry.set(actionKey, { modulePath, actionName });\n }\n }\n }\n\n // Convert paths to rollup input entries\n // CSS files\n for (const cssPath of allCssPaths) {\n // Remove leading /src/ and convert to relative path\n const relativePath = cssPath.startsWith('/src/')\n ? cssPath.substring(1) // Remove leading /\n : cssPath.startsWith('/')\n ? cssPath.substring(1)\n : cssPath;\n\n const absolutePath = resolve(rootDir, relativePath);\n\n // Only add if file exists\n if (!existsSync(absolutePath)) {\n console.warn(`[l5e] CSS file not found: ${absolutePath} (from ${cssPath})`);\n continue;\n }\n\n // Generate entry name from path (e.g., /src/views/home/home.css -> views-home-home)\n const entryName = relativePath\n .replace(/^src\\//, '')\n .replace(/\\.css$/, '')\n .replace(/\\//g, '-')\n .replace(/\\\\/g, '-');\n\n input[entryName] = absolutePath;\n }\n\n // JS/TS files\n for (const jsPath of allJsPaths) {\n const relativePath = jsPath.startsWith('/src/')\n ? jsPath.substring(1)\n : jsPath.startsWith('/')\n ? jsPath.substring(1)\n : jsPath;\n\n const absolutePath = resolve(rootDir, relativePath);\n\n // Only add if file exists\n if (!existsSync(absolutePath)) {\n console.warn(`[l5e] JS/TS file not found: ${absolutePath} (from ${jsPath})`);\n continue;\n }\n\n // Generate entry name from path\n const entryName = relativePath\n .replace(/^src\\//, '')\n .replace(/\\.(ts|tsx|js|jsx)$/, '')\n .replace(/\\//g, '-')\n .replace(/\\\\/g, '-');\n\n input[entryName] = absolutePath;\n }\n\n // Add island component files to rollup input so they appear in manifest.\n // server.ts looks up manifest at runtime to resolve per-page island URLs.\n for (const [key, src] of keyToSrc) {\n const absolutePath = resolve(rootDir, src);\n input[`island-${key}`] = absolutePath;\n }\n\n if (islandRegistry.size > 0) {\n console.log(`[l5e] Detected ${islandRegistry.size} island(s)`);\n }\n if (actionRegistry.size > 0) {\n console.log(`[l5e] Detected ${actionRegistry.size} action(s)`);\n }\n } catch (error) {\n // Silently fail if directory doesn't exist or other errors\n console.warn('[l5e] Failed to discover rollup input:', error);\n }\n\n return { input, islandRegistry, pathToKey, keyToSrc, actionRegistry };\n}\n\n/**\n * Inject __key prop into ClientIsland calls\n * Use anchor-based approach to avoid fragile regex with nested objects\n */\nfunction injectIslandKeys(\n code: string,\n fileId: string,\n rootDir: string,\n pathToKey: Map<string, string>,\n keyToSrc: Map<string, string>,\n): string {\n // After esbuild, code has form:\n // jsxFactory(ClientIsland, { from: \"./react/Counter\", props: { n: 5 } })\n // ^anchor ^find from here\n\n const result: string[] = [];\n let lastIndex = 0;\n\n // Find each position \"ClientIsland,\" (anchor)\n const anchorRegex = /ClientIsland\\s*,\\s*\\{/g;\n let anchorMatch;\n\n while ((anchorMatch = anchorRegex.exec(code)) !== null) {\n const searchStart = anchorMatch.index + anchorMatch[0].length;\n\n // Scan for from: \"...\" in window ~500 chars after anchor\n const window = code.substring(searchStart, searchStart + 500);\n const fromMatch = /from:\\s*\"([^\"]+)\"/.exec(window);\n\n if (!fromMatch) continue;\n\n const fromPath = fromMatch[1];\n\n // Resolve path (handle ~/ alias)\n let resolved: string;\n if (fromPath.startsWith('~/')) {\n resolved = '/src/' + fromPath.substring(2);\n } else if (fromPath.startsWith('/src/')) {\n resolved = fromPath;\n } else if (fromPath.startsWith('./') || fromPath.startsWith('../')) {\n const abs = resolve(fileId, '..', fromPath);\n resolved = '/' + relative(rootDir, abs).replace(/\\\\/g, '/');\n } else {\n resolved = fromPath;\n }\n\n const key = pathToKey.get(resolved);\n if (!key) continue;\n\n const src = keyToSrc.get(key);\n if (!src) continue;\n\n // Inject __key and __src right after from: \"...\"\n const insertPos = searchStart + fromMatch.index + fromMatch[0].length;\n result.push(code.substring(lastIndex, insertPos));\n result.push(`, __key: \"${key}\", __src: \"${src}\"`);\n lastIndex = insertPos;\n }\n\n result.push(code.substring(lastIndex));\n return result.join('');\n}\n\nexport function coreVite(): Plugin {\n let rootDir: string = process.cwd();\n let islandRegistry = new Map<string, string>();\n let pathToKey = new Map<string, string>();\n let keyToSrc = new Map<string, string>();\n let actionRegistry = new Map<string, { modulePath: string; actionName: string }>();\n\n return {\n name: 'l5e-jsx-classic',\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n // Store root directory for later use\n rootDir = resolvedConfig.root || process.cwd();\n },\n\n handleHotUpdate({ file, server }) {\n const relPath = relative(rootDir, file);\n\n // Re-scan action registry when an actions file changes\n if (/actions\\.(ts|tsx)$/.test(file)) {\n const discovered = discoverRollupInput(rootDir);\n actionRegistry = discovered.actionRegistry;\n // Invalidate the virtual:l5e-actions module so server picks up new registry\n const mod = server.moduleGraph.getModuleById('\\0' + VIRTUAL_L5E_ACTIONS);\n if (mod) {\n server.moduleGraph.invalidateModule(mod);\n }\n console.log(\n `[l5e] Action file changed: ${relPath} — re-scanned ${actionRegistry.size} action(s)`,\n );\n }\n\n // Allow Vite's default HMR for CSS and client-side JS files\n if (file.endsWith('.css')) {\n console.log(`[l5e] CSS changed: ${relPath} - using Vite HMR`);\n return; // Let Vite handle CSS HMR\n }\n\n if (file.includes('client.ts')) {\n console.log(`[l5e] Client JS changed: ${relPath} - using Vite HMR`);\n return; // Let Vite handle client JS HMR\n }\n\n // Trigger full page reload for SSR-related file changes (components, loaders, routes)\n // This ensures that server-side rendered components update properly\n if (file.includes('/src/') || file.includes('\\\\src\\\\')) {\n console.log(`[l5e] SSR file changed: ${relPath} - triggering full reload`);\n server.ws.send({\n type: 'full-reload',\n path: '*',\n });\n return []; // Prevent Vite's default HMR behavior\n }\n },\n\n buildEnd() {\n // Cleanup temporary files after build\n const tempDir = join(rootDir, '.l5e-temp');\n if (existsSync(tempDir)) {\n try {\n rmSync(tempDir, { recursive: true, force: true });\n console.log('[l5e] Cleaned up temporary files');\n } catch (error) {\n console.warn('[l5e] Failed to cleanup temporary files:', error);\n }\n }\n },\n\n writeBundle(_options, _bundle) {\n // Emit action registry JSON for production server\n if (actionRegistry.size > 0) {\n const outDir = join(rootDir, 'dist', 'server');\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n const registryObj = Object.fromEntries(actionRegistry);\n writeFileSync(join(outDir, 'action-registry.json'), JSON.stringify(registryObj), 'utf-8');\n console.log(`[l5e] Wrote action-registry.json (${actionRegistry.size} actions)`);\n }\n },\n\n config(userConfig) {\n // Auto-discover rollup input from useCss and useClientJs\n const projectRoot = userConfig.root || process.cwd();\n const discovered = discoverRollupInput(projectRoot);\n\n // Store island registries for use in other hooks\n islandRegistry = discovered.islandRegistry;\n pathToKey = discovered.pathToKey;\n keyToSrc = discovered.keyToSrc;\n actionRegistry = discovered.actionRegistry;\n\n // Merge with existing rollupOptions.input if any\n const existingInput = userConfig.build?.rollupOptions?.input || {};\n const mergedInput =\n typeof existingInput === 'object' && !Array.isArray(existingInput)\n ? { ...discovered.input, ...existingInput }\n : discovered.input;\n\n return {\n build: {\n ...userConfig.build,\n rollupOptions: {\n ...userConfig.build?.rollupOptions,\n input: Object.keys(mergedInput).length > 0 ? mergedInput : undefined,\n // Preserve exports for island component entries — without this,\n // Rollup tree-shakes their exports since nothing in the bundle imports them\n // (they're loaded at runtime via dynamic import from the island runtime).\n preserveEntrySignatures: 'exports-only',\n },\n },\n } satisfies UserConfig;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_L5E_VIEWS) {\n return '\\0' + VIRTUAL_L5E_VIEWS;\n }\n if (id === VIRTUAL_L5E_ROUTE) {\n return '\\0' + VIRTUAL_L5E_ROUTE;\n }\n if (id === VIRTUAL_L5E_SSR_ENTRY) {\n return '\\0' + VIRTUAL_L5E_SSR_ENTRY;\n }\n if (id === VIRTUAL_L5E_GLOBAL_LOADER) {\n return '\\0' + VIRTUAL_L5E_GLOBAL_LOADER;\n }\n if (id === VIRTUAL_L5E_ISLAND_STRATEGIES) {\n return '\\0' + VIRTUAL_L5E_ISLAND_STRATEGIES;\n }\n if (id === VIRTUAL_L5E_ACTIONS) {\n return '\\0' + VIRTUAL_L5E_ACTIONS;\n }\n if (id === VIRTUAL_L5E_MIDDLEWARE) {\n return '\\0' + VIRTUAL_L5E_MIDDLEWARE;\n }\n return null;\n },\n\n async transform(code, id, options) {\n // Auto-inject island runtime into client.global.ts so it's always loaded globally\n if (id.replace(/\\\\/g, '/').endsWith('src/client.global.ts') && !options?.ssr) {\n return {\n code: `import '@withl5e/l5e/island/runtime';\\n${code}`,\n map: null,\n };\n }\n\n // Client-side action transform: replace defineAction exports with fetch stubs\n // Supports actions anywhere under src/ (e.g., src/views/*, src/features/*, etc.)\n if (!options?.ssr) {\n const normalizedId = id.replace(/\\\\/g, '/');\n const actionMatch = normalizedId.match(/\\/src\\/(.+)\\/actions\\.(ts|tsx)$/);\n if (actionMatch) {\n const modulePath = actionMatch[1];\n\n // Parse exported action names\n const exportRegex = /export\\s+const\\s+(\\w+)\\s*=\\s*defineAction\\s*\\(/g;\n const actions: Array<{ name: string; method: string }> = [];\n let m;\n while ((m = exportRegex.exec(code)) !== null) {\n const name = m[1];\n // Find method for this action — scan from the defineAction( position\n const chunk = code.substring(m.index, m.index + 500);\n const methodMatch = chunk.match(/method:\\s*['\"](\\w+)['\"]/);\n const method = methodMatch ? methodMatch[1].toUpperCase() : 'GET';\n actions.push({ name, method });\n }\n\n if (actions.length > 0) {\n const stubs = actions.map(({ name, method }) => {\n const actionKey = `${name}_${shortHash(modulePath)}`;\n if (method === 'GET') {\n return `export async function ${name}(params) {\n const res = await fetch('/_l5e/action/${actionKey}?' + new URLSearchParams(params));\n if (!res.ok) throw Object.assign(new Error('HTTP ' + res.status), { status: res.status });\n return res;\n}`;\n }\n return `export async function ${name}(body) {\n const res = await fetch('/_l5e/action/${actionKey}', {\n method: '${method}',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw Object.assign(new Error('HTTP ' + res.status), { status: res.status });\n return res;\n}`;\n });\n\n return {\n code: stubs.join('\\n\\n'),\n map: null,\n };\n }\n }\n }\n\n // Skip node_modules\n if (id.includes('node_modules')) {\n return null;\n }\n\n // Skip files in /react/ directories\n if (id.includes('/react/') || id.includes('\\\\react\\\\')) {\n return null;\n }\n\n // Check if this is a JSX/TSX file that needs L5E JSX transformation\n const isJsxFile = /\\.(tsx|jsx)$/.test(id);\n\n if (isJsxFile) {\n try {\n const injected = `import { Fragment as __Fragment, jsxFactory } from \"@withl5e/l5e/jsx-runtime\"\\n${code}`;\n\n // Transform JSX using esbuild with L5E's JSX runtime (async)\n const result = await transform(injected, {\n loader: id.endsWith('.tsx') ? 'tsx' : 'jsx',\n jsx: 'transform',\n jsxFactory: 'jsxFactory',\n jsxFragment: '__Fragment',\n sourcemap: true,\n sourcefile: id,\n target: 'es2020',\n });\n\n let transformedCode = result.code;\n\n // Inject __key and __src for ALL ClientIsland calls\n if (transformedCode.includes('ClientIsland')) {\n transformedCode = injectIslandKeys(transformedCode, id, rootDir, pathToKey, keyToSrc);\n }\n\n return {\n code: transformedCode,\n map: result.map || null,\n };\n } catch (error) {\n // Log error but let Vite handle it\n console.error(`[l5e] Failed to transform JSX in ${id}:`, error);\n throw error;\n }\n }\n\n // Transform import.meta.env thà nh process.env ở server side\n // Server side có thể access tất cả env variables\n // Client side chỉ access được VITE_ env variables\n // Transform nà y chạy ở runtime khi module được load trong SSR context\n if (options?.ssr) {\n let transformedCode = code;\n let hasChanges = false;\n\n // Thay thế import.meta.env.VARIABLE_NAME thà nh process.env.VARIABLE_NAME\n // Match: import.meta.env.VITE_EVENT_CATEGORY_SLUG\n transformedCode = transformedCode.replace(\n /import\\.meta\\.env\\.([a-zA-Z_][a-zA-Z0-9_]*)/g,\n (match, varName) => {\n // Leave Vite's built-in env vars for Vite to handle.\n if (VITE_RESERVED_ENV.has(varName)) return match;\n hasChanges = true;\n return `process.env.${varName}`;\n },\n );\n\n // Thay thế import.meta.env['VARIABLE_NAME'] hoặc import.meta.env[\"VARIABLE_NAME\"]\n // thà nh process.env['VARIABLE_NAME'] hoặc process.env[\"VARIABLE_NAME\"]\n // Match: import.meta.env['VITE_EVENT_CATEGORY_SLUG'] hoặc import.meta.env[\"VITE_EVENT_CATEGORY_SLUG\"]\n transformedCode = transformedCode.replace(\n /import\\.meta\\.env\\[(['\"`])([^'\"`]+)\\1\\]/g,\n (match, quote, varName) => {\n // Leave Vite's built-in env vars for Vite to handle.\n if (VITE_RESERVED_ENV.has(varName)) return match;\n hasChanges = true;\n return `process.env[${quote}${varName}${quote}]`;\n },\n );\n\n if (hasChanges) {\n return {\n code: transformedCode,\n map: null, // Không cần source map cho env transform\n };\n }\n }\n return null;\n },\n\n load(id) {\n // Virtual module: l5e-views\n if (id === '\\0' + VIRTUAL_L5E_VIEWS) {\n return `\nexport const viewLoaders = import.meta.glob('/src/views/*/loader.{ts,tsx}');\nexport const viewComponents = import.meta.glob('/src/views/*/index.tsx');\n`;\n }\n\n // Virtual module: l5e-route\n if (id === '\\0' + VIRTUAL_L5E_ROUTE) {\n // Always use TypeScript\n return `export { default } from '/src/route.ts';`;\n }\n\n // Virtual module: l5e-ssr-entry\n if (id === '\\0' + VIRTUAL_L5E_SSR_ENTRY) {\n return `export { render } from '@withl5e/l5e/entry-server';\nexport { viewActions } from 'virtual:l5e-actions';`;\n }\n\n // Virtual module: l5e-global-loader\n if (id === '\\0' + VIRTUAL_L5E_GLOBAL_LOADER) {\n return `export const globalLoader = import.meta.glob('/src/global-loader.{ts,tsx}', { eager: false });`;\n }\n\n // Virtual module: l5e-actions\n if (id === '\\0' + VIRTUAL_L5E_ACTIONS) {\n const registryObj = Object.fromEntries(actionRegistry);\n return `export const viewActions = import.meta.glob('/src/**/actions.{ts,tsx}');\nexport const actionRegistry = ${JSON.stringify(registryObj)};`;\n }\n\n // Virtual module: l5e-middleware\n if (id === '\\0' + VIRTUAL_L5E_MIDDLEWARE) {\n return `\nconst middlewareModules = import.meta.glob([\n '/src/middleware.{ts,tsx,js,jsx}',\n '/src/middleware/index.{ts,tsx,js,jsx}',\n]);\n\nconst middlewarePaths = [\n '/src/middleware.ts',\n '/src/middleware.tsx',\n '/src/middleware.js',\n '/src/middleware.jsx',\n '/src/middleware/index.ts',\n '/src/middleware/index.tsx',\n '/src/middleware/index.js',\n '/src/middleware/index.jsx',\n];\n\nexport async function loadMiddleware() {\n const middlewarePath = middlewarePaths.find((path) => middlewareModules[path]);\n if (!middlewarePath) return undefined;\n\n const mod = await middlewareModules[middlewarePath]();\n return mod.onRequest;\n}\n`;\n }\n\n // Virtual module: l5e-island-strategies\n if (id === '\\0' + VIRTUAL_L5E_ISLAND_STRATEGIES) {\n // Check if src/island-strategies.ts exists\n const strategiesFile = join(rootDir, 'src', 'island-strategies.ts');\n if (existsSync(strategiesFile)) {\n // Re-export user's file → Vite will build this file\n return `import '/src/island-strategies.ts';`;\n }\n // No file → empty module, no error\n return `/* no custom island strategies */`;\n }\n\n return null;\n },\n };\n}\n\nexport default coreVite;\n"],"names":["VIRTUAL_L5E_VIEWS","VIRTUAL_L5E_ROUTE","VIRTUAL_L5E_SSR_ENTRY","VIRTUAL_L5E_GLOBAL_LOADER","VIRTUAL_L5E_ISLAND_STRATEGIES","VIRTUAL_L5E_ACTIONS","VIRTUAL_L5E_MIDDLEWARE","VITE_RESERVED_ENV","scanTsFiles","dir","fileList","files","readdirSync","file","filePath","join","statSync","shortHash","str","hash","i","deriveComponentName","fromPath","segments","filename","makeIslandKey","resolvedPath","resolveWithExtension","rootDir","relPath","absBase","resolve","ext","existsSync","extractIslandEntries","content","readFileSync","entries","regex","seen","match","abs","relative","src","key","extractPathsFromFile","css","js","useCssPattern","useClientJsPattern","path","absolutePath","relativePath","discoverRollupInput","input","srcDir","islandRegistry","pathToKey","keyToSrc","actionRegistry","globalTsPath","tsFiles","allCssPaths","allJsPaths","islandEntries","entry","normalizedFile","actionExportRegex","actionMatch","modulePath","dirname","actionName","actionKey","cssPath","entryName","jsPath","error","injectIslandKeys","code","fileId","result","lastIndex","anchorRegex","anchorMatch","searchStart","window","fromMatch","resolved","insertPos","coreVite","resolvedConfig","server","mod","tempDir","rmSync","_options","_bundle","outDir","mkdirSync","registryObj","writeFileSync","userConfig","projectRoot","discovered","existingInput","mergedInput","id","options","exportRegex","actions","m","name","methodMatch","method","injected","transform","transformedCode","hasChanges","varName","quote","strategiesFile"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","sources":["../src/core/vite-plugin.ts"],"sourcesContent":["import { transform } from 'esbuild';\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmSync,\n statSync,\n writeFileSync,\n} from 'fs';\nimport { dirname, join, relative, resolve } from 'path';\nimport type { Plugin, UserConfig } from 'vite';\n\nconst VIRTUAL_L5E_VIEWS = 'virtual:l5e-views';\nconst VIRTUAL_L5E_ROUTE = 'virtual:l5e-route';\nconst VIRTUAL_L5E_SSR_ENTRY = 'virtual:l5e-ssr-entry';\nconst VIRTUAL_L5E_GLOBAL_LOADER = 'virtual:l5e-global-loader';\nconst VIRTUAL_L5E_ISLAND_STRATEGIES = 'virtual:l5e-island-strategies';\nconst VIRTUAL_L5E_ISLANDS = 'virtual:l5e-islands';\nconst VIRTUAL_L5E_ACTIONS = 'virtual:l5e-actions';\nconst VIRTUAL_L5E_MIDDLEWARE = 'virtual:l5e-middleware';\n\n/**\n * Vite's built-in env vars on `import.meta.env`. These are statically replaced\n * by Vite itself (DEV/PROD/SSR are booleans, MODE/BASE_URL strings), so the SSR\n * `import.meta.env.* -> process.env.*` rewrite must skip them — otherwise it\n * clobbers Vite defaults (e.g. `import.meta.env.DEV` would become the undefined\n * `process.env.DEV`).\n */\nconst VITE_RESERVED_ENV = new Set(['MODE', 'BASE_URL', 'PROD', 'DEV', 'SSR', 'LEGACY']);\n\n/**\n * Recursively scan directory for .tsx and .ts files\n */\nfunction scanTsFiles(dir: string, fileList: string[] = []): string[] {\n const files = readdirSync(dir);\n\n for (const file of files) {\n const filePath = join(dir, file);\n const stat = statSync(filePath);\n\n if (stat.isDirectory()) {\n // Skip node_modules and dist directories\n if (file === 'node_modules' || file === 'dist' || file === '.git') {\n continue;\n }\n scanTsFiles(filePath, fileList);\n } else if (file.endsWith('.tsx') || file.endsWith('.ts')) {\n fileList.push(filePath);\n }\n }\n\n return fileList;\n}\n\n/**\n * Short hash function for island keys\n */\nfunction shortHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n hash = (hash << 5) - hash + str.charCodeAt(i);\n hash = hash & hash;\n }\n return Math.abs(hash).toString(16).slice(0, 4);\n}\n\n/**\n * Derive component name from path: \"./react/Counter\" → \"Counter\"\n */\nfunction deriveComponentName(fromPath: string): string {\n const segments = fromPath.split('/');\n let filename = segments[segments.length - 1];\n // Remove extension if present\n filename = filename.replace(/\\.(tsx?|jsx?)$/, '');\n return filename;\n}\n\n/**\n * Create island registry key: \"/src/views/test-island/react/Counter\" → \"Counter_a3f2\"\n */\nfunction makeIslandKey(resolvedPath: string): string {\n const name = deriveComponentName(resolvedPath);\n return `${name}_${shortHash(resolvedPath)}`;\n}\n\n/**\n * Resolve file path with extension (.tsx, .ts, .jsx, .js)\n * Returns the relative path with extension (e.g., \"src/views/.../Counter.tsx\")\n * or null if file not found\n */\nfunction resolveWithExtension(resolvedPath: string, rootDir: string): string | null {\n // resolvedPath: \"/src/views/test-island/react/Counter\" (no extension, leading /)\n const relPath = resolvedPath.replace(/^\\//, ''); // \"src/views/.../Counter\"\n const absBase = resolve(rootDir, relPath);\n\n // Check with extensions\n for (const ext of ['.tsx', '.ts', '.jsx', '.js']) {\n if (existsSync(absBase + ext)) {\n return relPath + ext; // \"src/views/.../Counter.tsx\"\n }\n }\n // Check without extension (file might already have one)\n if (existsSync(absBase)) {\n return relPath;\n }\n return null;\n}\n\n/**\n * Extract island entries from file using regex on JSX source\n */\nfunction extractIslandEntries(\n filePath: string,\n rootDir: string,\n): Array<{ component: string; resolvedPath: string; src: string; key: string }> {\n const content = readFileSync(filePath, 'utf-8');\n const entries: Array<{ component: string; resolvedPath: string; src: string; key: string }> = [];\n\n // Regex scan JSX source (NOT post-transform code).\n // Just find <ClientIsland ... from=\"Y\" ...> — component name derived from path.\n const regex = /<ClientIsland\\s[^>]*?from\\s*=\\s*\"([^\"]+)\"/g;\n\n const seen = new Set<string>();\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const fromPath = match[1];\n\n // Resolve relative path → absolute\n let resolvedPath: string;\n if (fromPath.startsWith('~/')) {\n // Alias ~/ → /src/ (project convention)\n resolvedPath = '/src/' + fromPath.substring(2);\n } else if (fromPath.startsWith('/src/')) {\n resolvedPath = fromPath;\n } else if (fromPath.startsWith('./') || fromPath.startsWith('../')) {\n const abs = resolve(filePath, '..', fromPath);\n resolvedPath = '/' + relative(rootDir, abs).replace(/\\\\/g, '/');\n } else {\n resolvedPath = fromPath;\n }\n\n // Resolve file extension for manifest compatibility\n const src = resolveWithExtension(resolvedPath, rootDir);\n if (!src) {\n console.warn(\n `[l5e] Island component not found: ${resolvedPath} (from ${fromPath} in ${filePath})`,\n );\n continue;\n }\n\n const key = makeIslandKey(resolvedPath);\n if (!seen.has(key)) {\n seen.add(key);\n entries.push({\n component: deriveComponentName(resolvedPath),\n resolvedPath,\n src, // \"src/views/.../Counter.tsx\" — matches manifest key format\n key,\n });\n }\n }\n return entries;\n}\n\n/**\n * Extract paths from useCss and useClientJs calls using regex\n */\nfunction extractPathsFromFile(filePath: string, rootDir: string): { css: string[]; js: string[] } {\n const content = readFileSync(filePath, 'utf-8');\n const css: string[] = [];\n const js: string[] = [];\n\n // Regex patterns to match useCss('path') or useCss(\"path\")\n // Handles single and double quotes, and escaped quotes\n const useCssPattern = /useCss\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*,?\\s*\\)/g;\n const useClientJsPattern = /useClientJs\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*,?\\s*\\)/g;\n\n let match;\n\n // Extract CSS paths\n while ((match = useCssPattern.exec(content)) !== null) {\n const path = match[1];\n // Convert relative path to absolute if needed\n if (path.startsWith('/src/')) {\n css.push(path);\n } else if (path.startsWith('./') || path.startsWith('../')) {\n // Resolve relative path\n const absolutePath = resolve(filePath, '..', path);\n const relativePath = '/' + relative(rootDir, absolutePath).replace(/\\\\/g, '/');\n css.push(relativePath);\n } else {\n css.push(path);\n }\n }\n\n // Extract JS paths\n while ((match = useClientJsPattern.exec(content)) !== null) {\n const path = match[1];\n if (path.startsWith('/src/')) {\n js.push(path);\n } else if (path.startsWith('./') || path.startsWith('../')) {\n const absolutePath = resolve(filePath, '..', path);\n const relativePath = '/' + relative(rootDir, absolutePath).replace(/\\\\/g, '/');\n js.push(relativePath);\n } else {\n js.push(path);\n }\n }\n\n return { css, js };\n}\n\n/**\n * Auto-discover rollup input entries from useCss and useClientJs calls\n */\nfunction discoverRollupInput(rootDir: string): {\n input: Record<string, string>;\n islandRegistry: Map<string, string>;\n pathToKey: Map<string, string>;\n keyToSrc: Map<string, string>;\n actionRegistry: Map<string, { modulePath: string; actionName: string }>;\n} {\n const input: Record<string, string> = {};\n const srcDir = join(rootDir, 'src');\n\n // Island registries\n const islandRegistry = new Map<string, string>(); // key → resolvedPath\n const pathToKey = new Map<string, string>(); // resolvedPath → key\n const keyToSrc = new Map<string, string>(); // key → src (manifest-compatible path with extension)\n\n // Action registry: actionKey → { modulePath, actionName }\n const actionRegistry = new Map<string, { modulePath: string; actionName: string }>();\n\n try {\n // Check if src directory exists\n if (!statSync(srcDir).isDirectory()) {\n return { input, islandRegistry, pathToKey, keyToSrc, actionRegistry };\n }\n\n // Check if src/client.global.ts exists and add it as an entry\n const globalTsPath = join(rootDir, 'src', 'client.global.ts');\n if (existsSync(globalTsPath)) {\n input['global'] = globalTsPath;\n console.log('[l5e] Detected src/client.global.ts and added as entry');\n }\n\n // Scan all .tsx and .ts files\n const tsFiles = scanTsFiles(srcDir);\n\n // Extract all paths\n const allCssPaths = new Set<string>();\n const allJsPaths = new Set<string>();\n\n for (const file of tsFiles) {\n const { css, js } = extractPathsFromFile(file, rootDir);\n css.forEach((path) => allCssPaths.add(path));\n js.forEach((path) => allJsPaths.add(path));\n\n // Extract island entries\n const islandEntries = extractIslandEntries(file, rootDir);\n for (const entry of islandEntries) {\n islandRegistry.set(entry.key, entry.resolvedPath);\n pathToKey.set(entry.resolvedPath, entry.key);\n keyToSrc.set(entry.key, entry.src);\n }\n\n // Extract action entries from actions.ts/tsx files\n const normalizedFile = file.replace(/\\\\/g, '/');\n if (/\\/actions\\.(ts|tsx)$/.test(normalizedFile)) {\n const content = readFileSync(file, 'utf-8');\n const actionExportRegex = /export\\s+const\\s+(\\w+)\\s*=\\s*defineAction\\s*\\(/g;\n let actionMatch;\n\n // Compute modulePath: relative path from src/ to parent dir\n const relFromSrc = relative(srcDir, dirname(file)).replace(/\\\\/g, '/');\n const modulePath = relFromSrc || '.';\n\n while ((actionMatch = actionExportRegex.exec(content)) !== null) {\n const actionName = actionMatch[1];\n const actionKey = `${actionName}_${shortHash(modulePath)}`;\n actionRegistry.set(actionKey, { modulePath, actionName });\n }\n }\n }\n\n // Convert paths to rollup input entries\n // CSS files\n for (const cssPath of allCssPaths) {\n // Remove leading /src/ and convert to relative path\n const relativePath = cssPath.startsWith('/src/')\n ? cssPath.substring(1) // Remove leading /\n : cssPath.startsWith('/')\n ? cssPath.substring(1)\n : cssPath;\n\n const absolutePath = resolve(rootDir, relativePath);\n\n // Only add if file exists\n if (!existsSync(absolutePath)) {\n console.warn(`[l5e] CSS file not found: ${absolutePath} (from ${cssPath})`);\n continue;\n }\n\n // Generate entry name from path (e.g., /src/views/home/home.css -> views-home-home)\n const entryName = relativePath\n .replace(/^src\\//, '')\n .replace(/\\.css$/, '')\n .replace(/\\//g, '-')\n .replace(/\\\\/g, '-');\n\n input[entryName] = absolutePath;\n }\n\n // JS/TS files\n for (const jsPath of allJsPaths) {\n const relativePath = jsPath.startsWith('/src/')\n ? jsPath.substring(1)\n : jsPath.startsWith('/')\n ? jsPath.substring(1)\n : jsPath;\n\n const absolutePath = resolve(rootDir, relativePath);\n\n // Only add if file exists\n if (!existsSync(absolutePath)) {\n console.warn(`[l5e] JS/TS file not found: ${absolutePath} (from ${jsPath})`);\n continue;\n }\n\n // Generate entry name from path\n const entryName = relativePath\n .replace(/^src\\//, '')\n .replace(/\\.(ts|tsx|js|jsx)$/, '')\n .replace(/\\//g, '-')\n .replace(/\\\\/g, '-');\n\n input[entryName] = absolutePath;\n }\n\n // Add island component files to rollup input so they appear in manifest.\n // server.ts looks up manifest at runtime to resolve per-page island URLs.\n for (const [key, src] of keyToSrc) {\n const absolutePath = resolve(rootDir, src);\n input[`island-${key}`] = absolutePath;\n }\n\n if (islandRegistry.size > 0) {\n console.log(`[l5e] Detected ${islandRegistry.size} island(s)`);\n }\n if (actionRegistry.size > 0) {\n console.log(`[l5e] Detected ${actionRegistry.size} action(s)`);\n }\n } catch (error) {\n // Silently fail if directory doesn't exist or other errors\n console.warn('[l5e] Failed to discover rollup input:', error);\n }\n\n return { input, islandRegistry, pathToKey, keyToSrc, actionRegistry };\n}\n\n/**\n * Inject __key prop into ClientIsland calls\n * Use anchor-based approach to avoid fragile regex with nested objects\n */\nfunction injectIslandKeys(\n code: string,\n fileId: string,\n rootDir: string,\n pathToKey: Map<string, string>,\n keyToSrc: Map<string, string>,\n): string {\n // After esbuild, code has form:\n // jsxFactory(ClientIsland, { from: \"./react/Counter\", props: { n: 5 } })\n // ^anchor ^find from here\n\n const result: string[] = [];\n let lastIndex = 0;\n\n // Find each position \"ClientIsland,\" (anchor)\n const anchorRegex = /ClientIsland\\s*,\\s*\\{/g;\n let anchorMatch;\n\n while ((anchorMatch = anchorRegex.exec(code)) !== null) {\n const searchStart = anchorMatch.index + anchorMatch[0].length;\n\n // Scan for from: \"...\" in window ~500 chars after anchor\n const window = code.substring(searchStart, searchStart + 500);\n const fromMatch = /from:\\s*\"([^\"]+)\"/.exec(window);\n\n if (!fromMatch) continue;\n\n const fromPath = fromMatch[1];\n\n // Resolve path (handle ~/ alias)\n let resolved: string;\n if (fromPath.startsWith('~/')) {\n resolved = '/src/' + fromPath.substring(2);\n } else if (fromPath.startsWith('/src/')) {\n resolved = fromPath;\n } else if (fromPath.startsWith('./') || fromPath.startsWith('../')) {\n const abs = resolve(fileId, '..', fromPath);\n resolved = '/' + relative(rootDir, abs).replace(/\\\\/g, '/');\n } else {\n resolved = fromPath;\n }\n\n const key = pathToKey.get(resolved);\n if (!key) continue;\n\n const src = keyToSrc.get(key);\n if (!src) continue;\n\n // Inject __key and __src right after from: \"...\"\n const insertPos = searchStart + fromMatch.index + fromMatch[0].length;\n result.push(code.substring(lastIndex, insertPos));\n result.push(`, __key: \"${key}\", __src: \"${src}\"`);\n lastIndex = insertPos;\n }\n\n result.push(code.substring(lastIndex));\n return result.join('');\n}\n\nexport function coreVite(): Plugin {\n let rootDir: string = process.cwd();\n let islandRegistry = new Map<string, string>();\n let pathToKey = new Map<string, string>();\n let keyToSrc = new Map<string, string>();\n let actionRegistry = new Map<string, { modulePath: string; actionName: string }>();\n\n return {\n name: 'l5e-jsx-classic',\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n // Store root directory for later use\n rootDir = resolvedConfig.root || process.cwd();\n },\n\n handleHotUpdate({ file, server }) {\n const relPath = relative(rootDir, file);\n\n // Re-scan action registry when an actions file changes\n if (/actions\\.(ts|tsx)$/.test(file)) {\n const discovered = discoverRollupInput(rootDir);\n actionRegistry = discovered.actionRegistry;\n // Invalidate the virtual:l5e-actions module so server picks up new registry\n const mod = server.moduleGraph.getModuleById('\\0' + VIRTUAL_L5E_ACTIONS);\n if (mod) {\n server.moduleGraph.invalidateModule(mod);\n }\n console.log(\n `[l5e] Action file changed: ${relPath} — re-scanned ${actionRegistry.size} action(s)`,\n );\n }\n\n // Allow Vite's default HMR for CSS and client-side JS files\n if (file.endsWith('.css')) {\n console.log(`[l5e] CSS changed: ${relPath} - using Vite HMR`);\n return; // Let Vite handle CSS HMR\n }\n\n if (file.includes('client.ts')) {\n console.log(`[l5e] Client JS changed: ${relPath} - using Vite HMR`);\n return; // Let Vite handle client JS HMR\n }\n\n // Trigger full page reload for SSR-related file changes (components, loaders, routes)\n // This ensures that server-side rendered components update properly\n if (file.includes('/src/') || file.includes('\\\\src\\\\')) {\n console.log(`[l5e] SSR file changed: ${relPath} - triggering full reload`);\n server.ws.send({\n type: 'full-reload',\n path: '*',\n });\n return []; // Prevent Vite's default HMR behavior\n }\n },\n\n buildEnd() {\n // Cleanup temporary files after build\n const tempDir = join(rootDir, '.l5e-temp');\n if (existsSync(tempDir)) {\n try {\n rmSync(tempDir, { recursive: true, force: true });\n console.log('[l5e] Cleaned up temporary files');\n } catch (error) {\n console.warn('[l5e] Failed to cleanup temporary files:', error);\n }\n }\n },\n\n writeBundle(_options, _bundle) {\n // Emit action registry JSON for production server\n if (actionRegistry.size > 0) {\n const outDir = join(rootDir, 'dist', 'server');\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n const registryObj = Object.fromEntries(actionRegistry);\n writeFileSync(join(outDir, 'action-registry.json'), JSON.stringify(registryObj), 'utf-8');\n console.log(`[l5e] Wrote action-registry.json (${actionRegistry.size} actions)`);\n }\n },\n\n config(userConfig) {\n // Auto-discover rollup input from useCss and useClientJs\n const projectRoot = userConfig.root || process.cwd();\n const discovered = discoverRollupInput(projectRoot);\n\n // Store island registries for use in other hooks\n islandRegistry = discovered.islandRegistry;\n pathToKey = discovered.pathToKey;\n keyToSrc = discovered.keyToSrc;\n actionRegistry = discovered.actionRegistry;\n\n // Merge with existing rollupOptions.input if any\n const existingInput = userConfig.build?.rollupOptions?.input || {};\n const mergedInput =\n typeof existingInput === 'object' && !Array.isArray(existingInput)\n ? { ...discovered.input, ...existingInput }\n : discovered.input;\n\n return {\n build: {\n ...userConfig.build,\n rollupOptions: {\n ...userConfig.build?.rollupOptions,\n input: Object.keys(mergedInput).length > 0 ? mergedInput : undefined,\n // Preserve exports for island component entries — without this,\n // Rollup tree-shakes their exports since nothing in the bundle imports them\n // (they're loaded at runtime via dynamic import from the island runtime).\n preserveEntrySignatures: 'exports-only',\n },\n },\n } satisfies UserConfig;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_L5E_VIEWS) {\n return '\\0' + VIRTUAL_L5E_VIEWS;\n }\n if (id === VIRTUAL_L5E_ROUTE) {\n return '\\0' + VIRTUAL_L5E_ROUTE;\n }\n if (id === VIRTUAL_L5E_SSR_ENTRY) {\n return '\\0' + VIRTUAL_L5E_SSR_ENTRY;\n }\n if (id === VIRTUAL_L5E_GLOBAL_LOADER) {\n return '\\0' + VIRTUAL_L5E_GLOBAL_LOADER;\n }\n if (id === VIRTUAL_L5E_ISLAND_STRATEGIES) {\n return '\\0' + VIRTUAL_L5E_ISLAND_STRATEGIES;\n }\n if (id === VIRTUAL_L5E_ISLANDS) {\n return '\\0' + VIRTUAL_L5E_ISLANDS;\n }\n if (id === VIRTUAL_L5E_ACTIONS) {\n return '\\0' + VIRTUAL_L5E_ACTIONS;\n }\n if (id === VIRTUAL_L5E_MIDDLEWARE) {\n return '\\0' + VIRTUAL_L5E_MIDDLEWARE;\n }\n return null;\n },\n\n async transform(code, id, options) {\n // Auto-inject island runtime into client.global.ts so it's always loaded globally\n if (id.replace(/\\\\/g, '/').endsWith('src/client.global.ts') && !options?.ssr) {\n return {\n code: `import '@withl5e/l5e/island/runtime';\\n${code}`,\n map: null,\n };\n }\n\n // Client-side action transform: replace defineAction exports with fetch stubs\n // Supports actions anywhere under src/ (e.g., src/views/*, src/features/*, etc.)\n if (!options?.ssr) {\n const normalizedId = id.replace(/\\\\/g, '/');\n const actionMatch = normalizedId.match(/\\/src\\/(.+)\\/actions\\.(ts|tsx)$/);\n if (actionMatch) {\n const modulePath = actionMatch[1];\n\n // Parse exported action names\n const exportRegex = /export\\s+const\\s+(\\w+)\\s*=\\s*defineAction\\s*\\(/g;\n const actions: Array<{ name: string; method: string }> = [];\n let m;\n while ((m = exportRegex.exec(code)) !== null) {\n const name = m[1];\n // Find method for this action — scan from the defineAction( position\n const chunk = code.substring(m.index, m.index + 500);\n const methodMatch = chunk.match(/method:\\s*['\"](\\w+)['\"]/);\n const method = methodMatch ? methodMatch[1].toUpperCase() : 'GET';\n actions.push({ name, method });\n }\n\n if (actions.length > 0) {\n const stubs = actions.map(({ name, method }) => {\n const actionKey = `${name}_${shortHash(modulePath)}`;\n if (method === 'GET') {\n return `export async function ${name}(params) {\n const res = await fetch('/_l5e/action/${actionKey}?' + new URLSearchParams(params));\n if (!res.ok) throw Object.assign(new Error('HTTP ' + res.status), { status: res.status });\n return res;\n}`;\n }\n return `export async function ${name}(body) {\n const res = await fetch('/_l5e/action/${actionKey}', {\n method: '${method}',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw Object.assign(new Error('HTTP ' + res.status), { status: res.status });\n return res;\n}`;\n });\n\n return {\n code: stubs.join('\\n\\n'),\n map: null,\n };\n }\n }\n }\n\n // Skip node_modules\n if (id.includes('node_modules')) {\n return null;\n }\n\n // Skip files in /react/ directories\n if (id.includes('/react/') || id.includes('\\\\react\\\\')) {\n return null;\n }\n\n // Check if this is a JSX/TSX file that needs L5E JSX transformation\n const isJsxFile = /\\.(tsx|jsx)$/.test(id);\n\n if (isJsxFile) {\n try {\n const injected = `import { Fragment as __Fragment, jsxFactory } from \"@withl5e/l5e/jsx-runtime\"\\n${code}`;\n\n // Transform JSX using esbuild with L5E's JSX runtime (async)\n const result = await transform(injected, {\n loader: id.endsWith('.tsx') ? 'tsx' : 'jsx',\n jsx: 'transform',\n jsxFactory: 'jsxFactory',\n jsxFragment: '__Fragment',\n sourcemap: true,\n sourcefile: id,\n target: 'es2020',\n });\n\n let transformedCode = result.code;\n\n // Inject __key and __src for ALL ClientIsland calls\n if (transformedCode.includes('ClientIsland')) {\n transformedCode = injectIslandKeys(transformedCode, id, rootDir, pathToKey, keyToSrc);\n }\n\n return {\n code: transformedCode,\n map: result.map || null,\n };\n } catch (error) {\n // Log error but let Vite handle it\n console.error(`[l5e] Failed to transform JSX in ${id}:`, error);\n throw error;\n }\n }\n\n // Transform import.meta.env thà nh process.env ở server side\n // Server side có thể access tất cả env variables\n // Client side chỉ access được VITE_ env variables\n // Transform nà y chạy ở runtime khi module được load trong SSR context\n if (options?.ssr) {\n let transformedCode = code;\n let hasChanges = false;\n\n // Thay thế import.meta.env.VARIABLE_NAME thà nh process.env.VARIABLE_NAME\n // Match: import.meta.env.VITE_EVENT_CATEGORY_SLUG\n transformedCode = transformedCode.replace(\n /import\\.meta\\.env\\.([a-zA-Z_][a-zA-Z0-9_]*)/g,\n (match, varName) => {\n // Leave Vite's built-in env vars for Vite to handle.\n if (VITE_RESERVED_ENV.has(varName)) return match;\n hasChanges = true;\n return `process.env.${varName}`;\n },\n );\n\n // Thay thế import.meta.env['VARIABLE_NAME'] hoặc import.meta.env[\"VARIABLE_NAME\"]\n // thà nh process.env['VARIABLE_NAME'] hoặc process.env[\"VARIABLE_NAME\"]\n // Match: import.meta.env['VITE_EVENT_CATEGORY_SLUG'] hoặc import.meta.env[\"VITE_EVENT_CATEGORY_SLUG\"]\n transformedCode = transformedCode.replace(\n /import\\.meta\\.env\\[(['\"`])([^'\"`]+)\\1\\]/g,\n (match, quote, varName) => {\n // Leave Vite's built-in env vars for Vite to handle.\n if (VITE_RESERVED_ENV.has(varName)) return match;\n hasChanges = true;\n return `process.env[${quote}${varName}${quote}]`;\n },\n );\n\n if (hasChanges) {\n return {\n code: transformedCode,\n map: null, // Không cần source map cho env transform\n };\n }\n }\n return null;\n },\n\n load(id) {\n // Virtual module: l5e-views\n if (id === '\\0' + VIRTUAL_L5E_VIEWS) {\n return `\nexport const viewLoaders = import.meta.glob('/src/views/*/loader.{ts,tsx}');\nexport const viewComponents = import.meta.glob('/src/views/*/index.tsx');\n`;\n }\n\n // Virtual module: l5e-route\n if (id === '\\0' + VIRTUAL_L5E_ROUTE) {\n // Always use TypeScript\n return `export { default } from '/src/route.ts';`;\n }\n\n // Virtual module: l5e-ssr-entry\n if (id === '\\0' + VIRTUAL_L5E_SSR_ENTRY) {\n return `export { render } from '@withl5e/l5e/entry-server';\nexport { viewActions } from 'virtual:l5e-actions';`;\n }\n\n // Virtual module: l5e-global-loader\n if (id === '\\0' + VIRTUAL_L5E_GLOBAL_LOADER) {\n return `export const globalLoader = import.meta.glob('/src/global-loader.{ts,tsx}', { eager: false });`;\n }\n\n // Virtual module: l5e-actions\n if (id === '\\0' + VIRTUAL_L5E_ACTIONS) {\n const registryObj = Object.fromEntries(actionRegistry);\n return `export const viewActions = import.meta.glob('/src/**/actions.{ts,tsx}');\nexport const actionRegistry = ${JSON.stringify(registryObj)};`;\n }\n\n // Virtual module: l5e-middleware\n if (id === '\\0' + VIRTUAL_L5E_MIDDLEWARE) {\n return `\nconst middlewareModules = import.meta.glob([\n '/src/middleware.{ts,tsx,js,jsx}',\n '/src/middleware/index.{ts,tsx,js,jsx}',\n]);\n\nconst middlewarePaths = [\n '/src/middleware.ts',\n '/src/middleware.tsx',\n '/src/middleware.js',\n '/src/middleware.jsx',\n '/src/middleware/index.ts',\n '/src/middleware/index.tsx',\n '/src/middleware/index.js',\n '/src/middleware/index.jsx',\n];\n\nexport async function loadMiddleware() {\n const middlewarePath = middlewarePaths.find((path) => middlewareModules[path]);\n if (!middlewarePath) return undefined;\n\n const mod = await middlewareModules[middlewarePath]();\n return mod.onRequest;\n}\n`;\n }\n\n // Virtual module: l5e-islands\n // Lazy glob of all React island components so the SSR entry can import a\n // component by its __src path and renderToString() it. Non-eager → only\n // islands actually present on a page (with ssr) get imported at runtime.\n if (id === '\\0' + VIRTUAL_L5E_ISLANDS) {\n return `export const islandModules = import.meta.glob('/src/**/react/*.{tsx,jsx}');`;\n }\n\n // Virtual module: l5e-island-strategies\n if (id === '\\0' + VIRTUAL_L5E_ISLAND_STRATEGIES) {\n // Check if src/island-strategies.ts exists\n const strategiesFile = join(rootDir, 'src', 'island-strategies.ts');\n if (existsSync(strategiesFile)) {\n // Re-export user's file → Vite will build this file\n return `import '/src/island-strategies.ts';`;\n }\n // No file → empty module, no error\n return `/* no custom island strategies */`;\n }\n\n return null;\n },\n };\n}\n\nexport default coreVite;\n"],"names":["VIRTUAL_L5E_VIEWS","VIRTUAL_L5E_ROUTE","VIRTUAL_L5E_SSR_ENTRY","VIRTUAL_L5E_GLOBAL_LOADER","VIRTUAL_L5E_ISLAND_STRATEGIES","VIRTUAL_L5E_ISLANDS","VIRTUAL_L5E_ACTIONS","VIRTUAL_L5E_MIDDLEWARE","VITE_RESERVED_ENV","scanTsFiles","dir","fileList","files","readdirSync","file","filePath","join","statSync","shortHash","str","hash","i","deriveComponentName","fromPath","segments","filename","makeIslandKey","resolvedPath","resolveWithExtension","rootDir","relPath","absBase","resolve","ext","existsSync","extractIslandEntries","content","readFileSync","entries","regex","seen","match","abs","relative","src","key","extractPathsFromFile","css","js","useCssPattern","useClientJsPattern","path","absolutePath","relativePath","discoverRollupInput","input","srcDir","islandRegistry","pathToKey","keyToSrc","actionRegistry","globalTsPath","tsFiles","allCssPaths","allJsPaths","islandEntries","entry","normalizedFile","actionExportRegex","actionMatch","modulePath","dirname","actionName","actionKey","cssPath","entryName","jsPath","error","injectIslandKeys","code","fileId","result","lastIndex","anchorRegex","anchorMatch","searchStart","window","fromMatch","resolved","insertPos","coreVite","resolvedConfig","server","mod","tempDir","rmSync","_options","_bundle","outDir","mkdirSync","registryObj","writeFileSync","userConfig","projectRoot","discovered","existingInput","mergedInput","id","options","exportRegex","actions","m","name","methodMatch","method","injected","transform","transformedCode","hasChanges","varName","quote","strategiesFile"],"mappings":";;;AAaA,MAAMA,IAAoB,qBACpBC,IAAoB,qBACpBC,IAAwB,yBACxBC,IAA4B,6BAC5BC,IAAgC,iCAChCC,IAAsB,uBACtBC,IAAsB,uBACtBC,IAAyB,0BASzBC,IAAoB,oBAAI,IAAI,CAAC,QAAQ,YAAY,QAAQ,OAAO,OAAO,QAAQ,CAAC;AAKtF,SAASC,EAAYC,GAAaC,IAAqB,IAAc;AACnE,QAAMC,IAAQC,EAAYH,CAAG;AAE7B,aAAWI,KAAQF,GAAO;AACxB,UAAMG,IAAWC,EAAKN,GAAKI,CAAI;AAG/B,QAFaG,EAASF,CAAQ,EAErB,eAAe;AAEtB,UAAID,MAAS,kBAAkBA,MAAS,UAAUA,MAAS;AACzD;AAEF,MAAAL,EAAYM,GAAUJ,CAAQ;AAAA,IAChC,MAAA,EAAWG,EAAK,SAAS,MAAM,KAAKA,EAAK,SAAS,KAAK,MACrDH,EAAS,KAAKI,CAAQ;AAAA,EAE1B;AAEA,SAAOJ;AACT;AAKA,SAASO,EAAUC,GAAqB;AACtC,MAAIC,IAAO;AACX,WAASC,IAAI,GAAGA,IAAIF,EAAI,QAAQE;AAC9B,IAAAD,KAAQA,KAAQ,KAAKA,IAAOD,EAAI,WAAWE,CAAC,GAC5CD,IAAOA,IAAOA;AAEhB,SAAO,KAAK,IAAIA,CAAI,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC;AAC/C;AAKA,SAASE,EAAoBC,GAA0B;AACrD,QAAMC,IAAWD,EAAS,MAAM,GAAG;AACnC,MAAIE,IAAWD,EAASA,EAAS,SAAS,CAAC;AAE3C,SAAAC,IAAWA,EAAS,QAAQ,kBAAkB,EAAE,GACzCA;AACT;AAKA,SAASC,EAAcC,GAA8B;AAEnD,SAAO,GADML,EAAoBK,CAAY,CAC/B,IAAIT,EAAUS,CAAY,CAAC;AAC3C;AAOA,SAASC,EAAqBD,GAAsBE,GAAgC;AAElF,QAAMC,IAAUH,EAAa,QAAQ,OAAO,EAAE,GACxCI,IAAUC,EAAQH,GAASC,CAAO;AAGxC,aAAWG,KAAO,CAAC,QAAQ,OAAO,QAAQ,KAAK;AAC7C,QAAIC,EAAWH,IAAUE,CAAG;AAC1B,aAAOH,IAAUG;AAIrB,SAAIC,EAAWH,CAAO,IACbD,IAEF;AACT;AAKA,SAASK,EACPpB,GACAc,GAC8E;AAC9E,QAAMO,IAAUC,EAAatB,GAAU,OAAO,GACxCuB,IAAwF,CAAA,GAIxFC,IAAQ,8CAERC,wBAAW,IAAA;AACjB,MAAIC;AAEJ,UAAQA,IAAQF,EAAM,KAAKH,CAAO,OAAO,QAAM;AAC7C,UAAMb,IAAWkB,EAAM,CAAC;AAGxB,QAAId;AACJ,QAAIJ,EAAS,WAAW,IAAI;AAE1B,MAAAI,IAAe,UAAUJ,EAAS,UAAU,CAAC;AAAA,aACpCA,EAAS,WAAW,OAAO;AACpC,MAAAI,IAAeJ;AAAA,aACNA,EAAS,WAAW,IAAI,KAAKA,EAAS,WAAW,KAAK,GAAG;AAClE,YAAMmB,IAAMV,EAAQjB,GAAU,MAAMQ,CAAQ;AAC5C,MAAAI,IAAe,MAAMgB,EAASd,GAASa,CAAG,EAAE,QAAQ,OAAO,GAAG;AAAA,IAChE;AACE,MAAAf,IAAeJ;AAIjB,UAAMqB,IAAMhB,EAAqBD,GAAcE,CAAO;AACtD,QAAI,CAACe,GAAK;AACR,cAAQ;AAAA,QACN,qCAAqCjB,CAAY,UAAUJ,CAAQ,OAAOR,CAAQ;AAAA,MAAA;AAEpF;AAAA,IACF;AAEA,UAAM8B,IAAMnB,EAAcC,CAAY;AACtC,IAAKa,EAAK,IAAIK,CAAG,MACfL,EAAK,IAAIK,CAAG,GACZP,EAAQ,KAAK;AAAA,MACX,WAAWhB,EAAoBK,CAAY;AAAA,MAC3C,cAAAA;AAAA,MACA,KAAAiB;AAAA;AAAA,MACA,KAAAC;AAAA,IAAA,CACD;AAAA,EAEL;AACA,SAAOP;AACT;AAKA,SAASQ,EAAqB/B,GAAkBc,GAAkD;AAChG,QAAMO,IAAUC,EAAatB,GAAU,OAAO,GACxCgC,IAAgB,CAAA,GAChBC,IAAe,CAAA,GAIfC,IAAgB,6CAChBC,IAAqB;AAE3B,MAAIT;AAGJ,UAAQA,IAAQQ,EAAc,KAAKb,CAAO,OAAO,QAAM;AACrD,UAAMe,IAAOV,EAAM,CAAC;AAEpB,QAAIU,EAAK,WAAW,OAAO;AACzB,MAAAJ,EAAI,KAAKI,CAAI;AAAA,aACJA,EAAK,WAAW,IAAI,KAAKA,EAAK,WAAW,KAAK,GAAG;AAE1D,YAAMC,IAAepB,EAAQjB,GAAU,MAAMoC,CAAI,GAC3CE,IAAe,MAAMV,EAASd,GAASuB,CAAY,EAAE,QAAQ,OAAO,GAAG;AAC7E,MAAAL,EAAI,KAAKM,CAAY;AAAA,IACvB;AACE,MAAAN,EAAI,KAAKI,CAAI;AAAA,EAEjB;AAGA,UAAQV,IAAQS,EAAmB,KAAKd,CAAO,OAAO,QAAM;AAC1D,UAAMe,IAAOV,EAAM,CAAC;AACpB,QAAIU,EAAK,WAAW,OAAO;AACzB,MAAAH,EAAG,KAAKG,CAAI;AAAA,aACHA,EAAK,WAAW,IAAI,KAAKA,EAAK,WAAW,KAAK,GAAG;AAC1D,YAAMC,IAAepB,EAAQjB,GAAU,MAAMoC,CAAI,GAC3CE,IAAe,MAAMV,EAASd,GAASuB,CAAY,EAAE,QAAQ,OAAO,GAAG;AAC7E,MAAAJ,EAAG,KAAKK,CAAY;AAAA,IACtB;AACE,MAAAL,EAAG,KAAKG,CAAI;AAAA,EAEhB;AAEA,SAAO,EAAE,KAAAJ,GAAK,IAAAC,EAAA;AAChB;AAKA,SAASM,EAAoBzB,GAM3B;AACA,QAAM0B,IAAgC,CAAA,GAChCC,IAASxC,EAAKa,GAAS,KAAK,GAG5B4B,wBAAqB,IAAA,GACrBC,wBAAgB,IAAA,GAChBC,wBAAe,IAAA,GAGfC,wBAAqB,IAAA;AAE3B,MAAI;AAEF,QAAI,CAAC3C,EAASuC,CAAM,EAAE;AACpB,aAAO,EAAE,OAAAD,GAAO,gBAAAE,GAAgB,WAAAC,GAAW,UAAAC,GAAU,gBAAAC,EAAA;AAIvD,UAAMC,IAAe7C,EAAKa,GAAS,OAAO,kBAAkB;AAC5D,IAAIK,EAAW2B,CAAY,MACzBN,EAAM,SAAYM,GAClB,QAAQ,IAAI,wDAAwD;AAItE,UAAMC,IAAUrD,EAAY+C,CAAM,GAG5BO,wBAAkB,IAAA,GAClBC,wBAAiB,IAAA;AAEvB,eAAWlD,KAAQgD,GAAS;AAC1B,YAAM,EAAE,KAAAf,GAAK,IAAAC,EAAA,IAAOF,EAAqBhC,GAAMe,CAAO;AACtD,MAAAkB,EAAI,QAAQ,CAACI,MAASY,EAAY,IAAIZ,CAAI,CAAC,GAC3CH,EAAG,QAAQ,CAACG,MAASa,EAAW,IAAIb,CAAI,CAAC;AAGzC,YAAMc,IAAgB9B,EAAqBrB,GAAMe,CAAO;AACxD,iBAAWqC,KAASD;AAClB,QAAAR,EAAe,IAAIS,EAAM,KAAKA,EAAM,YAAY,GAChDR,EAAU,IAAIQ,EAAM,cAAcA,EAAM,GAAG,GAC3CP,EAAS,IAAIO,EAAM,KAAKA,EAAM,GAAG;AAInC,YAAMC,IAAiBrD,EAAK,QAAQ,OAAO,GAAG;AAC9C,UAAI,uBAAuB,KAAKqD,CAAc,GAAG;AAC/C,cAAM/B,IAAUC,EAAavB,GAAM,OAAO,GACpCsD,IAAoB;AAC1B,YAAIC;AAIJ,cAAMC,IADa3B,EAASa,GAAQe,EAAQzD,CAAI,CAAC,EAAE,QAAQ,OAAO,GAAG,KACpC;AAEjC,gBAAQuD,IAAcD,EAAkB,KAAKhC,CAAO,OAAO,QAAM;AAC/D,gBAAMoC,IAAaH,EAAY,CAAC,GAC1BI,IAAY,GAAGD,CAAU,IAAItD,EAAUoD,CAAU,CAAC;AACxD,UAAAV,EAAe,IAAIa,GAAW,EAAE,YAAAH,GAAY,YAAAE,GAAY;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAIA,eAAWE,KAAWX,GAAa;AAEjC,YAAMV,IAAeqB,EAAQ,WAAW,OAAO,KAE3CA,EAAQ,WAAW,GAAG,IADtBA,EAAQ,UAAU,CAAC,IAGjBA,GAEAtB,IAAepB,EAAQH,GAASwB,CAAY;AAGlD,UAAI,CAACnB,EAAWkB,CAAY,GAAG;AAC7B,gBAAQ,KAAK,6BAA6BA,CAAY,UAAUsB,CAAO,GAAG;AAC1E;AAAA,MACF;AAGA,YAAMC,IAAYtB,EACf,QAAQ,UAAU,EAAE,EACpB,QAAQ,UAAU,EAAE,EACpB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AAErB,MAAAE,EAAMoB,CAAS,IAAIvB;AAAA,IACrB;AAGA,eAAWwB,KAAUZ,GAAY;AAC/B,YAAMX,IAAeuB,EAAO,WAAW,OAAO,KAE1CA,EAAO,WAAW,GAAG,IADrBA,EAAO,UAAU,CAAC,IAGhBA,GAEAxB,IAAepB,EAAQH,GAASwB,CAAY;AAGlD,UAAI,CAACnB,EAAWkB,CAAY,GAAG;AAC7B,gBAAQ,KAAK,+BAA+BA,CAAY,UAAUwB,CAAM,GAAG;AAC3E;AAAA,MACF;AAGA,YAAMD,IAAYtB,EACf,QAAQ,UAAU,EAAE,EACpB,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AAErB,MAAAE,EAAMoB,CAAS,IAAIvB;AAAA,IACrB;AAIA,eAAW,CAACP,GAAKD,CAAG,KAAKe,GAAU;AACjC,YAAMP,IAAepB,EAAQH,GAASe,CAAG;AACzC,MAAAW,EAAM,UAAUV,CAAG,EAAE,IAAIO;AAAA,IAC3B;AAEA,IAAIK,EAAe,OAAO,KACxB,QAAQ,IAAI,kBAAkBA,EAAe,IAAI,YAAY,GAE3DG,EAAe,OAAO,KACxB,QAAQ,IAAI,kBAAkBA,EAAe,IAAI,YAAY;AAAA,EAEjE,SAASiB,GAAO;AAEd,YAAQ,KAAK,0CAA0CA,CAAK;AAAA,EAC9D;AAEA,SAAO,EAAE,OAAAtB,GAAO,gBAAAE,GAAgB,WAAAC,GAAW,UAAAC,GAAU,gBAAAC,EAAA;AACvD;AAMA,SAASkB,EACPC,GACAC,GACAnD,GACA6B,GACAC,GACQ;AAKR,QAAMsB,IAAmB,CAAA;AACzB,MAAIC,IAAY;AAGhB,QAAMC,IAAc;AACpB,MAAIC;AAEJ,UAAQA,IAAcD,EAAY,KAAKJ,CAAI,OAAO,QAAM;AACtD,UAAMM,IAAcD,EAAY,QAAQA,EAAY,CAAC,EAAE,QAGjDE,IAASP,EAAK,UAAUM,GAAaA,IAAc,GAAG,GACtDE,IAAY,oBAAoB,KAAKD,CAAM;AAEjD,QAAI,CAACC,EAAW;AAEhB,UAAMhE,IAAWgE,EAAU,CAAC;AAG5B,QAAIC;AACJ,QAAIjE,EAAS,WAAW,IAAI;AAC1B,MAAAiE,IAAW,UAAUjE,EAAS,UAAU,CAAC;AAAA,aAChCA,EAAS,WAAW,OAAO;AACpC,MAAAiE,IAAWjE;AAAA,aACFA,EAAS,WAAW,IAAI,KAAKA,EAAS,WAAW,KAAK,GAAG;AAClE,YAAMmB,IAAMV,EAAQgD,GAAQ,MAAMzD,CAAQ;AAC1C,MAAAiE,IAAW,MAAM7C,EAASd,GAASa,CAAG,EAAE,QAAQ,OAAO,GAAG;AAAA,IAC5D;AACE,MAAA8C,IAAWjE;AAGb,UAAMsB,IAAMa,EAAU,IAAI8B,CAAQ;AAClC,QAAI,CAAC3C,EAAK;AAEV,UAAMD,IAAMe,EAAS,IAAId,CAAG;AAC5B,QAAI,CAACD,EAAK;AAGV,UAAM6C,IAAYJ,IAAcE,EAAU,QAAQA,EAAU,CAAC,EAAE;AAC/D,IAAAN,EAAO,KAAKF,EAAK,UAAUG,GAAWO,CAAS,CAAC,GAChDR,EAAO,KAAK,aAAapC,CAAG,cAAcD,CAAG,GAAG,GAChDsC,IAAYO;AAAA,EACd;AAEA,SAAAR,EAAO,KAAKF,EAAK,UAAUG,CAAS,CAAC,GAC9BD,EAAO,KAAK,EAAE;AACvB;AAEO,SAASS,KAAmB;AACjC,MAAI7D,IAAkB,QAAQ,IAAA,GAE1B6B,wBAAgB,IAAA,GAChBC,wBAAe,IAAA,GACfC,wBAAqB,IAAA;AAEzB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IAET,eAAe+B,GAAgB;AAE7B,MAAA9D,IAAU8D,EAAe,QAAQ,QAAQ,IAAA;AAAA,IAC3C;AAAA,IAEA,gBAAgB,EAAE,MAAA7E,GAAM,QAAA8E,KAAU;AAChC,YAAM9D,IAAUa,EAASd,GAASf,CAAI;AAGtC,UAAI,qBAAqB,KAAKA,CAAI,GAAG;AAEnC,QAAA8C,IADmBN,EAAoBzB,CAAO,EAClB;AAE5B,cAAMgE,IAAMD,EAAO,YAAY,cAAc,OAAOtF,CAAmB;AACvE,QAAIuF,KACFD,EAAO,YAAY,iBAAiBC,CAAG,GAEzC,QAAQ;AAAA,UACN,8BAA8B/D,CAAO,mBAAmB8B,EAAe,IAAI;AAAA,QAAA;AAAA,MAE/E;AAGA,UAAI9C,EAAK,SAAS,MAAM,GAAG;AACzB,gBAAQ,IAAI,sBAAsBgB,CAAO,mBAAmB;AAC5D;AAAA,MACF;AAEA,UAAIhB,EAAK,SAAS,WAAW,GAAG;AAC9B,gBAAQ,IAAI,4BAA4BgB,CAAO,mBAAmB;AAClE;AAAA,MACF;AAIA,UAAIhB,EAAK,SAAS,OAAO,KAAKA,EAAK,SAAS,SAAS;AACnD,uBAAQ,IAAI,2BAA2BgB,CAAO,2BAA2B,GACzE8D,EAAO,GAAG,KAAK;AAAA,UACb,MAAM;AAAA,UACN,MAAM;AAAA,QAAA,CACP,GACM,CAAA;AAAA,IAEX;AAAA,IAEA,WAAW;AAET,YAAME,IAAU9E,EAAKa,GAAS,WAAW;AACzC,UAAIK,EAAW4D,CAAO;AACpB,YAAI;AACF,UAAAC,EAAOD,GAAS,EAAE,WAAW,IAAM,OAAO,IAAM,GAChD,QAAQ,IAAI,kCAAkC;AAAA,QAChD,SAASjB,GAAO;AACd,kBAAQ,KAAK,4CAA4CA,CAAK;AAAA,QAChE;AAAA,IAEJ;AAAA,IAEA,YAAYmB,GAAUC,GAAS;AAE7B,UAAIrC,EAAe,OAAO,GAAG;AAC3B,cAAMsC,IAASlF,EAAKa,GAAS,QAAQ,QAAQ;AAC7C,QAAKK,EAAWgE,CAAM,KACpBC,EAAUD,GAAQ,EAAE,WAAW,GAAA,CAAM;AAEvC,cAAME,IAAc,OAAO,YAAYxC,CAAc;AACrD,QAAAyC,EAAcrF,EAAKkF,GAAQ,sBAAsB,GAAG,KAAK,UAAUE,CAAW,GAAG,OAAO,GACxF,QAAQ,IAAI,qCAAqCxC,EAAe,IAAI,WAAW;AAAA,MACjF;AAAA,IACF;AAAA,IAEA,OAAO0C,GAAY;AAEjB,YAAMC,IAAcD,EAAW,QAAQ,QAAQ,IAAA,GACzCE,IAAalD,EAAoBiD,CAAW;AAGjC,MAAAC,EAAW,gBAC5B9C,IAAY8C,EAAW,WACvB7C,IAAW6C,EAAW,UACtB5C,IAAiB4C,EAAW;AAG5B,YAAMC,IAAgBH,EAAW,OAAO,eAAe,SAAS,CAAA,GAC1DI,IACJ,OAAOD,KAAkB,YAAY,CAAC,MAAM,QAAQA,CAAa,IAC7D,EAAE,GAAGD,EAAW,OAAO,GAAGC,EAAA,IAC1BD,EAAW;AAEjB,aAAO;AAAA,QACL,OAAO;AAAA,UACL,GAAGF,EAAW;AAAA,UACd,eAAe;AAAA,YACb,GAAGA,EAAW,OAAO;AAAA,YACrB,OAAO,OAAO,KAAKI,CAAW,EAAE,SAAS,IAAIA,IAAc;AAAA;AAAA;AAAA;AAAA,YAI3D,yBAAyB;AAAA,UAAA;AAAA,QAC3B;AAAA,MACF;AAAA,IAEJ;AAAA,IAEA,UAAUC,GAAI;AACZ,aAAIA,MAAO3G,IACF,OAAOA,IAEZ2G,MAAO1G,IACF,OAAOA,IAEZ0G,MAAOzG,IACF,OAAOA,IAEZyG,MAAOxG,IACF,OAAOA,IAEZwG,MAAOvG,IACF,OAAOA,IAEZuG,MAAOtG,IACF,OAAOA,IAEZsG,MAAOrG,IACF,OAAOA,IAEZqG,MAAOpG,IACF,OAAOA,IAET;AAAA,IACT;AAAA,IAEA,MAAM,UAAUwE,GAAM4B,GAAIC,GAAS;AAEjC,UAAID,EAAG,QAAQ,OAAO,GAAG,EAAE,SAAS,sBAAsB,KAAK,CAACC,GAAS;AACvE,eAAO;AAAA,UACL,MAAM;AAAA,EAA0C7B,CAAI;AAAA,UACpD,KAAK;AAAA,QAAA;AAMT,UAAI,CAAC6B,GAAS,KAAK;AAEjB,cAAMvC,IADesC,EAAG,QAAQ,OAAO,GAAG,EACT,MAAM,iCAAiC;AACxE,YAAItC,GAAa;AACf,gBAAMC,IAAaD,EAAY,CAAC,GAG1BwC,IAAc,mDACdC,IAAmD,CAAA;AACzD,cAAIC;AACJ,kBAAQA,IAAIF,EAAY,KAAK9B,CAAI,OAAO,QAAM;AAC5C,kBAAMiC,IAAOD,EAAE,CAAC,GAGVE,IADQlC,EAAK,UAAUgC,EAAE,OAAOA,EAAE,QAAQ,GAAG,EACzB,MAAM,yBAAyB,GACnDG,IAASD,IAAcA,EAAY,CAAC,EAAE,gBAAgB;AAC5D,YAAAH,EAAQ,KAAK,EAAE,MAAAE,GAAM,QAAAE,EAAA,CAAQ;AAAA,UAC/B;AAEA,cAAIJ,EAAQ,SAAS;AAqBnB,mBAAO;AAAA,cACL,MArBYA,EAAQ,IAAI,CAAC,EAAE,MAAAE,GAAM,QAAAE,QAAa;AAC9C,sBAAMzC,IAAY,GAAGuC,CAAI,IAAI9F,EAAUoD,CAAU,CAAC;AAClD,uBAAI4C,MAAW,QACN,yBAAyBF,CAAI;AAAA,0CACVvC,CAAS;AAAA;AAAA;AAAA,KAK9B,yBAAyBuC,CAAI;AAAA,0CACRvC,CAAS;AAAA,eACpCyC,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOT,CAAC,EAGa,KAAK;AAAA;AAAA,CAAM;AAAA,cACvB,KAAK;AAAA,YAAA;AAAA,QAGX;AAAA,MACF;AAQA,UALIP,EAAG,SAAS,cAAc,KAK1BA,EAAG,SAAS,SAAS,KAAKA,EAAG,SAAS,WAAW;AACnD,eAAO;AAMT,UAFkB,eAAe,KAAKA,CAAE;AAGtC,YAAI;AACF,gBAAMQ,IAAW;AAAA,EAAkFpC,CAAI,IAGjGE,IAAS,MAAMmC,EAAUD,GAAU;AAAA,YACvC,QAAQR,EAAG,SAAS,MAAM,IAAI,QAAQ;AAAA,YACtC,KAAK;AAAA,YACL,YAAY;AAAA,YACZ,aAAa;AAAA,YACb,WAAW;AAAA,YACX,YAAYA;AAAA,YACZ,QAAQ;AAAA,UAAA,CACT;AAED,cAAIU,IAAkBpC,EAAO;AAG7B,iBAAIoC,EAAgB,SAAS,cAAc,MACzCA,IAAkBvC,EAAiBuC,GAAiBV,GAAI9E,GAAS6B,GAAWC,CAAQ,IAG/E;AAAA,YACL,MAAM0D;AAAA,YACN,KAAKpC,EAAO,OAAO;AAAA,UAAA;AAAA,QAEvB,SAASJ,GAAO;AAEd,wBAAQ,MAAM,oCAAoC8B,CAAE,KAAK9B,CAAK,GACxDA;AAAA,QACR;AAOF,UAAI+B,GAAS,KAAK;AAChB,YAAIS,IAAkBtC,GAClBuC,IAAa;AA2BjB,YAvBAD,IAAkBA,EAAgB;AAAA,UAChC;AAAA,UACA,CAAC5E,GAAO8E,MAEF/G,EAAkB,IAAI+G,CAAO,IAAU9E,KAC3C6E,IAAa,IACN,eAAeC,CAAO;AAAA,QAC/B,GAMFF,IAAkBA,EAAgB;AAAA,UAChC;AAAA,UACA,CAAC5E,GAAO+E,GAAOD,MAET/G,EAAkB,IAAI+G,CAAO,IAAU9E,KAC3C6E,IAAa,IACN,eAAeE,CAAK,GAAGD,CAAO,GAAGC,CAAK;AAAA,QAC/C,GAGEF;AACF,iBAAO;AAAA,YACL,MAAMD;AAAA,YACN,KAAK;AAAA;AAAA,UAAA;AAAA,MAGX;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAKV,GAAI;AAEP,UAAIA,MAAO,OAAO3G;AAChB,eAAO;AAAA;AAAA;AAAA;AAOT,UAAI2G,MAAO,OAAO1G;AAEhB,eAAO;AAIT,UAAI0G,MAAO,OAAOzG;AAChB,eAAO;AAAA;AAKT,UAAIyG,MAAO,OAAOxG;AAChB,eAAO;AAIT,UAAIwG,MAAO,OAAOrG,GAAqB;AACrC,cAAM8F,IAAc,OAAO,YAAYxC,CAAc;AACrD,eAAO;AAAA,gCACiB,KAAK,UAAUwC,CAAW,CAAC;AAAA,MACrD;AAGA,UAAIO,MAAO,OAAOpG;AAChB,eAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BT,UAAIoG,MAAO,OAAOtG;AAChB,eAAO;AAIT,UAAIsG,MAAO,OAAOvG,GAA+B;AAE/C,cAAMqH,IAAiBzG,EAAKa,GAAS,OAAO,sBAAsB;AAClE,eAAIK,EAAWuF,CAAc,IAEpB,wCAGF;AAAA,MACT;AAEA,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withl5e/l5e",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "HTML-first SSR MPA framework with loaders, middleware, islands, actions, swap, SEO and cache controls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,66 +16,82 @@
|
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
|
+
"development": "./src/core/index.ts",
|
|
19
20
|
"types": "./src/core/index.ts",
|
|
20
21
|
"default": "./dist/index.js"
|
|
21
22
|
},
|
|
22
23
|
"./jsx-runtime": {
|
|
24
|
+
"development": "./src/core/jsx-runtime.ts",
|
|
23
25
|
"types": "./src/core/jsx-runtime.ts",
|
|
24
26
|
"default": "./dist/jsx-runtime.js"
|
|
25
27
|
},
|
|
26
28
|
"./vite-plugin": {
|
|
29
|
+
"development": "./src/core/vite-plugin.ts",
|
|
27
30
|
"types": "./src/core/vite-plugin.ts",
|
|
28
31
|
"default": "./dist/vite-plugin.js"
|
|
29
32
|
},
|
|
30
33
|
"./server": {
|
|
34
|
+
"development": "./src/core/server.ts",
|
|
31
35
|
"types": "./src/core/server.ts",
|
|
32
36
|
"default": "./dist/server.js"
|
|
33
37
|
},
|
|
34
38
|
"./entry-server": {
|
|
39
|
+
"development": "./src/core/entry-server.ts",
|
|
35
40
|
"types": "./src/core/entry-server.d.ts",
|
|
36
41
|
"default": "./dist/entry-server.js"
|
|
37
42
|
},
|
|
38
43
|
"./middleware": {
|
|
44
|
+
"development": "./src/middleware/index.ts",
|
|
39
45
|
"types": "./src/middleware/index.ts",
|
|
40
46
|
"default": "./dist/middleware.js"
|
|
41
47
|
},
|
|
42
48
|
"./tooltip": {
|
|
49
|
+
"development": "./src/tooltip/index.ts",
|
|
43
50
|
"types": "./src/tooltip/index.ts",
|
|
44
51
|
"default": "./dist/tooltip.js"
|
|
45
52
|
},
|
|
46
53
|
"./jsx-types": {
|
|
54
|
+
"development": "./src/core/jsx-types.d.ts",
|
|
47
55
|
"types": "./src/core/jsx-types.d.ts",
|
|
48
56
|
"default": "./src/core/jsx-types.d.ts"
|
|
49
57
|
},
|
|
50
58
|
"./seo": {
|
|
59
|
+
"development": "./src/seo/index.ts",
|
|
51
60
|
"types": "./src/seo/index.ts",
|
|
52
61
|
"default": "./dist/seo.js"
|
|
53
62
|
},
|
|
54
63
|
"./swap": {
|
|
64
|
+
"development": "./src/swap/index.ts",
|
|
55
65
|
"types": "./src/swap/index.ts",
|
|
56
66
|
"default": "./dist/swap.js"
|
|
57
67
|
},
|
|
58
68
|
"./swap/server": {
|
|
69
|
+
"development": "./src/swap/server.ts",
|
|
59
70
|
"types": "./src/swap/server.ts",
|
|
60
71
|
"default": "./dist/swap/server.js"
|
|
61
72
|
},
|
|
62
73
|
"./action": {
|
|
74
|
+
"development": "./src/action/index.ts",
|
|
63
75
|
"types": "./src/action/index.ts",
|
|
64
76
|
"default": "./dist/action.js"
|
|
65
77
|
},
|
|
66
78
|
"./island": {
|
|
79
|
+
"development": "./src/island/index.ts",
|
|
67
80
|
"types": "./src/island/index.ts",
|
|
68
81
|
"default": "./dist/island.js"
|
|
69
82
|
},
|
|
70
83
|
"./island/client": {
|
|
84
|
+
"development": "./src/island/client.ts",
|
|
71
85
|
"types": "./src/island/client.ts",
|
|
72
86
|
"default": "./dist/island/client.js"
|
|
73
87
|
},
|
|
74
88
|
"./island/runtime": {
|
|
89
|
+
"development": "./src/island/runtime.ts",
|
|
75
90
|
"types": "./src/island/runtime.ts",
|
|
76
91
|
"default": "./dist/island/runtime.js"
|
|
77
92
|
},
|
|
78
93
|
"./router": {
|
|
94
|
+
"development": "./src/router/index.ts",
|
|
79
95
|
"types": "./src/router/index.ts",
|
|
80
96
|
"default": "./dist/router.js"
|
|
81
97
|
}
|
package/src/core/entry-server.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getHeadContent,
|
|
19
19
|
getIslandEntries,
|
|
20
20
|
getSchemas,
|
|
21
|
+
getSsrIslands,
|
|
21
22
|
jsxFactory as h,
|
|
22
23
|
Head,
|
|
23
24
|
pushMetadata,
|
|
@@ -34,6 +35,69 @@ import routeHandler from 'virtual:l5e-route';
|
|
|
34
35
|
import { globalLoader } from 'virtual:l5e-global-loader';
|
|
35
36
|
// @ts-ignore - Virtual modules provided by Vite plugin
|
|
36
37
|
export { loadMiddleware } from 'virtual:l5e-middleware';
|
|
38
|
+
// @ts-ignore - Virtual modules provided by Vite plugin
|
|
39
|
+
import { islandModules } from 'virtual:l5e-islands';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fill `ssr` island placeholders with server-rendered HTML.
|
|
43
|
+
*
|
|
44
|
+
* During the synchronous render pass, each `<ClientIsland ssr>` emits a unique
|
|
45
|
+
* token (as `<!--token-->` body + `data-island-ssr="token"`). Here — after the
|
|
46
|
+
* pass — we can `await import()` the actual React component and renderToString it,
|
|
47
|
+
* then string-replace the token. Only islands present on this page are imported.
|
|
48
|
+
*
|
|
49
|
+
* On failure we strip both the token and the `data-island-ssr` attribute so the
|
|
50
|
+
* client cleanly falls back to a client-only mount (no hydration mismatch).
|
|
51
|
+
*/
|
|
52
|
+
async function fillSsrIslands(htmlBody: string): Promise<string> {
|
|
53
|
+
const pending = getSsrIslands();
|
|
54
|
+
if (pending.length === 0) return htmlBody;
|
|
55
|
+
|
|
56
|
+
let renderToString: (el: any) => string;
|
|
57
|
+
let createElement: (type: any, props: any) => any;
|
|
58
|
+
try {
|
|
59
|
+
[{ renderToString }, { createElement }] = await Promise.all([
|
|
60
|
+
import('react-dom/server'),
|
|
61
|
+
import('react'),
|
|
62
|
+
]);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error('[l5e-island] SSR requires react-dom/server + react:', err);
|
|
65
|
+
// Strip every pending token/attr → client-only fallback for all.
|
|
66
|
+
for (const island of pending) {
|
|
67
|
+
htmlBody = htmlBody
|
|
68
|
+
.replace(`<!--${island.token}-->`, '')
|
|
69
|
+
.replace(` data-island-ssr="${island.token}"`, '');
|
|
70
|
+
}
|
|
71
|
+
return htmlBody;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const island of pending) {
|
|
75
|
+
try {
|
|
76
|
+
const loader = islandModules['/' + island.src];
|
|
77
|
+
if (!loader) {
|
|
78
|
+
throw new Error(`island module not found in glob: /${island.src}`);
|
|
79
|
+
}
|
|
80
|
+
const mod: any = await loader();
|
|
81
|
+
const Component = mod[island.name] ?? mod.default;
|
|
82
|
+
if (!Component) {
|
|
83
|
+
throw new Error(`no export "${island.name}" or default in /${island.src}`);
|
|
84
|
+
}
|
|
85
|
+
const out = renderToString(createElement(Component, island.props));
|
|
86
|
+
// Replacer fn (not a string) so `$`-sequences in `out` (e.g. "$&", "$$")
|
|
87
|
+
// aren't interpreted as special replacement patterns.
|
|
88
|
+
htmlBody = htmlBody
|
|
89
|
+
.replace(`<!--${island.token}-->`, () => out)
|
|
90
|
+
.replace(`data-island-ssr="${island.token}"`, 'data-island-ssr="1"');
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(`[l5e-island] Failed to SSR island "${island.name}":`, err);
|
|
93
|
+
htmlBody = htmlBody
|
|
94
|
+
.replace(`<!--${island.token}-->`, '')
|
|
95
|
+
.replace(` data-island-ssr="${island.token}"`, '');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return htmlBody;
|
|
100
|
+
}
|
|
37
101
|
|
|
38
102
|
export interface RawResponse {
|
|
39
103
|
body: string | Buffer;
|
|
@@ -339,7 +403,9 @@ export async function render(url: string, requestInfo: RequestInfo = {}): Promis
|
|
|
339
403
|
});
|
|
340
404
|
|
|
341
405
|
// Render component (có thể có Head components khác)
|
|
342
|
-
|
|
406
|
+
let htmlBody = renderJsxToHtmlString(h(Component, props));
|
|
407
|
+
// Fill any opt-in `ssr` island placeholders with server-rendered React HTML.
|
|
408
|
+
htmlBody = await fillSsrIslands(htmlBody);
|
|
343
409
|
const clientEntries = getClientJsEntries();
|
|
344
410
|
const cssEntries = getCssEntries();
|
|
345
411
|
const islandEntries = getIslandEntries();
|
package/src/core/jsx-runtime.ts
CHANGED
|
@@ -65,10 +65,23 @@ interface IslandEntry {
|
|
|
65
65
|
name: string; // "Counter" — export name
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A pending server-side render request for an `ssr` island.
|
|
70
|
+
* Collected during the synchronous render pass and filled in afterwards by
|
|
71
|
+
* entry-server (which can `await import()` the component + call renderToString).
|
|
72
|
+
*/
|
|
73
|
+
interface SsrIslandEntry {
|
|
74
|
+
token: string; // unique placeholder token embedded in the HTML body
|
|
75
|
+
src: string; // "src/views/.../Counter.tsx" — manifest-compatible path (no leading slash)
|
|
76
|
+
name: string; // "Counter" — export name
|
|
77
|
+
props: Record<string, any>;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
interface RenderContext {
|
|
69
81
|
clientJsRegistry: Array<{ path: string; from: string }>;
|
|
70
82
|
cssRegistry: Array<{ path: string; from: string }>;
|
|
71
83
|
islandRegistry: IslandEntry[];
|
|
84
|
+
ssrIslands: SsrIslandEntry[];
|
|
72
85
|
cacheTags: Set<string>;
|
|
73
86
|
headRegistry: HeadEntry[]; // Thay vì JSXChild[]
|
|
74
87
|
metadataStack: Metadata[]; // Stack để track metadata hierarchy
|
|
@@ -85,6 +98,7 @@ function createRequestContext(requestInfo: RequestInfo): RenderContext {
|
|
|
85
98
|
clientJsRegistry: [],
|
|
86
99
|
cssRegistry: [],
|
|
87
100
|
islandRegistry: [],
|
|
101
|
+
ssrIslands: [],
|
|
88
102
|
cacheTags: new Set(),
|
|
89
103
|
headRegistry: [],
|
|
90
104
|
metadataStack: [],
|
|
@@ -119,6 +133,25 @@ export function getIslandEntries(): IslandEntry[] {
|
|
|
119
133
|
return context.islandRegistry.slice();
|
|
120
134
|
}
|
|
121
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Register a pending SSR island render. Returns a unique placeholder token that
|
|
138
|
+
* the caller embeds (as raw HTML) in the island's body. entry-server replaces
|
|
139
|
+
* the token with the server-rendered component HTML after the sync render pass.
|
|
140
|
+
*/
|
|
141
|
+
export function registerSsrIsland(src: string, name: string, props: Record<string, any>): string {
|
|
142
|
+
const renderContext = renderStore.getStore();
|
|
143
|
+
if (!renderContext) return '';
|
|
144
|
+
const token = `__L5E_SSR_${renderContext.ssrIslands.length}__`;
|
|
145
|
+
renderContext.ssrIslands.push({ token, src, name, props });
|
|
146
|
+
return token;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getSsrIslands(): SsrIslandEntry[] {
|
|
150
|
+
const context = renderStore.getStore();
|
|
151
|
+
if (!context) return [];
|
|
152
|
+
return context.ssrIslands.slice();
|
|
153
|
+
}
|
|
154
|
+
|
|
122
155
|
export function useCss(path: string): string {
|
|
123
156
|
if (typeof path === 'string' && path.length > 0) {
|
|
124
157
|
const renderContext = renderStore.getStore();
|
package/src/core/vite-plugin.ts
CHANGED
|
@@ -16,6 +16,7 @@ const VIRTUAL_L5E_ROUTE = 'virtual:l5e-route';
|
|
|
16
16
|
const VIRTUAL_L5E_SSR_ENTRY = 'virtual:l5e-ssr-entry';
|
|
17
17
|
const VIRTUAL_L5E_GLOBAL_LOADER = 'virtual:l5e-global-loader';
|
|
18
18
|
const VIRTUAL_L5E_ISLAND_STRATEGIES = 'virtual:l5e-island-strategies';
|
|
19
|
+
const VIRTUAL_L5E_ISLANDS = 'virtual:l5e-islands';
|
|
19
20
|
const VIRTUAL_L5E_ACTIONS = 'virtual:l5e-actions';
|
|
20
21
|
const VIRTUAL_L5E_MIDDLEWARE = 'virtual:l5e-middleware';
|
|
21
22
|
|
|
@@ -553,6 +554,9 @@ export function coreVite(): Plugin {
|
|
|
553
554
|
if (id === VIRTUAL_L5E_ISLAND_STRATEGIES) {
|
|
554
555
|
return '\0' + VIRTUAL_L5E_ISLAND_STRATEGIES;
|
|
555
556
|
}
|
|
557
|
+
if (id === VIRTUAL_L5E_ISLANDS) {
|
|
558
|
+
return '\0' + VIRTUAL_L5E_ISLANDS;
|
|
559
|
+
}
|
|
556
560
|
if (id === VIRTUAL_L5E_ACTIONS) {
|
|
557
561
|
return '\0' + VIRTUAL_L5E_ACTIONS;
|
|
558
562
|
}
|
|
@@ -772,6 +776,14 @@ export async function loadMiddleware() {
|
|
|
772
776
|
`;
|
|
773
777
|
}
|
|
774
778
|
|
|
779
|
+
// Virtual module: l5e-islands
|
|
780
|
+
// Lazy glob of all React island components so the SSR entry can import a
|
|
781
|
+
// component by its __src path and renderToString() it. Non-eager → only
|
|
782
|
+
// islands actually present on a page (with ssr) get imported at runtime.
|
|
783
|
+
if (id === '\0' + VIRTUAL_L5E_ISLANDS) {
|
|
784
|
+
return `export const islandModules = import.meta.glob('/src/**/react/*.{tsx,jsx}');`;
|
|
785
|
+
}
|
|
786
|
+
|
|
775
787
|
// Virtual module: l5e-island-strategies
|
|
776
788
|
if (id === '\0' + VIRTUAL_L5E_ISLAND_STRATEGIES) {
|
|
777
789
|
// Check if src/island-strategies.ts exists
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
jsxFactory,
|
|
3
|
+
registerIsland,
|
|
4
|
+
registerSsrIsland,
|
|
5
|
+
type JSXChild,
|
|
6
|
+
type JSXNode,
|
|
7
|
+
} from '../core/jsx-runtime';
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* Derive component name from `from` path.
|
|
@@ -21,6 +27,13 @@ export interface ClientIslandProps {
|
|
|
21
27
|
id?: string;
|
|
22
28
|
children?: JSXChild | JSXChild[];
|
|
23
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Opt-in: server-render the component into the placeholder and hydrate on the
|
|
32
|
+
* client (instead of the default client-only mount). Component must be
|
|
33
|
+
* SSR-safe (no top-level browser access) and props must be JSON-serializable.
|
|
34
|
+
*/
|
|
35
|
+
ssr?: boolean;
|
|
36
|
+
|
|
24
37
|
/** INTERNAL — injected by vite-plugin. Format: "[name]_[hash]" */
|
|
25
38
|
__key?: string;
|
|
26
39
|
/** INTERNAL — injected by vite-plugin. Manifest-compatible source path with extension */
|
|
@@ -36,6 +49,7 @@ export function ClientIsland(attrs: ClientIslandProps): JSXNode {
|
|
|
36
49
|
class: className,
|
|
37
50
|
id,
|
|
38
51
|
children,
|
|
52
|
+
ssr,
|
|
39
53
|
__key,
|
|
40
54
|
__src,
|
|
41
55
|
} = attrs;
|
|
@@ -44,6 +58,8 @@ export function ClientIsland(attrs: ClientIslandProps): JSXNode {
|
|
|
44
58
|
|
|
45
59
|
// Register island in render context (like useClientJs)
|
|
46
60
|
// server.ts will use this to generate per-page window.__L5E_ISLANDS__
|
|
61
|
+
// (needed for BOTH client-only mount AND ssr hydration — the client still
|
|
62
|
+
// loads the component chunk via this registry).
|
|
47
63
|
if (__key && __src) {
|
|
48
64
|
registerIsland(__key, __src, componentName);
|
|
49
65
|
}
|
|
@@ -59,6 +75,25 @@ export function ClientIsland(attrs: ClientIslandProps): JSXNode {
|
|
|
59
75
|
dataAttrs['data-island-opts'] = mountOpts;
|
|
60
76
|
}
|
|
61
77
|
|
|
78
|
+
// SSR opt-in: register a pending server render and embed a unique token as the
|
|
79
|
+
// placeholder body. entry-server replaces the token with renderToString() output
|
|
80
|
+
// after the synchronous render pass, then normalizes data-island-ssr to "1".
|
|
81
|
+
// The attribute value holds the token so the post-pass can target this exact
|
|
82
|
+
// island (and strip the attribute on render failure → clean client-only fallback).
|
|
83
|
+
if (ssr && __src) {
|
|
84
|
+
const token = registerSsrIsland(__src, componentName, props);
|
|
85
|
+
if (token) {
|
|
86
|
+
return jsxFactory('div', {
|
|
87
|
+
...dataAttrs,
|
|
88
|
+
'data-island-ssr': token,
|
|
89
|
+
...(id ? { id } : {}),
|
|
90
|
+
class: className ? `l5e-island ${className}` : 'l5e-island',
|
|
91
|
+
// Raw token comment as body; replaced with SSR HTML in entry-server.
|
|
92
|
+
setHtml: `<!--${token}-->`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
return jsxFactory(
|
|
63
98
|
'div',
|
|
64
99
|
{
|
package/src/island/runtime.ts
CHANGED
|
@@ -77,6 +77,7 @@ function discoverIslands(): IslandMeta[] {
|
|
|
77
77
|
props: JSON.parse(el.getAttribute('data-island-props') || '{}'),
|
|
78
78
|
mount: el.getAttribute('data-island-mount') || 'load',
|
|
79
79
|
mountOpts: el.getAttribute('data-island-opts') || undefined,
|
|
80
|
+
ssr: el.hasAttribute('data-island-ssr'),
|
|
80
81
|
}));
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -97,7 +98,7 @@ function createMountFn(island: IslandMeta): () => Promise<void> {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
try {
|
|
100
|
-
const [
|
|
101
|
+
const [reactDomClient, { createElement }, mod] = await Promise.all([
|
|
101
102
|
import('react-dom/client'),
|
|
102
103
|
import('react'),
|
|
103
104
|
import(/* @vite-ignore */ url),
|
|
@@ -109,8 +110,14 @@ function createMountFn(island: IslandMeta): () => Promise<void> {
|
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
if (island.ssr) {
|
|
114
|
+
// Server already rendered this component into the element → hydrate it.
|
|
115
|
+
reactDomClient.hydrateRoot(island.element, createElement(Component, island.props));
|
|
116
|
+
} else {
|
|
117
|
+
// Client-only mount (default): fresh render into an empty placeholder.
|
|
118
|
+
const root = reactDomClient.createRoot(island.element);
|
|
119
|
+
root.render(createElement(Component, island.props));
|
|
120
|
+
}
|
|
114
121
|
} catch (error) {
|
|
115
122
|
console.error(`[l5e-island] Failed to mount "${island.registryKey}":`, error);
|
|
116
123
|
}
|
package/src/island/types.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface IslandMeta {
|
|
|
19
19
|
props: Record<string, any>;
|
|
20
20
|
mount: string; // Strategy name
|
|
21
21
|
mountOpts?: string; // Options passed to strategy function
|
|
22
|
+
ssr?: boolean; // Server-rendered → hydrate instead of fresh client mount
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface IslandEntry {
|