@withl5e/l5e 0.3.3-alpha.4 → 1.0.0

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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/action.js +10 -9
  3. package/dist/action.js.map +1 -1
  4. package/dist/defineMiddleware-D017PWDc.js +8 -0
  5. package/dist/defineMiddleware-D017PWDc.js.map +1 -0
  6. package/dist/entry-server.js +161 -176
  7. package/dist/entry-server.js.map +1 -1
  8. package/dist/exceptions-D4OdiQjY.js +30 -0
  9. package/dist/exceptions-D4OdiQjY.js.map +1 -0
  10. package/dist/generateMetadata-DcdY-XC1.js +303 -0
  11. package/dist/generateMetadata-DcdY-XC1.js.map +1 -0
  12. package/dist/global-style-BhiGjxVU.js +21 -0
  13. package/dist/global-style-BhiGjxVU.js.map +1 -0
  14. package/dist/i18n.js +8 -11
  15. package/dist/i18n.js.map +1 -1
  16. package/dist/index.js +9 -55
  17. package/dist/island/client.js +2 -5
  18. package/dist/island/runtime.js +90 -107
  19. package/dist/island/runtime.js.map +1 -1
  20. package/dist/island.js +38 -56
  21. package/dist/island.js.map +1 -1
  22. package/dist/jsx-runtime-CI0Jmr-C.js +275 -0
  23. package/dist/jsx-runtime-CI0Jmr-C.js.map +1 -0
  24. package/dist/jsx-runtime.js +2 -31
  25. package/dist/middleware-CnDqZobD.js +217 -0
  26. package/dist/middleware-CnDqZobD.js.map +1 -0
  27. package/dist/middleware.js +3 -8
  28. package/dist/render-hUQUreah.js +152 -0
  29. package/dist/render-hUQUreah.js.map +1 -0
  30. package/dist/rolldown-runtime-B0aSnxlc.js +20 -0
  31. package/dist/router.js +72 -68
  32. package/dist/router.js.map +1 -1
  33. package/dist/seo.js +3 -7
  34. package/dist/server-5BEwGAL4.js +461 -0
  35. package/dist/server-5BEwGAL4.js.map +1 -0
  36. package/dist/server.js +2 -515
  37. package/dist/strategy-registry-wmHnsHQb.js +9 -0
  38. package/dist/strategy-registry-wmHnsHQb.js.map +1 -0
  39. package/dist/swap/server.js +11 -14
  40. package/dist/swap/server.js.map +1 -1
  41. package/dist/swap.js +109 -116
  42. package/dist/swap.js.map +1 -1
  43. package/dist/tooltip.js +145 -141
  44. package/dist/tooltip.js.map +1 -1
  45. package/dist/vite-plugin.js +315 -372
  46. package/dist/vite-plugin.js.map +1 -1
  47. package/package.json +8 -8
  48. package/src/core/bundler.ts +41 -25
  49. package/src/core/vite-plugin.ts +14 -15
  50. package/dist/client-D67hK4Yy.js +0 -9
  51. package/dist/client-D67hK4Yy.js.map +0 -1
  52. package/dist/defineMiddleware-VdHQuiUc.js +0 -7
  53. package/dist/defineMiddleware-VdHQuiUc.js.map +0 -1
  54. package/dist/exceptions-CHPz01DJ.js +0 -39
  55. package/dist/exceptions-CHPz01DJ.js.map +0 -1
  56. package/dist/generateMetadata-Cn8BIxq6.js +0 -144
  57. package/dist/generateMetadata-Cn8BIxq6.js.map +0 -1
  58. package/dist/global-style-DFvaBcuR.js +0 -27
  59. package/dist/global-style-DFvaBcuR.js.map +0 -1
  60. package/dist/index-CybB8Tdk.js +0 -315
  61. package/dist/index-CybB8Tdk.js.map +0 -1
  62. package/dist/index.js.map +0 -1
  63. package/dist/island/client.js.map +0 -1
  64. package/dist/jsx-runtime-DqAp8xlt.js +0 -296
  65. package/dist/jsx-runtime-DqAp8xlt.js.map +0 -1
  66. package/dist/jsx-runtime.js.map +0 -1
  67. package/dist/middleware.js.map +0 -1
  68. package/dist/render-CRag-5V7.js +0 -202
  69. package/dist/render-CRag-5V7.js.map +0 -1
  70. package/dist/seo.js.map +0 -1
  71. package/dist/server.js.map +0 -1
@@ -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';\nimport { findGlobalStyleInput } from './global-style';\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 const globalStyleInput = findGlobalStyleInput(rootDir);\n if (globalStyleInput) {\n input['global-style'] = globalStyleInput;\n console.log('[l5e] Detected src/global.css and added as global stylesheet');\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 // src/global.css is already a dedicated convention entry. Keeping one\n // Rollup input avoids collisions if a shared layout also calls useCss().\n if (globalStyleInput && resolve(absolutePath) === resolve(globalStyleInput)) {\n continue;\n }\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 // Load /src/route.ts LAZILY (dynamic import inside the handler) rather\n // than via a static `export { default } from '/src/route.ts'`.\n //\n // A static re-export drags the user's route module — and everything it\n // imports — into entry-server's *static* SSR graph. Route files commonly\n // import the `@withl5e/l5e` barrel (for RedirectException, types, ...),\n // and that barrel re-exports `render` from entry-server. That closes a\n // static import cycle:\n // entry-server → virtual:l5e-route → /src/route.ts → @withl5e/l5e → entry-server\n // On the first dev request Vite can begin evaluating entry-server while\n // it is still suspended awaiting this very import, so `render()` runs\n // before `const __vite_ssr_import_N__ = await import('virtual:l5e-route')`\n // has been assigned — throwing \"Cannot access '__vite_ssr_import_N__'\n // before initialization\". A page reload \"fixes\" it only because the graph\n // is fully warm the second time.\n //\n // Importing lazily removes /src/route.ts from the static graph: entry-server\n // finishes initializing first, and the route module (with its barrel import\n // back to a now-complete entry-server) is pulled only on the first render()\n // call. No manual caching — Vite/ESM caches the evaluated module and later\n // imports still observe HMR invalidations of route.ts.\n return `export default function routeHandler(requestInfo) {\n return import('/src/route.ts').then((mod) => mod.default(requestInfo));\n}`;\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","globalStyleInput","findGlobalStyleInput","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","names":[],"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';\nimport { findGlobalStyleInput } from './global-style';\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 bundler input entries from useCss and useClientJs calls\n */\nfunction discoverBundlerInput(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 const globalStyleInput = findGlobalStyleInput(rootDir);\n if (globalStyleInput) {\n input['global-style'] = globalStyleInput;\n console.log('[l5e] Detected src/global.css and added as global stylesheet');\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 bundler 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 // src/global.css is already a dedicated convention entry. Keeping one\n // Separate input entries avoid collisions if a shared layout also calls useCss().\n if (globalStyleInput && resolve(absolutePath) === resolve(globalStyleInput)) {\n continue;\n }\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 the bundler input so they appear in the 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 bundler 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 = discoverBundlerInput(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 bundler input from useCss and useClientJs\n const projectRoot = userConfig.root || process.cwd();\n const discovered = discoverBundlerInput(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 rolldownOptions.input if any\n const existingInput = userConfig.build?.rolldownOptions?.input || {};\n const mergedInput =\n typeof existingInput === 'object' && !Array.isArray(existingInput)\n ? { ...discovered.input, ...existingInput }\n : discovered.input;\n\n return {\n build: {\n rolldownOptions: {\n ...userConfig.build?.rolldownOptions,\n input: Object.keys(mergedInput).length > 0 ? mergedInput : undefined,\n // Preserve exports for island component entries — without this,\n // The bundler tree-shakes their exports since nothing 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 // Load /src/route.ts LAZILY (dynamic import inside the handler) rather\n // than via a static `export { default } from '/src/route.ts'`.\n //\n // A static re-export drags the user's route module — and everything it\n // imports — into entry-server's *static* SSR graph. Route files commonly\n // import the `@withl5e/l5e` barrel (for RedirectException, types, ...),\n // and that barrel re-exports `render` from entry-server. That closes a\n // static import cycle:\n // entry-server → virtual:l5e-route → /src/route.ts → @withl5e/l5e → entry-server\n // On the first dev request Vite can begin evaluating entry-server while\n // it is still suspended awaiting this very import, so `render()` runs\n // before `const __vite_ssr_import_N__ = await import('virtual:l5e-route')`\n // has been assigned — throwing \"Cannot access '__vite_ssr_import_N__'\n // before initialization\". A page reload \"fixes\" it only because the graph\n // is fully warm the second time.\n //\n // Importing lazily removes /src/route.ts from the static graph: entry-server\n // finishes initializing first, and the route module (with its barrel import\n // back to a now-complete entry-server) is pulled only on the first render()\n // call. No manual caching — Vite/ESM caches the evaluated module and later\n // imports still observe HMR invalidations of route.ts.\n return `export default function routeHandler(requestInfo) {\n return import('/src/route.ts').then((mod) => mod.default(requestInfo));\n}`;\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withl5e/l5e",
3
- "version": "0.3.3-alpha.4",
3
+ "version": "1.0.0",
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",
@@ -90,32 +90,32 @@
90
90
  },
91
91
  "dependencies": {
92
92
  "@floating-ui/dom": "1.7.4",
93
+ "@types/request-ip": "0.0.41",
94
+ "@types/serialize-javascript": "^5.0.4",
93
95
  "compression": "^1.7.4",
94
96
  "cookie": "^1.1.1",
95
- "esbuild": "^0.24.2",
97
+ "esbuild": "^0.28.0",
96
98
  "express": "^4.18.2",
97
99
  "path-to-regexp": "^8.4.2",
98
100
  "request-ip": "^3.3.0",
99
- "rollup": "^4.0.0",
101
+ "rolldown": "^1.2.7",
100
102
  "serialize-javascript": "^7.0.5",
101
103
  "sirv": "^2.0.3"
102
104
  },
103
105
  "peerDependencies": {
104
106
  "react": "^19.0.0",
105
107
  "react-dom": "^19.0.0",
106
- "vite": "^7.0.0"
108
+ "vite": "^8.0.0"
107
109
  },
108
110
  "devDependencies": {
109
111
  "@types/express": "^4.17.25",
110
112
  "@types/react": "^19.0.0",
111
113
  "@types/react-dom": "^19.0.0",
112
- "@types/request-ip": "0.0.41",
113
- "@types/serialize-javascript": "^5.0.4",
114
114
  "react": "^19.0.0",
115
115
  "react-dom": "^19.0.0",
116
116
  "typescript": "^5.6.3",
117
- "vite": "^7.1.5",
118
- "vitest": "^2.1.3"
117
+ "vite": "^8.2.2",
118
+ "vitest": "^4.1.11"
119
119
  },
120
120
  "publishConfig": {
121
121
  "access": "public"
@@ -4,27 +4,29 @@ import fs from 'node:fs/promises';
4
4
  import { createRequire } from 'node:module';
5
5
  import path from 'node:path';
6
6
  import { pathToFileURL } from 'node:url';
7
- import type { OutputOptions, Plugin, RollupOptions } from 'rollup';
7
+ import type { InputOptions, OutputChunk, OutputOptions, Plugin } from 'rolldown';
8
8
 
9
- let rollupModulePromise: Promise<typeof import('rollup')> | null = null;
9
+ let rolldownModulePromise: Promise<typeof import('rolldown')> | null = null;
10
10
 
11
11
  /**
12
- * Resolve rollup lazily. We can't `import 'rollup'` directly because when the
13
- * framework gets bundled into the consumer's SSR output the bare specifier is
14
- * hoisted to a static import that pnpm doesn't satisfy. Resolve through `vite`
15
- * instead vite is a peer dep so it's always installed, and it always ships
16
- * rollup as a direct dependency.
12
+ * Resolve Rolldown lazily from the installed framework package. A bare static
13
+ * import can be hoisted when the framework is bundled into a consumer's SSR
14
+ * output, which breaks pnpm's strict dependency layout. Anchoring resolution at
15
+ * @withl5e/l5e keeps the runtime dependency owned by the package that declares it.
17
16
  */
18
- function loadRollup(): Promise<typeof import('rollup')> {
19
- if (!rollupModulePromise) {
20
- rollupModulePromise = (async () => {
17
+ function loadRolldown(): Promise<typeof import('rolldown')> {
18
+ if (!rolldownModulePromise) {
19
+ rolldownModulePromise = (async () => {
21
20
  const require = createRequire(import.meta.url);
22
- const vitePath = require.resolve('vite');
23
- const rollupPath = require.resolve('rollup', { paths: [path.dirname(vitePath)] });
24
- return (await import(pathToFileURL(rollupPath).href)) as typeof import('rollup');
21
+ const frameworkEntry = require.resolve('@withl5e/l5e/server');
22
+ const frameworkRequire = createRequire(frameworkEntry);
23
+ const rolldownPath = frameworkRequire.resolve('rolldown');
24
+ return (await import(
25
+ /* @vite-ignore */ pathToFileURL(rolldownPath).href
26
+ )) as typeof import('rolldown');
25
27
  })();
26
28
  }
27
- return rollupModulePromise;
29
+ return rolldownModulePromise;
28
30
  }
29
31
 
30
32
  interface BundledFile {
@@ -76,13 +78,13 @@ function generateHash(content: string): string {
76
78
  return createHash('sha256').update(content).digest('hex').substring(0, 16);
77
79
  }
78
80
 
79
- // Rollup coi id bắt đầu bằng \0 là virtual — nó sẽ không cố đọc từ đĩa.
81
+ // Rolldown coi id bắt đầu bằng \0 là virtual — nó sẽ không cố đọc từ đĩa.
80
82
  const VIRTUAL_ENTRY_ID = '\0l5e:bundle-entry';
81
83
 
82
84
  /**
83
85
  * Entry của mỗi lần bundle chỉ là một danh sách import. Giữ nó trong memory thay
84
86
  * vì ghi ra đĩa: hai request đồng thời cùng một tập script sinh ra cùng nội dung
85
- * entry, nên file tạm dùng chung path sẽ bị request này xoá trong lúc rollup của
87
+ * entry, nên file tạm dùng chung path sẽ bị request này xoá trong lúc Rolldown của
86
88
  * request kia còn đang đọc.
87
89
  */
88
90
  function virtualEntryPlugin(entryContent: string): Plugin {
@@ -119,12 +121,15 @@ function vendorPathRewriterPlugin(distClientDir: string): Plugin {
119
121
 
120
122
  if (path.isAbsolute(source)) {
121
123
  // e.g. C:\...\dist\client\assets\vendor-react-XXX.js -> /assets/vendor-react-XXX.js
122
- return { id: toWebPath(source), external: true };
124
+ return { id: toWebPath(source), external: 'absolute' };
123
125
  }
124
126
 
125
127
  if (importer && source.startsWith('.')) {
126
128
  // Relative path như ./auth.global-BOVr81Z5.js — resolve từ importer
127
- return { id: toWebPath(path.resolve(path.dirname(importer), source)), external: true };
129
+ return {
130
+ id: toWebPath(path.resolve(path.dirname(importer), source)),
131
+ external: 'absolute',
132
+ };
128
133
  }
129
134
 
130
135
  return null;
@@ -145,9 +150,16 @@ async function runScriptBundle(
145
150
  })
146
151
  .join('\n');
147
152
 
148
- const rollupOptions: RollupOptions = {
153
+ const rolldownOptions: InputOptions = {
149
154
  input: VIRTUAL_ENTRY_ID,
150
155
  plugins: [virtualEntryPlugin(entryContent), vendorPathRewriterPlugin(distClientDir)],
156
+ platform: 'neutral',
157
+ tsconfig: false,
158
+ // Runtime scripts are imported for their side effects. Do not let an app's
159
+ // package.json sideEffects flag erase those entry imports.
160
+ // Rolldown 1.2 still consults package metadata for boolean `true`, so use
161
+ // an explicit callback to override consumer `sideEffects: false`.
162
+ treeshake: { moduleSideEffects: () => true },
151
163
  external: (id) => {
152
164
  // External node_modules
153
165
  if (!id.startsWith('.') && !path.isAbsolute(id) && id !== VIRTUAL_ENTRY_ID) {
@@ -161,13 +173,14 @@ async function runScriptBundle(
161
173
 
162
174
  const outputOptions: OutputOptions = {
163
175
  format: 'es',
164
- inlineDynamicImports: false,
176
+ codeSplitting: true,
177
+ minify: false,
165
178
  entryFileNames: 'bundle-[hash].js',
166
179
  chunkFileNames: 'bundle-[hash].js',
167
180
  };
168
181
 
169
- const { rollup } = await loadRollup();
170
- const bundle = await rollup(rollupOptions);
182
+ const { rolldown } = await loadRolldown();
183
+ const bundle = await rolldown(rolldownOptions);
171
184
  let output;
172
185
  try {
173
186
  ({ output } = await bundle.generate(outputOptions));
@@ -187,9 +200,12 @@ async function runScriptBundle(
187
200
  });
188
201
  }
189
202
 
190
- const entryChunk = output[0];
191
- if (entryChunk?.type !== 'chunk') {
192
- throw new Error('[bundler] rollup produced no entry chunk');
203
+ const entryChunk = output.find(
204
+ (item): item is OutputChunk =>
205
+ item.type === 'chunk' && item.isEntry && item.facadeModuleId === VIRTUAL_ENTRY_ID,
206
+ );
207
+ if (!entryChunk) {
208
+ throw new Error('[bundler] rolldown produced no entry chunk');
193
209
  }
194
210
 
195
211
  return {
@@ -214,9 +214,9 @@ function extractPathsFromFile(filePath: string, rootDir: string): { css: string[
214
214
  }
215
215
 
216
216
  /**
217
- * Auto-discover rollup input entries from useCss and useClientJs calls
217
+ * Auto-discover bundler input entries from useCss and useClientJs calls
218
218
  */
219
- function discoverRollupInput(rootDir: string): {
219
+ function discoverBundlerInput(rootDir: string): {
220
220
  input: Record<string, string>;
221
221
  islandRegistry: Map<string, string>;
222
222
  pathToKey: Map<string, string>;
@@ -292,7 +292,7 @@ function discoverRollupInput(rootDir: string): {
292
292
  }
293
293
  }
294
294
 
295
- // Convert paths to rollup input entries
295
+ // Convert paths to bundler input entries
296
296
  // CSS files
297
297
  for (const cssPath of allCssPaths) {
298
298
  // Remove leading /src/ and convert to relative path
@@ -305,7 +305,7 @@ function discoverRollupInput(rootDir: string): {
305
305
  const absolutePath = resolve(rootDir, relativePath);
306
306
 
307
307
  // src/global.css is already a dedicated convention entry. Keeping one
308
- // Rollup input avoids collisions if a shared layout also calls useCss().
308
+ // Separate input entries avoid collisions if a shared layout also calls useCss().
309
309
  if (globalStyleInput && resolve(absolutePath) === resolve(globalStyleInput)) {
310
310
  continue;
311
311
  }
@@ -352,7 +352,7 @@ function discoverRollupInput(rootDir: string): {
352
352
  input[entryName] = absolutePath;
353
353
  }
354
354
 
355
- // Add island component files to rollup input so they appear in manifest.
355
+ // Add island component files to the bundler input so they appear in the manifest.
356
356
  // server.ts looks up manifest at runtime to resolve per-page island URLs.
357
357
  for (const [key, src] of keyToSrc) {
358
358
  const absolutePath = resolve(rootDir, src);
@@ -367,7 +367,7 @@ function discoverRollupInput(rootDir: string): {
367
367
  }
368
368
  } catch (error) {
369
369
  // Silently fail if directory doesn't exist or other errors
370
- console.warn('[l5e] Failed to discover rollup input:', error);
370
+ console.warn('[l5e] Failed to discover bundler input:', error);
371
371
  }
372
372
 
373
373
  return { input, islandRegistry, pathToKey, keyToSrc, actionRegistry };
@@ -457,7 +457,7 @@ export function coreVite(): Plugin {
457
457
 
458
458
  // Re-scan action registry when an actions file changes
459
459
  if (/actions\.(ts|tsx)$/.test(file)) {
460
- const discovered = discoverRollupInput(rootDir);
460
+ const discovered = discoverBundlerInput(rootDir);
461
461
  actionRegistry = discovered.actionRegistry;
462
462
  // Invalidate the virtual:l5e-actions module so server picks up new registry
463
463
  const mod = server.moduleGraph.getModuleById('\0' + VIRTUAL_L5E_ACTIONS);
@@ -519,9 +519,9 @@ export function coreVite(): Plugin {
519
519
  },
520
520
 
521
521
  config(userConfig) {
522
- // Auto-discover rollup input from useCss and useClientJs
522
+ // Auto-discover bundler input from useCss and useClientJs
523
523
  const projectRoot = userConfig.root || process.cwd();
524
- const discovered = discoverRollupInput(projectRoot);
524
+ const discovered = discoverBundlerInput(projectRoot);
525
525
 
526
526
  // Store island registries for use in other hooks
527
527
  islandRegistry = discovered.islandRegistry;
@@ -529,8 +529,8 @@ export function coreVite(): Plugin {
529
529
  keyToSrc = discovered.keyToSrc;
530
530
  actionRegistry = discovered.actionRegistry;
531
531
 
532
- // Merge with existing rollupOptions.input if any
533
- const existingInput = userConfig.build?.rollupOptions?.input || {};
532
+ // Merge with existing rolldownOptions.input if any
533
+ const existingInput = userConfig.build?.rolldownOptions?.input || {};
534
534
  const mergedInput =
535
535
  typeof existingInput === 'object' && !Array.isArray(existingInput)
536
536
  ? { ...discovered.input, ...existingInput }
@@ -538,12 +538,11 @@ export function coreVite(): Plugin {
538
538
 
539
539
  return {
540
540
  build: {
541
- ...userConfig.build,
542
- rollupOptions: {
543
- ...userConfig.build?.rollupOptions,
541
+ rolldownOptions: {
542
+ ...userConfig.build?.rolldownOptions,
544
543
  input: Object.keys(mergedInput).length > 0 ? mergedInput : undefined,
545
544
  // Preserve exports for island component entries — without this,
546
- // Rollup tree-shakes their exports since nothing in the bundle imports them
545
+ // The bundler tree-shakes their exports since nothing imports them
547
546
  // (they're loaded at runtime via dynamic import from the island runtime).
548
547
  preserveEntrySignatures: 'exports-only',
549
548
  },
@@ -1,9 +0,0 @@
1
- const t = /* @__PURE__ */ new Map();
2
- function s(e, r) {
3
- t.has(e) && console.warn(`[l5e-island] Strategy "${e}" already registered, overwriting.`), t.set(e, r);
4
- }
5
- export {
6
- s as r,
7
- t as s
8
- };
9
- //# sourceMappingURL=client-D67hK4Yy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-D67hK4Yy.js","sources":["../src/island/strategy-registry.ts"],"sourcesContent":["import type { MountStrategy } from './types';\n\nexport const strategies = new Map<string, MountStrategy>();\n\nexport function registerMountStrategy(name: string, fn: MountStrategy) {\n if (strategies.has(name)) {\n console.warn(`[l5e-island] Strategy \"${name}\" already registered, overwriting.`);\n }\n strategies.set(name, fn);\n}\n"],"names":["strategies","registerMountStrategy","name","fn"],"mappings":"AAEO,MAAMA,wBAAiB,IAAA;AAEvB,SAASC,EAAsBC,GAAcC,GAAmB;AACrE,EAAIH,EAAW,IAAIE,CAAI,KACrB,QAAQ,KAAK,0BAA0BA,CAAI,oCAAoC,GAEjFF,EAAW,IAAIE,GAAMC,CAAE;AACzB;"}
@@ -1,7 +0,0 @@
1
- function d(e) {
2
- return e;
3
- }
4
- export {
5
- d
6
- };
7
- //# sourceMappingURL=defineMiddleware-VdHQuiUc.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"defineMiddleware-VdHQuiUc.js","sources":["../src/middleware/defineMiddleware.ts"],"sourcesContent":["import type { MiddlewareHandler } from './types';\n\nexport function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler {\n return fn;\n}\n"],"names":["defineMiddleware","fn"],"mappings":"AAEO,SAASA,EAAiBC,GAA0C;AACzE,SAAOA;AACT;"}
@@ -1,39 +0,0 @@
1
- class s extends Error {
2
- constructor(t, e, c) {
3
- super(e), this.name = "HttpException", this.statusCode = t, this.data = c, Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
4
- }
5
- }
6
- class a extends s {
7
- constructor(t = "Bad Request", e) {
8
- super(400, t, e), this.name = "BadRequestException";
9
- }
10
- }
11
- class o extends s {
12
- constructor(t = "Not Found", e) {
13
- super(404, t, e), this.name = "NotFoundException";
14
- }
15
- }
16
- class n extends s {
17
- constructor(t = "Internal Server Error", e) {
18
- super(500, t, e), this.name = "InternalServerErrorException";
19
- }
20
- }
21
- class i extends s {
22
- constructor(t = "Service Unavailable", e) {
23
- super(503, t, e), this.name = "ServiceUnavailableException";
24
- }
25
- }
26
- class u extends Error {
27
- constructor(t, e = 302, c) {
28
- super(`Redirect to ${t}`), this.name = "RedirectException", this.url = t, this.statusCode = e, this.data = c, Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
29
- }
30
- }
31
- export {
32
- a as B,
33
- s as H,
34
- n as I,
35
- o as N,
36
- u as R,
37
- i as S
38
- };
39
- //# sourceMappingURL=exceptions-CHPz01DJ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exceptions-CHPz01DJ.js","sources":["../src/core/exceptions.ts"],"sourcesContent":["/**\n * Base HTTP Exception class\n */\nexport class HttpException extends Error {\n public readonly statusCode: number;\n public readonly data?: Record<string, any>;\n\n constructor(statusCode: number, message: string, data?: Record<string, any>) {\n super(message);\n this.name = 'HttpException';\n this.statusCode = statusCode;\n this.data = data;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * 400 Bad Request Exception\n */\nexport class BadRequestException extends HttpException {\n constructor(message: string = 'Bad Request', data?: Record<string, any>) {\n super(400, message, data);\n this.name = 'BadRequestException';\n }\n}\n\n/**\n * 404 Not Found Exception\n */\nexport class NotFoundException extends HttpException {\n constructor(message: string = 'Not Found', data?: Record<string, any>) {\n super(404, message, data);\n this.name = 'NotFoundException';\n }\n}\n\n/**\n * 500 Internal Server Error Exception\n */\nexport class InternalServerErrorException extends HttpException {\n constructor(message: string = 'Internal Server Error', data?: Record<string, any>) {\n super(500, message, data);\n this.name = 'InternalServerErrorException';\n }\n}\n\n/**\n * 503 Service Unavailable Exception\n */\nexport class ServiceUnavailableException extends HttpException {\n constructor(message: string = 'Service Unavailable', data?: Record<string, any>) {\n super(503, message, data);\n this.name = 'ServiceUnavailableException';\n }\n}\n\n/**\n * Redirect Exception - for HTTP redirects (301/302)\n */\nexport class RedirectException extends Error {\n public readonly url: string;\n public readonly statusCode: number;\n public readonly data?: Record<string, any>;\n\n constructor(url: string, statusCode: number = 302, data?: Record<string, any>) {\n super(`Redirect to ${url}`);\n this.name = 'RedirectException';\n this.url = url;\n this.statusCode = statusCode;\n this.data = data;\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n"],"names":["HttpException","statusCode","message","data","BadRequestException","NotFoundException","InternalServerErrorException","ServiceUnavailableException","RedirectException","url"],"mappings":"AAGO,MAAMA,UAAsB,MAAM;AAAA,EAIvC,YAAYC,GAAoBC,GAAiBC,GAA4B;AAC3E,UAAMD,CAAO,GACb,KAAK,OAAO,iBACZ,KAAK,aAAaD,GAClB,KAAK,OAAOE,GAGR,MAAM,qBACR,MAAM,kBAAkB,MAAM,KAAK,WAAW;AAAA,EAElD;AACF;AAKO,MAAMC,UAA4BJ,EAAc;AAAA,EACrD,YAAYE,IAAkB,eAAeC,GAA4B;AACvE,UAAM,KAAKD,GAASC,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAME,UAA0BL,EAAc;AAAA,EACnD,YAAYE,IAAkB,aAAaC,GAA4B;AACrE,UAAM,KAAKD,GAASC,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAMG,UAAqCN,EAAc;AAAA,EAC9D,YAAYE,IAAkB,yBAAyBC,GAA4B;AACjF,UAAM,KAAKD,GAASC,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAMI,UAAoCP,EAAc;AAAA,EAC7D,YAAYE,IAAkB,uBAAuBC,GAA4B;AAC/E,UAAM,KAAKD,GAASC,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAMK,UAA0B,MAAM;AAAA,EAK3C,YAAYC,GAAaR,IAAqB,KAAKE,GAA4B;AAC7E,UAAM,eAAeM,CAAG,EAAE,GAC1B,KAAK,OAAO,qBACZ,KAAK,MAAMA,GACX,KAAK,aAAaR,GAClB,KAAK,OAAOE,GAER,MAAM,qBACR,MAAM,kBAAkB,MAAM,KAAK,WAAW;AAAA,EAElD;AACF;"}