@elurjs/kit 2.4.10 → 2.5.1
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/CHANGELOG.md +313 -0
- package/README.md +248 -15
- package/dist/lib/action/index.cjs +1 -1
- package/dist/lib/action/index.js +1 -1
- package/dist/lib/{action-DZHihU3Z.cjs → action-BiE4tEty.cjs} +2 -2
- package/dist/lib/{action-DZHihU3Z.cjs.map → action-BiE4tEty.cjs.map} +1 -1
- package/dist/lib/{action-BB6N5yD-.js → action-CXVk8rWt.js} +3 -3
- package/dist/lib/{action-BB6N5yD-.js.map → action-CXVk8rWt.js.map} +1 -1
- package/dist/lib/adapter-DNsjXvq5.js +160 -0
- package/dist/lib/adapter-DNsjXvq5.js.map +1 -0
- package/dist/lib/adapter-Dz2hzR36.cjs +3 -0
- package/dist/lib/adapter-Dz2hzR36.cjs.map +1 -0
- package/dist/lib/adapters/bun.cjs +2 -2
- package/dist/lib/adapters/bun.cjs.map +1 -1
- package/dist/lib/adapters/bun.js +17 -9
- package/dist/lib/adapters/bun.js.map +1 -1
- package/dist/lib/adapters/index.d.cts +34 -0
- package/dist/lib/adapters/index.d.ts +34 -0
- package/dist/lib/adapters/netlify.cjs +1 -1
- package/dist/lib/adapters/netlify.js +7 -7
- package/dist/lib/adapters/node.cjs +5 -5
- package/dist/lib/adapters/node.cjs.map +1 -1
- package/dist/lib/adapters/node.js +20 -12
- package/dist/lib/adapters/node.js.map +1 -1
- package/dist/lib/adapters/vercel.cjs +1 -1
- package/dist/lib/adapters/vercel.js +8 -8
- package/dist/lib/build/build.d.cts +40 -0
- package/dist/lib/build/build.d.ts +40 -0
- package/dist/lib/build/document-shell.d.cts +47 -1
- package/dist/lib/build/document-shell.d.ts +47 -1
- package/dist/lib/build/vite-build.d.cts +25 -2
- package/dist/lib/build/vite-build.d.ts +25 -2
- package/dist/lib/cache/adapter.d.cts +41 -0
- package/dist/lib/cache/adapter.d.ts +41 -0
- package/dist/lib/cache/index.cjs +2 -0
- package/dist/lib/cache/index.cjs.map +1 -0
- package/dist/lib/cache/index.d.cts +5 -0
- package/dist/lib/cache/index.d.ts +5 -0
- package/dist/lib/cache/index.js +113 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/invalidation.d.cts +36 -0
- package/dist/lib/cache/invalidation.d.ts +36 -0
- package/dist/lib/cache/redis-adapter.d.cts +49 -0
- package/dist/lib/cache/redis-adapter.d.ts +49 -0
- package/dist/lib/cache-DGhTYyqh.js +50 -0
- package/dist/lib/cache-DGhTYyqh.js.map +1 -0
- package/dist/lib/cache-X1n4Ossu.cjs +2 -0
- package/dist/lib/cache-X1n4Ossu.cjs.map +1 -0
- package/dist/lib/cli/output.d.cts +58 -0
- package/dist/lib/cli/output.d.ts +58 -0
- package/dist/lib/cli/ports.d.cts +20 -0
- package/dist/lib/cli/ports.d.ts +20 -0
- package/dist/lib/cli.cjs +2559 -1488
- package/dist/lib/cli.cjs.map +1 -1
- package/dist/lib/cli.d.cts +21 -0
- package/dist/lib/cli.d.ts +21 -0
- package/dist/lib/cli.js +2558 -1488
- package/dist/lib/cli.js.map +1 -1
- package/dist/lib/config/index.cjs +1 -1
- package/dist/lib/config/index.cjs.map +1 -1
- package/dist/lib/config/index.d.cts +79 -0
- package/dist/lib/config/index.d.ts +79 -0
- package/dist/lib/config/index.js +21 -14
- package/dist/lib/config/index.js.map +1 -1
- package/dist/lib/{errors-ePYxDlhU.js → errors-j4cg9VN3.js} +24 -30
- package/dist/lib/{errors-ePYxDlhU.js.map → errors-j4cg9VN3.js.map} +1 -1
- package/dist/lib/errors-kSl9yLkg.cjs +2 -0
- package/dist/lib/{errors-0X7pCFNU.cjs.map → errors-kSl9yLkg.cjs.map} +1 -1
- package/dist/lib/hydrate-BSLuI0Nh.cjs +2 -0
- package/dist/lib/hydrate-BSLuI0Nh.cjs.map +1 -0
- package/dist/lib/hydrate-BaS7-d8C.js +133 -0
- package/dist/lib/hydrate-BaS7-d8C.js.map +1 -0
- package/dist/lib/idiomorph.esm-BOOTOg1r.cjs +2 -0
- package/dist/lib/idiomorph.esm-BOOTOg1r.cjs.map +1 -0
- package/dist/lib/idiomorph.esm-CXuY-EHB.js +371 -0
- package/dist/lib/idiomorph.esm-CXuY-EHB.js.map +1 -0
- package/dist/lib/index.cjs +1 -1
- package/dist/lib/index.cjs.map +1 -1
- package/dist/lib/index.d.cts +8 -6
- package/dist/lib/index.d.ts +8 -6
- package/dist/lib/index.js +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/interpolation-plugin-C0dTd69H.cjs +58 -0
- package/dist/lib/interpolation-plugin-C0dTd69H.cjs.map +1 -0
- package/dist/lib/{interpolation-plugin-Wgb1j4pT.js → interpolation-plugin-K2qE69tF.js} +95 -126
- package/dist/lib/interpolation-plugin-K2qE69tF.js.map +1 -0
- package/dist/lib/invalidation-CFpNQUv6.cjs +2 -0
- package/dist/lib/invalidation-CFpNQUv6.cjs.map +1 -0
- package/dist/lib/invalidation-DXhdE9wj.js +64 -0
- package/dist/lib/invalidation-DXhdE9wj.js.map +1 -0
- package/dist/lib/island/generate-entry.d.cts +40 -2
- package/dist/lib/island/generate-entry.d.ts +40 -2
- package/dist/lib/island/hydrate.d.cts +19 -6
- package/dist/lib/island/hydrate.d.ts +19 -6
- package/dist/lib/island/index.cjs +1 -1
- package/dist/lib/island/index.d.cts +2 -1
- package/dist/lib/island/index.d.ts +2 -1
- package/dist/lib/island/index.js +1 -1
- package/dist/lib/island/island.d.cts +15 -0
- package/dist/lib/island/island.d.ts +15 -0
- package/dist/lib/island-CTBnsOhI.js +59 -0
- package/dist/lib/island-CTBnsOhI.js.map +1 -0
- package/dist/lib/island-kfQEKRe_.cjs +2 -0
- package/dist/lib/island-kfQEKRe_.cjs.map +1 -0
- package/dist/lib/node-http-DVFcYeBS.js +723 -0
- package/dist/lib/node-http-DVFcYeBS.js.map +1 -0
- package/dist/lib/node-http-Uct7DhmN.cjs +13 -0
- package/dist/lib/node-http-Uct7DhmN.cjs.map +1 -0
- package/dist/lib/rolldown-runtime-BIInC43l.cjs +1 -0
- package/dist/lib/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/lib/router/client.cjs +1 -1
- package/dist/lib/router/client.cjs.map +1 -1
- package/dist/lib/router/client.d.cts +55 -8
- package/dist/lib/router/client.d.ts +55 -8
- package/dist/lib/router/client.js +298 -97
- package/dist/lib/router/client.js.map +1 -1
- package/dist/lib/router/redirects.d.cts +34 -0
- package/dist/lib/router/redirects.d.ts +34 -0
- package/dist/lib/runtime/handler.d.cts +59 -0
- package/dist/lib/runtime/handler.d.ts +59 -0
- package/dist/lib/runtime/index.cjs +1 -1
- package/dist/lib/runtime/index.d.cts +2 -1
- package/dist/lib/runtime/index.d.ts +2 -1
- package/dist/lib/runtime/index.js +4 -4
- package/dist/lib/runtime/logger.d.cts +47 -0
- package/dist/lib/runtime/logger.d.ts +47 -0
- package/dist/lib/runtime/node-http.d.cts +11 -1
- package/dist/lib/runtime/node-http.d.ts +11 -1
- package/dist/lib/runtime-ChE4flX3.cjs +2 -0
- package/dist/lib/runtime-ChE4flX3.cjs.map +1 -0
- package/dist/lib/runtime-OMyz5yE-.js +905 -0
- package/dist/lib/runtime-OMyz5yE-.js.map +1 -0
- package/dist/lib/seo/index.cjs +1 -10
- package/dist/lib/seo/index.d.cts +1 -0
- package/dist/lib/seo/index.d.ts +1 -0
- package/dist/lib/seo/index.js +2 -41
- package/dist/lib/seo/sitemap-from-routes.d.cts +24 -0
- package/dist/lib/seo/sitemap-from-routes.d.ts +24 -0
- package/dist/lib/{shared-CfqOLsKj.cjs → shared-CP0Dc3cp.cjs} +72 -47
- package/dist/lib/shared-CP0Dc3cp.cjs.map +1 -0
- package/dist/lib/{shared-DU3iQgr7.js → shared-D8w-neq2.js} +87 -58
- package/dist/lib/shared-D8w-neq2.js.map +1 -0
- package/dist/lib/sitemap-from-routes-CaBoiGvE.js +77 -0
- package/dist/lib/sitemap-from-routes-CaBoiGvE.js.map +1 -0
- package/dist/lib/sitemap-from-routes-DbcLLL4G.cjs +13 -0
- package/dist/lib/sitemap-from-routes-DbcLLL4G.cjs.map +1 -0
- package/dist/lib/ssr/render.d.cts +8 -2
- package/dist/lib/ssr/render.d.ts +8 -2
- package/dist/lib/ssr/server.d.cts +7 -0
- package/dist/lib/ssr/server.d.ts +7 -0
- package/dist/lib/ssr/stream-response.d.cts +30 -0
- package/dist/lib/ssr/stream-response.d.ts +30 -0
- package/dist/lib/ssr/stream.d.cts +16 -1
- package/dist/lib/ssr/stream.d.ts +16 -1
- package/dist/lib/{stream-DmZjYqHm.cjs → stream-Cg-xYG82.cjs} +3 -3
- package/dist/lib/stream-Cg-xYG82.cjs.map +1 -0
- package/dist/lib/stream-D-ytogdu.js +106 -0
- package/dist/lib/stream-D-ytogdu.js.map +1 -0
- package/dist/lib/vite/index.cjs +1 -1
- package/dist/lib/vite/index.cjs.map +1 -1
- package/dist/lib/vite/index.d.cts +11 -0
- package/dist/lib/vite/index.d.ts +11 -0
- package/dist/lib/vite/index.js +101 -94
- package/dist/lib/vite/index.js.map +1 -1
- package/package.json +25 -8
- package/dist/lib/errors-0X7pCFNU.cjs +0 -2
- package/dist/lib/hydrate-CJbUWdXu.js +0 -126
- package/dist/lib/hydrate-CJbUWdXu.js.map +0 -1
- package/dist/lib/hydrate-D6MR24mz.cjs +0 -2
- package/dist/lib/hydrate-D6MR24mz.cjs.map +0 -1
- package/dist/lib/interpolation-plugin-TWDch8nn.cjs +0 -39
- package/dist/lib/interpolation-plugin-TWDch8nn.cjs.map +0 -1
- package/dist/lib/interpolation-plugin-Wgb1j4pT.js.map +0 -1
- package/dist/lib/node-http-DRAUhO0c.js +0 -617
- package/dist/lib/node-http-DRAUhO0c.js.map +0 -1
- package/dist/lib/node-http-DhxguYyz.cjs +0 -13
- package/dist/lib/node-http-DhxguYyz.cjs.map +0 -1
- package/dist/lib/runtime-CrH5X-Es.cjs +0 -2
- package/dist/lib/runtime-CrH5X-Es.cjs.map +0 -1
- package/dist/lib/runtime-Ds2-wH6P.js +0 -520
- package/dist/lib/runtime-Ds2-wH6P.js.map +0 -1
- package/dist/lib/seo/index.cjs.map +0 -1
- package/dist/lib/seo/index.js.map +0 -1
- package/dist/lib/shared-CfqOLsKj.cjs.map +0 -1
- package/dist/lib/shared-DU3iQgr7.js.map +0 -1
- package/dist/lib/stream-CpOG052V.js +0 -104
- package/dist/lib/stream-CpOG052V.js.map +0 -1
- package/dist/lib/stream-DmZjYqHm.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-CP0Dc3cp.cjs","names":[],"sources":["../../src/adapters/shared.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readdir, copyFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { scanRoutes } from \"../router/route-scanner.js\";\nimport type { AdapterOptions } from \"./index.js\";\nimport type { PageRoute } from \"../router/route-scanner.js\";\n\n/**\n * Shared helper: copy a directory recursively.\n */\nexport async function copyStatic(from: string, to: string): Promise<void> {\n await mkdir(to, { recursive: true });\n const entries = await readdir(from, { withFileTypes: true });\n for (const entry of entries) {\n const src = join(from, entry.name);\n const dest = join(to, entry.name);\n if (entry.isDirectory()) {\n await copyStatic(src, dest);\n } else {\n await copyFile(src, dest);\n }\n }\n}\n\n/** Adds every module the SSR runtime may import for a page to the registry. */\nfunction collectPageModules(\n page: PageRoute,\n moduleSet: Set<string>,\n actionPathsByPage: Map<string, Set<string>>,\n): void {\n moduleSet.add(page.pagePath);\n if (page.dataPath) moduleSet.add(page.dataPath);\n if (page.loadingPath) moduleSet.add(page.loadingPath);\n for (const layout of page.layouts) {\n moduleSet.add(layout);\n const layoutDataPath = layout.replace(/layout\\.ts$/, \"layout.data.ts\");\n if (layoutDataPath !== layout && existsSync(layoutDataPath)) {\n moduleSet.add(layoutDataPath);\n }\n }\n if (page.actionPath) {\n moduleSet.add(page.actionPath);\n let set = actionPathsByPage.get(page.path);\n if (!set) {\n set = new Set<string>();\n actionPathsByPage.set(page.path, set);\n }\n set.add(page.actionPath);\n }\n}\n\n/**\n * Build a self-contained SSR entry file for a platform adapter.\n * The generated module exports a default `handler(request: Request): Response`\n * and embeds the full route table plus a registry of all page/layout/data/\n * action modules so the runtime never touches the file system.\n */\nexport async function buildSsrEntry(\n routes: Awaited<ReturnType<typeof scanRoutes>>,\n options: AdapterOptions,\n entryDir: string,\n): Promise<string> {\n // Collect all module paths that the SSR runtime may need to import.\n const moduleSet = new Set<string>();\n const actionPathsByPage = new Map<string, Set<string>>();\n for (const page of routes.pages) {\n collectPageModules(page, moduleSet, actionPathsByPage);\n }\n if (routes.error404) collectPageModules(routes.error404, moduleSet, actionPathsByPage);\n if (routes.error500) collectPageModules(routes.error500, moduleSet, actionPathsByPage);\n for (const api of routes.api) {\n moduleSet.add(api.routePath);\n }\n const modules = Array.from(moduleSet);\n const moduleIndex = new Map(modules.map((path, index) => [path, index]));\n\n const imports = modules\n .map((path, index) => {\n const rel = relativeToPosix(entryDir, path);\n return `import * as m_${index} from ${JSON.stringify(rel)};`;\n })\n .join(\"\\n\");\n\n const renderPageRecord = (page: PageRoute): string => `{\n path: ${JSON.stringify(page.path)},\n pagePath: ${JSON.stringify(page.pagePath)},\n dataPath: ${JSON.stringify(page.dataPath ?? null)},\n actionPath: ${JSON.stringify(page.actionPath ?? null)},\n loadingPath: ${JSON.stringify(page.loadingPath ?? null)},\n layouts: ${JSON.stringify(page.layouts)},\n params: ${JSON.stringify(page.params)},\n }`;\n\n const pages = routes.pages.map(renderPageRecord).join(\",\\n\");\n\n const apiRoutes = routes.api\n .map((api) => {\n const index = moduleIndex.get(api.routePath);\n return ` { path: ${JSON.stringify(api.path)}, routePath: m_${index} },`;\n })\n .join(\"\\n\");\n\n const actionModules = Array.from(actionPathsByPage.entries())\n .map(([pagePath, paths]) => {\n const entries = Array.from(paths)\n .map((path) => {\n const index = moduleIndex.get(path);\n return ` [${JSON.stringify(path)}, m_${index}],`;\n })\n .join(\"\\n\");\n return ` [${JSON.stringify(pagePath)}, new Map([\\n${entries}\\n ])],`;\n })\n .join(\"\\n\");\n\n const actionsRegistry: Record<string, string[]> = {};\n for (const page of routes.pages) {\n if (!page.actionPath) continue;\n const mod = (await import(page.actionPath)) as Record<string, unknown>;\n const names: string[] = [];\n for (const [name, value] of Object.entries(mod)) {\n if (name === \"default\") continue;\n if (typeof value === \"function\") {\n names.push(name);\n }\n }\n if (names.length > 0) {\n actionsRegistry[page.path] = names;\n }\n }\n\n // The render config baked into the generated handler. The split router\n // chunk is advertised only when the bundle actually emitted it (the file\n // exists in the build output), so single-input legacy bundles keep working.\n const routerEnabled = options.router?.enabled !== false;\n const routerEntry =\n routerEnabled && options.js !== \"legacy\" &&\n existsSync(resolve(options.root, options.outDir, \"_elur\", \"router.js\"))\n ? \"/_elur/router.js\"\n : null;\n\n return `// AUTO-GENERATED by @elurjs/kit. Do not edit.\nimport { handleActionRequest, matchApiRoute, matchRoute, renderPage, renderPageBody, renderErrorPage, createStreamingResponse } from \"@elurjs/kit\";\n${imports}\n\nconst registry = new Map<string, unknown>([\n${modules.map((path, index) => ` [${JSON.stringify(path)}, m_${index}],`).join(\"\\n\")}\n]);\n\nconst pages = [\n${pages},\n];\n\nconst apiRoutes = [\n${apiRoutes}\n];\n\nconst actionModules = new Map<string, Map<string, unknown>>([\n${actionModules}\n]);\n\nconst actions = ${JSON.stringify(actionsRegistry)};\n\nconst routes = {\n pages,\n api: apiRoutes,\n error404: ${routes.error404 ? renderPageRecord(routes.error404) : \"undefined\"},\n error500: ${routes.error500 ? renderPageRecord(routes.error500) : \"undefined\"},\n};\n\nconst clientEntry = ${JSON.stringify(options.clientEntry)};\nconst lang = ${JSON.stringify(options.lang)};\n// Client router/JS emission rules baked at adapter build time.\nconst router = ${JSON.stringify({ enabled: routerEnabled, entry: routerEntry })};\nconst jsMode = ${JSON.stringify(options.js === \"legacy\" ? \"legacy\" : \"modern\")};\n// Opt-in streaming SSR (experimental): routes with a loading boundary stream\n// the shell first and swap in the resolved content as a follow-up chunk.\nconst streaming = ${options.streaming === true};\n\nfunction loadModule(path: string) {\n const mod = registry.get(path);\n if (mod) return mod;\n throw new Error(\\`Module not found in registry: \\${path}\\`);\n}\n\nasync function resolveAction(name: string, page?: string) {\n // Match concrete page paths (e.g. /movies/inception) to their route pattern\n // (/movies/:slug) so actions on dynamic routes resolve by scope.\n let pageKey: string | undefined;\n if (page) {\n pageKey = routes.pages.some((route) => route.path === page)\n ? page\n : (matchRoute(page, routes.pages)?.route.path ?? page);\n }\n const pageModules = pageKey ? actionModules.get(pageKey) : undefined;\n const candidates = pageModules ? [...pageModules.values()] : [];\n if (!pageModules) {\n for (const mods of actionModules.values()) {\n for (const mod of mods.values()) {\n const action = (mod as Record<string, unknown>)[name];\n if (typeof action === \"function\") return action;\n }\n }\n }\n for (const mod of candidates) {\n const action = (mod as Record<string, unknown>)[name];\n if (typeof action === \"function\") {\n return action as (...args: unknown[]) => unknown;\n }\n }\n return undefined;\n}\n\nexport default async function handler(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n if (url.pathname === \"/__elur-js/actions\") {\n return handleActionRequest(request, resolveAction);\n }\n\n // Render endpoint used by the SPA router and streaming boundaries.\n if (url.pathname === \"/__elur-js/render\") {\n const page = url.searchParams.get(\"page\") ?? \"/\";\n const search = url.searchParams.get(\"search\") ?? \"\";\n const wantsJson = (request.headers.get(\"Accept\") ?? \"\").includes(\"application/json\");\n try {\n const result = await renderPageBody({\n routes,\n pathname: page,\n searchParams: new URLSearchParams(search),\n config: { lang, clientEntry, router, js: jsMode },\n importer: loadModule,\n actions,\n request,\n });\n // A thrown Response from a loader is a first-class response (A-22).\n if (result.response) return result.response;\n const { body, title, head, clearActionErrorCookie, data, actions: actionsPayload } = result;\n if (wantsJson) {\n const headers = { \"Content-Type\": \"application/json; charset=utf-8\" };\n if (clearActionErrorCookie) headers[\"X-Elur-Action-Clear-Cookie\"] = clearActionErrorCookie;\n // The ?? null fallbacks keep every key present — JSON.stringify drops\n // undefined and the SPA payload shape must be stable across runtimes.\n return new Response(\n JSON.stringify({\n title,\n body,\n head: head ?? null,\n data: data ?? null,\n actions: actionsPayload ?? null,\n clearActionErrorCookie: clearActionErrorCookie ?? null,\n }),\n { status: 200, headers },\n );\n }\n const headers = { \"Content-Type\": \"text/html; charset=utf-8\" };\n if (clearActionErrorCookie) headers[\"Set-Cookie\"] = clearActionErrorCookie;\n return new Response(body, { status: 200, headers });\n } catch (err) {\n if ((err as { name?: string }).name === \"RouteNotFoundError\") {\n return new Response(\"Not Found\", {\n status: 404,\n headers: { \"Content-Type\": \"text/plain\" },\n });\n }\n console.error(\"[elur-kit] render endpoint error:\", err);\n return new Response(\"Internal Server Error\", {\n status: 500,\n headers: { \"Content-Type\": \"text/plain\" },\n });\n }\n }\n\n const apiMatch = matchApiRoute(url.pathname, apiRoutes);\n if (apiMatch) {\n const mod = apiMatch.route.routePath as Record<\n string,\n (request: Request, context?: { params: Record<string, string | string[]> }) => unknown\n >;\n const handler = mod[request.method ?? \"GET\"];\n if (typeof handler !== \"function\") {\n return new Response(\"Method not allowed: \" + request.method, { status: 405, headers: { \"Content-Type\": \"text/plain\" } });\n }\n return (await handler(request, { params: apiMatch.params })) as Response;\n }\n\n const match = matchRoute(url.pathname, routes.pages);\n if (!match) {\n const errorResult = await renderErrorPage({ routes, status: 404, config: { lang, clientEntry, router, js: jsMode }, actions, importer: loadModule });\n if (errorResult) {\n return new Response(errorResult.html, { status: errorResult.status, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n }\n return new Response(\"Not Found\", { status: 404, headers: { \"Content-Type\": \"text/plain\" } });\n }\n\n try {\n if (streaming && match.route.loadingPath) {\n // Streaming SSR: shell + loading boundary first, resolved content as a\n // follow-up chunk. Streamed pages are rendered live (no cache).\n return createStreamingResponse({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(url.search),\n config: { lang, clientEntry, router, js: jsMode },\n importer: loadModule,\n actions,\n request,\n signal: request.signal,\n });\n }\n const result = await renderPage({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(url.search),\n config: { lang, clientEntry, router, js: jsMode },\n importer: loadModule,\n actions,\n request,\n });\n // A thrown Response from a loader is a first-class response (A-22).\n if (result.response) return result.response;\n const headers = { \"Content-Type\": \"text/html; charset=utf-8\" };\n if (result.clearActionErrorCookie) headers[\"Set-Cookie\"] = result.clearActionErrorCookie;\n return new Response(result.html, { status: 200, headers });\n } catch (err) {\n console.error(\"[elur-kit] SSR render error:\", err);\n const errorResult = await renderErrorPage({ routes, status: 500, error: err, config: { lang, clientEntry, router, js: jsMode }, actions, importer: loadModule });\n if (errorResult) {\n return new Response(errorResult.html, { status: errorResult.status, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n }\n return new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n }\n}\n`;\n}\n\nfunction relativeToPosix(from: string, to: string): string {\n return relative(from, to).split(\"\\\\\").join(\"/\");\n}\n\n/**\n * Write a generated SSR entry file for an adapter.\n */\nexport async function writeSsrEntry(\n entryPath: string,\n routes: Awaited<ReturnType<typeof scanRoutes>>,\n options: AdapterOptions,\n): Promise<void> {\n await writeFile(\n entryPath,\n await buildSsrEntry(routes, options, dirname(entryPath)),\n \"utf8\",\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { copyFile as e, mkdir as t, readdir as n, writeFile as r } from "node:fs/promises";
|
|
2
|
-
import { dirname as i, join as a, relative as o } from "node:path";
|
|
3
|
-
import { existsSync as
|
|
2
|
+
import { dirname as i, join as a, relative as o, resolve as s } from "node:path";
|
|
3
|
+
import { existsSync as c } from "node:fs";
|
|
4
4
|
//#region src/adapters/shared.ts
|
|
5
|
-
async function
|
|
5
|
+
async function l(r, i) {
|
|
6
6
|
await t(i, { recursive: !0 });
|
|
7
7
|
let o = await n(r, { withFileTypes: !0 });
|
|
8
8
|
for (let t of o) {
|
|
9
9
|
let n = a(r, t.name), o = a(i, t.name);
|
|
10
|
-
t.isDirectory() ? await
|
|
10
|
+
t.isDirectory() ? await l(n, o) : await e(n, o);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function u(e, t, n) {
|
|
14
14
|
t.add(e.pagePath), e.dataPath && t.add(e.dataPath), e.loadingPath && t.add(e.loadingPath);
|
|
15
15
|
for (let n of e.layouts) {
|
|
16
16
|
t.add(n);
|
|
17
17
|
let e = n.replace(/layout\.ts$/, "layout.data.ts");
|
|
18
|
-
e !== n &&
|
|
18
|
+
e !== n && c(e) && t.add(e);
|
|
19
19
|
}
|
|
20
20
|
if (e.actionPath) {
|
|
21
21
|
t.add(e.actionPath);
|
|
@@ -23,15 +23,15 @@ function l(e, t, n) {
|
|
|
23
23
|
r || (r = /* @__PURE__ */ new Set(), n.set(e.path, r)), r.add(e.actionPath);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
async function
|
|
26
|
+
async function d(e, t, n) {
|
|
27
27
|
let r = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Map();
|
|
28
|
-
for (let t of e.pages)
|
|
29
|
-
e.error404 &&
|
|
28
|
+
for (let t of e.pages) u(t, r, i);
|
|
29
|
+
e.error404 && u(e.error404, r, i), e.error500 && u(e.error500, r, i);
|
|
30
30
|
for (let t of e.api) r.add(t.routePath);
|
|
31
|
-
let a = Array.from(r), o = new Map(a.map((e, t) => [e, t])),
|
|
32
|
-
let r =
|
|
31
|
+
let a = Array.from(r), o = new Map(a.map((e, t) => [e, t])), l = a.map((e, t) => {
|
|
32
|
+
let r = f(n, e);
|
|
33
33
|
return `import * as m_${t} from ${JSON.stringify(r)};`;
|
|
34
|
-
}).join("\n"),
|
|
34
|
+
}).join("\n"), d = (e) => `{
|
|
35
35
|
path: ${JSON.stringify(e.path)},
|
|
36
36
|
pagePath: ${JSON.stringify(e.pagePath)},
|
|
37
37
|
dataPath: ${JSON.stringify(e.dataPath ?? null)},
|
|
@@ -39,53 +39,63 @@ async function u(e, t, n) {
|
|
|
39
39
|
loadingPath: ${JSON.stringify(e.loadingPath ?? null)},
|
|
40
40
|
layouts: ${JSON.stringify(e.layouts)},
|
|
41
41
|
params: ${JSON.stringify(e.params)},
|
|
42
|
-
}`,
|
|
42
|
+
}`, p = e.pages.map(d).join(",\n"), m = e.api.map((e) => {
|
|
43
43
|
let t = o.get(e.routePath);
|
|
44
44
|
return ` { path: ${JSON.stringify(e.path)}, routePath: m_${t} },`;
|
|
45
|
-
}).join("\n"),
|
|
45
|
+
}).join("\n"), h = Array.from(i.entries()).map(([e, t]) => {
|
|
46
46
|
let n = Array.from(t).map((e) => {
|
|
47
47
|
let t = o.get(e);
|
|
48
48
|
return ` [${JSON.stringify(e)}, m_${t}],`;
|
|
49
49
|
}).join("\n");
|
|
50
50
|
return ` [${JSON.stringify(e)}, new Map([\n${n}\n ])],`;
|
|
51
|
-
}).join("\n"),
|
|
51
|
+
}).join("\n"), g = {};
|
|
52
52
|
for (let t of e.pages) {
|
|
53
53
|
if (!t.actionPath) continue;
|
|
54
54
|
let e = await import(t.actionPath), n = [];
|
|
55
55
|
for (let [t, r] of Object.entries(e)) t !== "default" && typeof r == "function" && n.push(t);
|
|
56
|
-
n.length > 0 && (
|
|
56
|
+
n.length > 0 && (g[t.path] = n);
|
|
57
57
|
}
|
|
58
|
+
let _ = t.router?.enabled !== !1, v = _ && t.js !== "legacy" && c(s(t.root, t.outDir, "_elur", "router.js")) ? "/_elur/router.js" : null;
|
|
58
59
|
return `// AUTO-GENERATED by @elurjs/kit. Do not edit.
|
|
59
|
-
import { handleActionRequest, matchApiRoute, matchRoute, renderPage, renderErrorPage } from "@elurjs/kit";
|
|
60
|
-
${
|
|
60
|
+
import { handleActionRequest, matchApiRoute, matchRoute, renderPage, renderPageBody, renderErrorPage, createStreamingResponse } from "@elurjs/kit";
|
|
61
|
+
${l}
|
|
61
62
|
|
|
62
63
|
const registry = new Map<string, unknown>([
|
|
63
64
|
${a.map((e, t) => ` [${JSON.stringify(e)}, m_${t}],`).join("\n")}
|
|
64
65
|
]);
|
|
65
66
|
|
|
66
67
|
const pages = [
|
|
67
|
-
${
|
|
68
|
+
${p},
|
|
68
69
|
];
|
|
69
70
|
|
|
70
71
|
const apiRoutes = [
|
|
71
|
-
${
|
|
72
|
+
${m}
|
|
72
73
|
];
|
|
73
74
|
|
|
74
75
|
const actionModules = new Map<string, Map<string, unknown>>([
|
|
75
|
-
${
|
|
76
|
+
${h}
|
|
76
77
|
]);
|
|
77
78
|
|
|
78
|
-
const actions = ${JSON.stringify(
|
|
79
|
+
const actions = ${JSON.stringify(g)};
|
|
79
80
|
|
|
80
81
|
const routes = {
|
|
81
82
|
pages,
|
|
82
83
|
api: apiRoutes,
|
|
83
|
-
error404: ${e.error404 ?
|
|
84
|
-
error500: ${e.error500 ?
|
|
84
|
+
error404: ${e.error404 ? d(e.error404) : "undefined"},
|
|
85
|
+
error500: ${e.error500 ? d(e.error500) : "undefined"},
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
const clientEntry = ${JSON.stringify(t.clientEntry)};
|
|
88
89
|
const lang = ${JSON.stringify(t.lang)};
|
|
90
|
+
// Client router/JS emission rules baked at adapter build time.
|
|
91
|
+
const router = ${JSON.stringify({
|
|
92
|
+
enabled: _,
|
|
93
|
+
entry: v
|
|
94
|
+
})};
|
|
95
|
+
const jsMode = ${JSON.stringify(t.js === "legacy" ? "legacy" : "modern")};
|
|
96
|
+
// Opt-in streaming SSR (experimental): routes with a loading boundary stream
|
|
97
|
+
// the shell first and swap in the resolved content as a follow-up chunk.
|
|
98
|
+
const streaming = ${t.streaming === !0};
|
|
89
99
|
|
|
90
100
|
function loadModule(path: string) {
|
|
91
101
|
const mod = registry.get(path);
|
|
@@ -134,37 +144,38 @@ export default async function handler(request: Request): Promise<Response> {
|
|
|
134
144
|
const search = url.searchParams.get("search") ?? "";
|
|
135
145
|
const wantsJson = (request.headers.get("Accept") ?? "").includes("application/json");
|
|
136
146
|
try {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
route: match.route,
|
|
141
|
-
params: match.params,
|
|
147
|
+
const result = await renderPageBody({
|
|
148
|
+
routes,
|
|
149
|
+
pathname: page,
|
|
142
150
|
searchParams: new URLSearchParams(search),
|
|
143
|
-
config: { lang, clientEntry },
|
|
151
|
+
config: { lang, clientEntry, router, js: jsMode },
|
|
144
152
|
importer: loadModule,
|
|
145
153
|
actions,
|
|
146
154
|
request,
|
|
147
155
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const body
|
|
151
|
-
? html.slice(bodyStart + 13, bodyEnd).trim()
|
|
152
|
-
: html;
|
|
153
|
-
const titleStart = html.indexOf("<title>");
|
|
154
|
-
const titleEnd = html.indexOf("</title>");
|
|
155
|
-
const title = titleStart >= 0 && titleEnd > titleStart
|
|
156
|
-
? html.slice(titleStart + 7, titleEnd)
|
|
157
|
-
: "";
|
|
156
|
+
// A thrown Response from a loader is a first-class response (A-22).
|
|
157
|
+
if (result.response) return result.response;
|
|
158
|
+
const { body, title, head, clearActionErrorCookie, data, actions: actionsPayload } = result;
|
|
158
159
|
if (wantsJson) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
const headers = { "Content-Type": "application/json; charset=utf-8" };
|
|
161
|
+
if (clearActionErrorCookie) headers["X-Elur-Action-Clear-Cookie"] = clearActionErrorCookie;
|
|
162
|
+
// The ?? null fallbacks keep every key present — JSON.stringify drops
|
|
163
|
+
// undefined and the SPA payload shape must be stable across runtimes.
|
|
164
|
+
return new Response(
|
|
165
|
+
JSON.stringify({
|
|
166
|
+
title,
|
|
167
|
+
body,
|
|
168
|
+
head: head ?? null,
|
|
169
|
+
data: data ?? null,
|
|
170
|
+
actions: actionsPayload ?? null,
|
|
171
|
+
clearActionErrorCookie: clearActionErrorCookie ?? null,
|
|
172
|
+
}),
|
|
173
|
+
{ status: 200, headers },
|
|
174
|
+
);
|
|
163
175
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
176
|
+
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
177
|
+
if (clearActionErrorCookie) headers["Set-Cookie"] = clearActionErrorCookie;
|
|
178
|
+
return new Response(body, { status: 200, headers });
|
|
168
179
|
} catch (err) {
|
|
169
180
|
if ((err as { name?: string }).name === "RouteNotFoundError") {
|
|
170
181
|
return new Response("Not Found", {
|
|
@@ -195,7 +206,7 @@ export default async function handler(request: Request): Promise<Response> {
|
|
|
195
206
|
|
|
196
207
|
const match = matchRoute(url.pathname, routes.pages);
|
|
197
208
|
if (!match) {
|
|
198
|
-
const errorResult = await renderErrorPage({ routes, status: 404, config: { lang, clientEntry }, actions, importer: loadModule });
|
|
209
|
+
const errorResult = await renderErrorPage({ routes, status: 404, config: { lang, clientEntry, router, js: jsMode }, actions, importer: loadModule });
|
|
199
210
|
if (errorResult) {
|
|
200
211
|
return new Response(errorResult.html, { status: errorResult.status, headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
201
212
|
}
|
|
@@ -203,19 +214,37 @@ export default async function handler(request: Request): Promise<Response> {
|
|
|
203
214
|
}
|
|
204
215
|
|
|
205
216
|
try {
|
|
206
|
-
|
|
217
|
+
if (streaming && match.route.loadingPath) {
|
|
218
|
+
// Streaming SSR: shell + loading boundary first, resolved content as a
|
|
219
|
+
// follow-up chunk. Streamed pages are rendered live (no cache).
|
|
220
|
+
return createStreamingResponse({
|
|
221
|
+
route: match.route,
|
|
222
|
+
params: match.params,
|
|
223
|
+
searchParams: new URLSearchParams(url.search),
|
|
224
|
+
config: { lang, clientEntry, router, js: jsMode },
|
|
225
|
+
importer: loadModule,
|
|
226
|
+
actions,
|
|
227
|
+
request,
|
|
228
|
+
signal: request.signal,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const result = await renderPage({
|
|
207
232
|
route: match.route,
|
|
208
233
|
params: match.params,
|
|
209
234
|
searchParams: new URLSearchParams(url.search),
|
|
210
|
-
config: { lang, clientEntry },
|
|
235
|
+
config: { lang, clientEntry, router, js: jsMode },
|
|
211
236
|
importer: loadModule,
|
|
212
237
|
actions,
|
|
213
238
|
request,
|
|
214
239
|
});
|
|
215
|
-
|
|
240
|
+
// A thrown Response from a loader is a first-class response (A-22).
|
|
241
|
+
if (result.response) return result.response;
|
|
242
|
+
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
243
|
+
if (result.clearActionErrorCookie) headers["Set-Cookie"] = result.clearActionErrorCookie;
|
|
244
|
+
return new Response(result.html, { status: 200, headers });
|
|
216
245
|
} catch (err) {
|
|
217
246
|
console.error("[elur-kit] SSR render error:", err);
|
|
218
|
-
const errorResult = await renderErrorPage({ routes, status: 500, error: err, config: { lang, clientEntry }, actions, importer: loadModule });
|
|
247
|
+
const errorResult = await renderErrorPage({ routes, status: 500, error: err, config: { lang, clientEntry, router, js: jsMode }, actions, importer: loadModule });
|
|
219
248
|
if (errorResult) {
|
|
220
249
|
return new Response(errorResult.html, { status: errorResult.status, headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
221
250
|
}
|
|
@@ -224,13 +253,13 @@ export default async function handler(request: Request): Promise<Response> {
|
|
|
224
253
|
}
|
|
225
254
|
`;
|
|
226
255
|
}
|
|
227
|
-
function
|
|
256
|
+
function f(e, t) {
|
|
228
257
|
return o(e, t).split("\\").join("/");
|
|
229
258
|
}
|
|
230
|
-
async function
|
|
231
|
-
await r(e, await
|
|
259
|
+
async function p(e, t, n) {
|
|
260
|
+
await r(e, await d(t, n, i(e)), "utf8");
|
|
232
261
|
}
|
|
233
262
|
//#endregion
|
|
234
|
-
export {
|
|
263
|
+
export { p as n, l as t };
|
|
235
264
|
|
|
236
|
-
//# sourceMappingURL=shared-
|
|
265
|
+
//# sourceMappingURL=shared-D8w-neq2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-D8w-neq2.js","names":[],"sources":["../../src/adapters/shared.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readdir, copyFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { scanRoutes } from \"../router/route-scanner.js\";\nimport type { AdapterOptions } from \"./index.js\";\nimport type { PageRoute } from \"../router/route-scanner.js\";\n\n/**\n * Shared helper: copy a directory recursively.\n */\nexport async function copyStatic(from: string, to: string): Promise<void> {\n await mkdir(to, { recursive: true });\n const entries = await readdir(from, { withFileTypes: true });\n for (const entry of entries) {\n const src = join(from, entry.name);\n const dest = join(to, entry.name);\n if (entry.isDirectory()) {\n await copyStatic(src, dest);\n } else {\n await copyFile(src, dest);\n }\n }\n}\n\n/** Adds every module the SSR runtime may import for a page to the registry. */\nfunction collectPageModules(\n page: PageRoute,\n moduleSet: Set<string>,\n actionPathsByPage: Map<string, Set<string>>,\n): void {\n moduleSet.add(page.pagePath);\n if (page.dataPath) moduleSet.add(page.dataPath);\n if (page.loadingPath) moduleSet.add(page.loadingPath);\n for (const layout of page.layouts) {\n moduleSet.add(layout);\n const layoutDataPath = layout.replace(/layout\\.ts$/, \"layout.data.ts\");\n if (layoutDataPath !== layout && existsSync(layoutDataPath)) {\n moduleSet.add(layoutDataPath);\n }\n }\n if (page.actionPath) {\n moduleSet.add(page.actionPath);\n let set = actionPathsByPage.get(page.path);\n if (!set) {\n set = new Set<string>();\n actionPathsByPage.set(page.path, set);\n }\n set.add(page.actionPath);\n }\n}\n\n/**\n * Build a self-contained SSR entry file for a platform adapter.\n * The generated module exports a default `handler(request: Request): Response`\n * and embeds the full route table plus a registry of all page/layout/data/\n * action modules so the runtime never touches the file system.\n */\nexport async function buildSsrEntry(\n routes: Awaited<ReturnType<typeof scanRoutes>>,\n options: AdapterOptions,\n entryDir: string,\n): Promise<string> {\n // Collect all module paths that the SSR runtime may need to import.\n const moduleSet = new Set<string>();\n const actionPathsByPage = new Map<string, Set<string>>();\n for (const page of routes.pages) {\n collectPageModules(page, moduleSet, actionPathsByPage);\n }\n if (routes.error404) collectPageModules(routes.error404, moduleSet, actionPathsByPage);\n if (routes.error500) collectPageModules(routes.error500, moduleSet, actionPathsByPage);\n for (const api of routes.api) {\n moduleSet.add(api.routePath);\n }\n const modules = Array.from(moduleSet);\n const moduleIndex = new Map(modules.map((path, index) => [path, index]));\n\n const imports = modules\n .map((path, index) => {\n const rel = relativeToPosix(entryDir, path);\n return `import * as m_${index} from ${JSON.stringify(rel)};`;\n })\n .join(\"\\n\");\n\n const renderPageRecord = (page: PageRoute): string => `{\n path: ${JSON.stringify(page.path)},\n pagePath: ${JSON.stringify(page.pagePath)},\n dataPath: ${JSON.stringify(page.dataPath ?? null)},\n actionPath: ${JSON.stringify(page.actionPath ?? null)},\n loadingPath: ${JSON.stringify(page.loadingPath ?? null)},\n layouts: ${JSON.stringify(page.layouts)},\n params: ${JSON.stringify(page.params)},\n }`;\n\n const pages = routes.pages.map(renderPageRecord).join(\",\\n\");\n\n const apiRoutes = routes.api\n .map((api) => {\n const index = moduleIndex.get(api.routePath);\n return ` { path: ${JSON.stringify(api.path)}, routePath: m_${index} },`;\n })\n .join(\"\\n\");\n\n const actionModules = Array.from(actionPathsByPage.entries())\n .map(([pagePath, paths]) => {\n const entries = Array.from(paths)\n .map((path) => {\n const index = moduleIndex.get(path);\n return ` [${JSON.stringify(path)}, m_${index}],`;\n })\n .join(\"\\n\");\n return ` [${JSON.stringify(pagePath)}, new Map([\\n${entries}\\n ])],`;\n })\n .join(\"\\n\");\n\n const actionsRegistry: Record<string, string[]> = {};\n for (const page of routes.pages) {\n if (!page.actionPath) continue;\n const mod = (await import(page.actionPath)) as Record<string, unknown>;\n const names: string[] = [];\n for (const [name, value] of Object.entries(mod)) {\n if (name === \"default\") continue;\n if (typeof value === \"function\") {\n names.push(name);\n }\n }\n if (names.length > 0) {\n actionsRegistry[page.path] = names;\n }\n }\n\n // The render config baked into the generated handler. The split router\n // chunk is advertised only when the bundle actually emitted it (the file\n // exists in the build output), so single-input legacy bundles keep working.\n const routerEnabled = options.router?.enabled !== false;\n const routerEntry =\n routerEnabled && options.js !== \"legacy\" &&\n existsSync(resolve(options.root, options.outDir, \"_elur\", \"router.js\"))\n ? \"/_elur/router.js\"\n : null;\n\n return `// AUTO-GENERATED by @elurjs/kit. Do not edit.\nimport { handleActionRequest, matchApiRoute, matchRoute, renderPage, renderPageBody, renderErrorPage, createStreamingResponse } from \"@elurjs/kit\";\n${imports}\n\nconst registry = new Map<string, unknown>([\n${modules.map((path, index) => ` [${JSON.stringify(path)}, m_${index}],`).join(\"\\n\")}\n]);\n\nconst pages = [\n${pages},\n];\n\nconst apiRoutes = [\n${apiRoutes}\n];\n\nconst actionModules = new Map<string, Map<string, unknown>>([\n${actionModules}\n]);\n\nconst actions = ${JSON.stringify(actionsRegistry)};\n\nconst routes = {\n pages,\n api: apiRoutes,\n error404: ${routes.error404 ? renderPageRecord(routes.error404) : \"undefined\"},\n error500: ${routes.error500 ? renderPageRecord(routes.error500) : \"undefined\"},\n};\n\nconst clientEntry = ${JSON.stringify(options.clientEntry)};\nconst lang = ${JSON.stringify(options.lang)};\n// Client router/JS emission rules baked at adapter build time.\nconst router = ${JSON.stringify({ enabled: routerEnabled, entry: routerEntry })};\nconst jsMode = ${JSON.stringify(options.js === \"legacy\" ? \"legacy\" : \"modern\")};\n// Opt-in streaming SSR (experimental): routes with a loading boundary stream\n// the shell first and swap in the resolved content as a follow-up chunk.\nconst streaming = ${options.streaming === true};\n\nfunction loadModule(path: string) {\n const mod = registry.get(path);\n if (mod) return mod;\n throw new Error(\\`Module not found in registry: \\${path}\\`);\n}\n\nasync function resolveAction(name: string, page?: string) {\n // Match concrete page paths (e.g. /movies/inception) to their route pattern\n // (/movies/:slug) so actions on dynamic routes resolve by scope.\n let pageKey: string | undefined;\n if (page) {\n pageKey = routes.pages.some((route) => route.path === page)\n ? page\n : (matchRoute(page, routes.pages)?.route.path ?? page);\n }\n const pageModules = pageKey ? actionModules.get(pageKey) : undefined;\n const candidates = pageModules ? [...pageModules.values()] : [];\n if (!pageModules) {\n for (const mods of actionModules.values()) {\n for (const mod of mods.values()) {\n const action = (mod as Record<string, unknown>)[name];\n if (typeof action === \"function\") return action;\n }\n }\n }\n for (const mod of candidates) {\n const action = (mod as Record<string, unknown>)[name];\n if (typeof action === \"function\") {\n return action as (...args: unknown[]) => unknown;\n }\n }\n return undefined;\n}\n\nexport default async function handler(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n if (url.pathname === \"/__elur-js/actions\") {\n return handleActionRequest(request, resolveAction);\n }\n\n // Render endpoint used by the SPA router and streaming boundaries.\n if (url.pathname === \"/__elur-js/render\") {\n const page = url.searchParams.get(\"page\") ?? \"/\";\n const search = url.searchParams.get(\"search\") ?? \"\";\n const wantsJson = (request.headers.get(\"Accept\") ?? \"\").includes(\"application/json\");\n try {\n const result = await renderPageBody({\n routes,\n pathname: page,\n searchParams: new URLSearchParams(search),\n config: { lang, clientEntry, router, js: jsMode },\n importer: loadModule,\n actions,\n request,\n });\n // A thrown Response from a loader is a first-class response (A-22).\n if (result.response) return result.response;\n const { body, title, head, clearActionErrorCookie, data, actions: actionsPayload } = result;\n if (wantsJson) {\n const headers = { \"Content-Type\": \"application/json; charset=utf-8\" };\n if (clearActionErrorCookie) headers[\"X-Elur-Action-Clear-Cookie\"] = clearActionErrorCookie;\n // The ?? null fallbacks keep every key present — JSON.stringify drops\n // undefined and the SPA payload shape must be stable across runtimes.\n return new Response(\n JSON.stringify({\n title,\n body,\n head: head ?? null,\n data: data ?? null,\n actions: actionsPayload ?? null,\n clearActionErrorCookie: clearActionErrorCookie ?? null,\n }),\n { status: 200, headers },\n );\n }\n const headers = { \"Content-Type\": \"text/html; charset=utf-8\" };\n if (clearActionErrorCookie) headers[\"Set-Cookie\"] = clearActionErrorCookie;\n return new Response(body, { status: 200, headers });\n } catch (err) {\n if ((err as { name?: string }).name === \"RouteNotFoundError\") {\n return new Response(\"Not Found\", {\n status: 404,\n headers: { \"Content-Type\": \"text/plain\" },\n });\n }\n console.error(\"[elur-kit] render endpoint error:\", err);\n return new Response(\"Internal Server Error\", {\n status: 500,\n headers: { \"Content-Type\": \"text/plain\" },\n });\n }\n }\n\n const apiMatch = matchApiRoute(url.pathname, apiRoutes);\n if (apiMatch) {\n const mod = apiMatch.route.routePath as Record<\n string,\n (request: Request, context?: { params: Record<string, string | string[]> }) => unknown\n >;\n const handler = mod[request.method ?? \"GET\"];\n if (typeof handler !== \"function\") {\n return new Response(\"Method not allowed: \" + request.method, { status: 405, headers: { \"Content-Type\": \"text/plain\" } });\n }\n return (await handler(request, { params: apiMatch.params })) as Response;\n }\n\n const match = matchRoute(url.pathname, routes.pages);\n if (!match) {\n const errorResult = await renderErrorPage({ routes, status: 404, config: { lang, clientEntry, router, js: jsMode }, actions, importer: loadModule });\n if (errorResult) {\n return new Response(errorResult.html, { status: errorResult.status, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n }\n return new Response(\"Not Found\", { status: 404, headers: { \"Content-Type\": \"text/plain\" } });\n }\n\n try {\n if (streaming && match.route.loadingPath) {\n // Streaming SSR: shell + loading boundary first, resolved content as a\n // follow-up chunk. Streamed pages are rendered live (no cache).\n return createStreamingResponse({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(url.search),\n config: { lang, clientEntry, router, js: jsMode },\n importer: loadModule,\n actions,\n request,\n signal: request.signal,\n });\n }\n const result = await renderPage({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(url.search),\n config: { lang, clientEntry, router, js: jsMode },\n importer: loadModule,\n actions,\n request,\n });\n // A thrown Response from a loader is a first-class response (A-22).\n if (result.response) return result.response;\n const headers = { \"Content-Type\": \"text/html; charset=utf-8\" };\n if (result.clearActionErrorCookie) headers[\"Set-Cookie\"] = result.clearActionErrorCookie;\n return new Response(result.html, { status: 200, headers });\n } catch (err) {\n console.error(\"[elur-kit] SSR render error:\", err);\n const errorResult = await renderErrorPage({ routes, status: 500, error: err, config: { lang, clientEntry, router, js: jsMode }, actions, importer: loadModule });\n if (errorResult) {\n return new Response(errorResult.html, { status: errorResult.status, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n }\n return new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n }\n}\n`;\n}\n\nfunction relativeToPosix(from: string, to: string): string {\n return relative(from, to).split(\"\\\\\").join(\"/\");\n}\n\n/**\n * Write a generated SSR entry file for an adapter.\n */\nexport async function writeSsrEntry(\n entryPath: string,\n routes: Awaited<ReturnType<typeof scanRoutes>>,\n options: AdapterOptions,\n): Promise<void> {\n await writeFile(\n entryPath,\n await buildSsrEntry(routes, options, dirname(entryPath)),\n \"utf8\",\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { mkdir as e, writeFile as t } from "node:fs/promises";
|
|
2
|
+
import { dirname as n, join as r } from "node:path";
|
|
3
|
+
//#region src/seo/index.ts
|
|
4
|
+
async function i(i) {
|
|
5
|
+
let { siteUrl: a, urls: o, outDir: c } = i, l = a.replace(/\/$/, ""), u = `<?xml version="1.0" encoding="UTF-8"?>
|
|
6
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
7
|
+
${o.map((e) => {
|
|
8
|
+
let t = typeof e == "string" ? { url: e } : e, n = [" <url>", ` <loc>${s(`${l}${t.url.startsWith("/") ? "" : "/"}${t.url}`)}</loc>`];
|
|
9
|
+
return t.lastmod && n.push(` <lastmod>${t.lastmod}</lastmod>`), t.changefreq && n.push(` <changefreq>${t.changefreq}</changefreq>`), t.priority !== void 0 && n.push(` <priority>${t.priority.toFixed(1)}</priority>`), n.push(" </url>"), n.join("\n");
|
|
10
|
+
}).join("\n")}
|
|
11
|
+
</urlset>
|
|
12
|
+
`, d = r(c, "sitemap.xml");
|
|
13
|
+
return await e(n(d), { recursive: !0 }), await t(d, u, "utf8"), d;
|
|
14
|
+
}
|
|
15
|
+
async function a(i) {
|
|
16
|
+
let { siteUrl: a, outDir: o, rules: s, disallow: c, sitemapUrl: l } = i, u = a.replace(/\/$/, ""), d = [];
|
|
17
|
+
if (s && s.length > 0) for (let e of s) {
|
|
18
|
+
if (d.push(`User-agent: ${e.userAgent}`), e.allow) for (let t of e.allow) d.push(`Allow: ${t}`);
|
|
19
|
+
if (e.disallow) for (let t of e.disallow) d.push(`Disallow: ${t}`);
|
|
20
|
+
e.crawlDelay !== void 0 && d.push(`Crawl-delay: ${e.crawlDelay}`), d.push("");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
if (d.push("User-agent: *"), c && c.length > 0) for (let e of c) d.push(`Disallow: ${e}`);
|
|
24
|
+
else d.push("Disallow:");
|
|
25
|
+
d.push("");
|
|
26
|
+
}
|
|
27
|
+
let f = l ?? `${u}/sitemap.xml`;
|
|
28
|
+
d.push(`Sitemap: ${f}`);
|
|
29
|
+
let p = d.join("\n") + "\n", m = r(o, "robots.txt");
|
|
30
|
+
return await e(n(m), { recursive: !0 }), await t(m, p, "utf8"), m;
|
|
31
|
+
}
|
|
32
|
+
function o(e) {
|
|
33
|
+
return `<script type="application/ld+json">${JSON.stringify(e).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}<\/script>`;
|
|
34
|
+
}
|
|
35
|
+
function s(e) {
|
|
36
|
+
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/seo/sitemap-from-routes.ts
|
|
40
|
+
async function c(e) {
|
|
41
|
+
let { siteUrl: t, outDir: n, routes: r, extraUrls: a = [], maxUrlsPerSitemap: o = 5e4 } = e, s = [];
|
|
42
|
+
for (let t of r.pages) t.params.length > 0 || t.path !== "/404" && t.path !== "/500" && (t.path.startsWith("/_elur") || t.path.startsWith("/__elur-js") || s.push({
|
|
43
|
+
url: t.path,
|
|
44
|
+
changefreq: e.defaultChangefreq,
|
|
45
|
+
priority: e.defaultPriority
|
|
46
|
+
}));
|
|
47
|
+
let c = [...s, ...a];
|
|
48
|
+
return c.length <= o ? [await i({
|
|
49
|
+
siteUrl: t,
|
|
50
|
+
outDir: n,
|
|
51
|
+
urls: c
|
|
52
|
+
})] : l({
|
|
53
|
+
siteUrl: t,
|
|
54
|
+
outDir: n,
|
|
55
|
+
urls: c,
|
|
56
|
+
maxUrlsPerSitemap: o
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function l(e) {
|
|
60
|
+
let { siteUrl: t, outDir: n, urls: r, maxUrlsPerSitemap: i } = e, a = t.replace(/\/$/, ""), o = [], s = [];
|
|
61
|
+
for (let e = 0; e < r.length; e += i) {
|
|
62
|
+
let t = r.slice(e, e + i), c = `sitemap-${Math.floor(e / i) + 1}.xml`, { writeFile: l, mkdir: d } = await import("node:fs/promises"), { join: f, dirname: p } = await import("node:path"), m = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${t.map((e) => {
|
|
63
|
+
let t = typeof e == "string" ? { url: e } : e, n = [" <url>", ` <loc>${u(`${a}${t.url.startsWith("/") ? "" : "/"}${t.url}`)}</loc>`];
|
|
64
|
+
return t.lastmod && n.push(` <lastmod>${t.lastmod}</lastmod>`), t.changefreq && n.push(` <changefreq>${t.changefreq}</changefreq>`), t.priority !== void 0 && n.push(` <priority>${t.priority.toFixed(1)}</priority>`), n.push(" </url>"), n.join("\n");
|
|
65
|
+
}).join("\n")}\n</urlset>\n`, h = f(n, c);
|
|
66
|
+
await d(p(h), { recursive: !0 }), await l(h, m, "utf8"), o.push(h), s.push(`${a}/${c}`);
|
|
67
|
+
}
|
|
68
|
+
let { writeFile: c, mkdir: l } = await import("node:fs/promises"), { join: d, dirname: f } = await import("node:path"), p = `<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${s.map((e) => ` <sitemap>\n <loc>${u(e)}</loc>\n </sitemap>`).join("\n")}\n</sitemapindex>\n`, m = d(n, "sitemap.xml");
|
|
69
|
+
return await l(f(m), { recursive: !0 }), await c(m, p, "utf8"), o.push(m), o;
|
|
70
|
+
}
|
|
71
|
+
function u(e) {
|
|
72
|
+
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { o as i, a as n, i as r, c as t };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=sitemap-from-routes-CaBoiGvE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-from-routes-CaBoiGvE.js","names":[],"sources":["../../src/seo/index.ts","../../src/seo/sitemap-from-routes.ts"],"sourcesContent":["/**\n * SEO utilities — sitemap.xml and robots.txt generation.\n *\n * @module\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\n// Sitemap generation from the scanned route manifest (wired into `build`).\nexport { generateSitemapFromRoutes, type SitemapFromRoutesOptions } from \"./sitemap-from-routes.js\";\n\n// Types\n\nexport interface SitemapEntry {\n /** URL path, e.g. \"/docs/getting-started/introduction\". */\n url: string;\n /** Last modification date (ISO 8601 or YYYY-MM-DD). */\n lastmod?: string;\n /** Change frequency: always, hourly, daily, weekly, monthly, yearly, never. */\n changefreq?: \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n /** Priority 0.0–1.0. */\n priority?: number;\n}\n\nexport interface SitemapConfig {\n /** Base URL of the site, e.g. \"https://example.com\". */\n siteUrl: string;\n /** List of URL entries to include in the sitemap. */\n urls: (SitemapEntry | string)[];\n /** Output directory where sitemap.xml will be written. */\n outDir: string;\n}\n\nexport interface RobotsConfig {\n /** Base URL of the site, e.g. \"https://example.com\". */\n siteUrl: string;\n /** Output directory where robots.txt will be written. */\n outDir: string;\n /** Rules for specific user agents. */\n rules?: RobotsRule[];\n /** Paths to disallow for all crawlers (shorthand for rules). */\n disallow?: string[];\n /** Sitemap URL override. If not set, defaults to `${siteUrl}/sitemap.xml`. */\n sitemapUrl?: string;\n}\n\nexport interface RobotsRule {\n /** User-agent, e.g. \"Googlebot\" or \"*\" for all. */\n userAgent: string;\n /** Paths to disallow. */\n disallow?: string[];\n /** Paths to allow. */\n allow?: string[];\n /** Crawl delay in seconds. */\n crawlDelay?: number;\n}\n\n// Sitemap generation\n\n/**\n * Generates a `sitemap.xml` file from a list of URLs.\n *\n * @example\n * ```ts\n * import { generateSitemap } from \"@elurjs/kit/seo\";\n *\n * await generateSitemap({\n * siteUrl: \"https://elur-kit.dev\",\n * outDir: \"./dist\",\n * urls: [\n * \"/\",\n * \"/docs/introduction\",\n * { url: \"/docs/routing\", changefreq: \"weekly\", priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport async function generateSitemap(config: SitemapConfig): Promise<string> {\n const { siteUrl, urls, outDir } = config;\n const base = siteUrl.replace(/\\/$/, \"\");\n\n const entries: string[] = urls.map((entry) => {\n const e = typeof entry === \"string\" ? { url: entry } : entry;\n const loc = `${base}${e.url.startsWith(\"/\") ? \"\" : \"/\"}${e.url}`;\n const lines = [` <url>`, ` <loc>${escapeXml(loc)}</loc>`];\n if (e.lastmod) lines.push(` <lastmod>${e.lastmod}</lastmod>`);\n if (e.changefreq) lines.push(` <changefreq>${e.changefreq}</changefreq>`);\n if (e.priority !== undefined) lines.push(` <priority>${e.priority.toFixed(1)}</priority>`);\n lines.push(` </url>`);\n return lines.join(\"\\n\");\n });\n\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${entries.join(\"\\n\")}\n</urlset>\n`;\n\n const filePath = join(outDir, \"sitemap.xml\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, xml, \"utf8\");\n return filePath;\n}\n\n// Robots.txt generation\n\n/**\n * Generates a `robots.txt` file.\n *\n * @example\n * ```ts\n * import { generateRobots } from \"@elurjs/kit/seo\";\n *\n * await generateRobots({\n * siteUrl: \"https://elur-kit.dev\",\n * outDir: \"./dist\",\n * disallow: [\"/api/\", \"/_elur/\"],\n * });\n * ```\n */\nexport async function generateRobots(config: RobotsConfig): Promise<string> {\n const { siteUrl, outDir, rules, disallow, sitemapUrl } = config;\n const base = siteUrl.replace(/\\/$/, \"\");\n const lines: string[] = [];\n\n if (rules && rules.length > 0) {\n for (const rule of rules) {\n lines.push(`User-agent: ${rule.userAgent}`);\n if (rule.allow) {\n for (const path of rule.allow) lines.push(`Allow: ${path}`);\n }\n if (rule.disallow) {\n for (const path of rule.disallow) lines.push(`Disallow: ${path}`);\n }\n if (rule.crawlDelay !== undefined) {\n lines.push(`Crawl-delay: ${rule.crawlDelay}`);\n }\n lines.push(\"\");\n }\n } else {\n lines.push(\"User-agent: *\");\n if (disallow && disallow.length > 0) {\n for (const path of disallow) lines.push(`Disallow: ${path}`);\n } else {\n lines.push(\"Disallow:\");\n }\n lines.push(\"\");\n }\n\n const sitemap = sitemapUrl ?? `${base}/sitemap.xml`;\n lines.push(`Sitemap: ${sitemap}`);\n\n const content = lines.join(\"\\n\") + \"\\n\";\n const filePath = join(outDir, \"robots.txt\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, content, \"utf8\");\n return filePath;\n}\n\n// JSON-LD / Structured data\n\nexport interface JsonLdSchema {\n [key: string]: unknown;\n}\n\n/**\n * Serializes a JSON-LD structured data object into a `<script type=\"application/ld+json\">` tag.\n *\n * @example\n * ```ts\n * import { jsonLd } from \"@elurjs/kit/seo\";\n *\n * const schema = jsonLd({\n * \"@context\": \"https://schema.org\",\n * \"@type\": \"TechArticle\",\n * headline: \"Routing\",\n * author: { \"@type\": \"Person\", name: \"Deiver Vasquez\" },\n * });\n * // Returns: <script type=\"application/ld+json\">{...}</script>\n * ```\n */\nexport function jsonLd(schema: JsonLdSchema | JsonLdSchema[]): string {\n const data = JSON.stringify(Array.isArray(schema) ? schema : schema);\n // Escape sequences that could close the <script> tag or introduce markup.\n // Per the HTML spec, inside a <script> block the only dangerous sequence\n // is \"</script\" (case-insensitive). We also escape \"<\" more broadly to\n // prevent any interpreter from seeing markup-like content, and escape\n // \"<!--\" to prevent HTML comment-based escapes.\n const safe = data\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n return `<script type=\"application/ld+json\">${safe}</script>`;\n}\n\n// Helpers\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","// --- Sitemap generation from route manifest (plan §11.3) ---\n//\n// Generates a sitemap.xml from the scanned routes, excluding:\n// - API routes\n// - Dynamic routes (they require data to generate URLs)\n// - Error pages (404, 500)\n// - Routes with noindex metadata\n//\n// For dynamic routes, the author should provide a `generateSitemapUrls()`\n// function in their page.data.ts that returns concrete URLs.\n//\n// Supports large sitemaps via sitemap index files (split at 50,000 URLs).\n\nimport type { ScannedRoutes } from \"../router/route-scanner.js\";\nimport { generateSitemap, type SitemapEntry } from \"./index.js\";\n\nexport interface SitemapFromRoutesOptions {\n siteUrl: string;\n outDir: string;\n routes: ScannedRoutes;\n /** Additional URLs to include (e.g. from dynamic routes). */\n extraUrls?: (SitemapEntry | string)[];\n /** Max URLs per sitemap file. Default: 50000. */\n maxUrlsPerSitemap?: number;\n /** Default changefreq for routes. */\n defaultChangefreq?: SitemapEntry[\"changefreq\"];\n /** Default priority for routes. */\n defaultPriority?: number;\n}\n\n/**\n * Generates a sitemap.xml from the route manifest.\n *\n * Static routes are included automatically. Dynamic routes require the author\n * to provide URLs via `extraUrls` or a `generateSitemapUrls()` export.\n *\n * For large sites (>50,000 URLs), a sitemap index is generated.\n */\nexport async function generateSitemapFromRoutes(\n options: SitemapFromRoutesOptions,\n): Promise<string[]> {\n const { siteUrl, outDir, routes, extraUrls = [], maxUrlsPerSitemap = 50000 } = options;\n\n // Collect static route URLs.\n const routeUrls: SitemapEntry[] = [];\n for (const page of routes.pages) {\n // Skip dynamic routes (they have params).\n if (page.params.length > 0) continue;\n // Skip error pages.\n if (page.path === \"/404\" || page.path === \"/500\") continue;\n // Skip internal namespaces.\n if (page.path.startsWith(\"/_elur\") || page.path.startsWith(\"/__elur-js\")) continue;\n\n routeUrls.push({\n url: page.path,\n changefreq: options.defaultChangefreq,\n priority: options.defaultPriority,\n });\n }\n\n // Merge with extra URLs.\n const allUrls = [...routeUrls, ...extraUrls];\n\n // If under the limit, generate a single sitemap.\n if (allUrls.length <= maxUrlsPerSitemap) {\n const path = await generateSitemap({ siteUrl, outDir, urls: allUrls });\n return [path];\n }\n\n // For large sitemaps, split into multiple files with an index.\n return generateSitemapIndex({ siteUrl, outDir, urls: allUrls, maxUrlsPerSitemap });\n}\n\n/**\n * Generates a sitemap index file that references multiple sitemap files.\n * Used for large sites (>50,000 URLs).\n */\nasync function generateSitemapIndex(\n options: { siteUrl: string; outDir: string; urls: (SitemapEntry | string)[]; maxUrlsPerSitemap: number },\n): Promise<string[]> {\n const { siteUrl, outDir, urls, maxUrlsPerSitemap } = options;\n const base = siteUrl.replace(/\\/$/, \"\");\n const files: string[] = [];\n const sitemapUrls: string[] = [];\n\n // Split URLs into chunks.\n for (let i = 0; i < urls.length; i += maxUrlsPerSitemap) {\n const chunk = urls.slice(i, i + maxUrlsPerSitemap);\n const filename = `sitemap-${Math.floor(i / maxUrlsPerSitemap) + 1}.xml`;\n const { writeFile, mkdir } = await import(\"node:fs/promises\");\n const { join, dirname } = await import(\"node:path\");\n\n // Generate the chunk sitemap.\n const entries = chunk.map((entry) => {\n const e = typeof entry === \"string\" ? { url: entry } : entry;\n const loc = `${base}${e.url.startsWith(\"/\") ? \"\" : \"/\"}${e.url}`;\n const lines = [` <url>`, ` <loc>${escapeXml(loc)}</loc>`];\n if (e.lastmod) lines.push(` <lastmod>${e.lastmod}</lastmod>`);\n if (e.changefreq) lines.push(` <changefreq>${e.changefreq}</changefreq>`);\n if (e.priority !== undefined) lines.push(` <priority>${e.priority.toFixed(1)}</priority>`);\n lines.push(` </url>`);\n return lines.join(\"\\n\");\n });\n\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${entries.join(\"\\n\")}\\n</urlset>\\n`;\n const filePath = join(outDir, filename);\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, xml, \"utf8\");\n files.push(filePath);\n sitemapUrls.push(`${base}/${filename}`);\n }\n\n // Generate the index file.\n const { writeFile, mkdir } = await import(\"node:fs/promises\");\n const { join, dirname } = await import(\"node:path\");\n\n const indexEntries = sitemapUrls.map((url) => ` <sitemap>\\n <loc>${escapeXml(url)}</loc>\\n </sitemap>`).join(\"\\n\");\n const indexXml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${indexEntries}\\n</sitemapindex>\\n`;\n const indexPath = join(outDir, \"sitemap.xml\");\n await mkdir(dirname(indexPath), { recursive: true });\n await writeFile(indexPath, indexXml, \"utf8\");\n files.push(indexPath);\n\n return files;\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
let e=require("node:fs/promises"),t=require("node:path");async function n(n){let{siteUrl:r,urls:i,outDir:o}=n,s=r.replace(/\/$/,``),c=`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
|
+
${i.map(e=>{let t=typeof e==`string`?{url:e}:e,n=[` <url>`,` <loc>${a(`${s}${t.url.startsWith(`/`)?``:`/`}${t.url}`)}</loc>`];return t.lastmod&&n.push(` <lastmod>${t.lastmod}</lastmod>`),t.changefreq&&n.push(` <changefreq>${t.changefreq}</changefreq>`),t.priority!==void 0&&n.push(` <priority>${t.priority.toFixed(1)}</priority>`),n.push(` </url>`),n.join(`
|
|
4
|
+
`)}).join(`
|
|
5
|
+
`)}
|
|
6
|
+
</urlset>
|
|
7
|
+
`,l=(0,t.join)(o,`sitemap.xml`);return await(0,e.mkdir)((0,t.dirname)(l),{recursive:!0}),await(0,e.writeFile)(l,c,`utf8`),l}async function r(n){let{siteUrl:r,outDir:i,rules:a,disallow:o,sitemapUrl:s}=n,c=r.replace(/\/$/,``),l=[];if(a&&a.length>0)for(let e of a){if(l.push(`User-agent: ${e.userAgent}`),e.allow)for(let t of e.allow)l.push(`Allow: ${t}`);if(e.disallow)for(let t of e.disallow)l.push(`Disallow: ${t}`);e.crawlDelay!==void 0&&l.push(`Crawl-delay: ${e.crawlDelay}`),l.push(``)}else{if(l.push(`User-agent: *`),o&&o.length>0)for(let e of o)l.push(`Disallow: ${e}`);else l.push(`Disallow:`);l.push(``)}let u=s??`${c}/sitemap.xml`;l.push(`Sitemap: ${u}`);let d=l.join(`
|
|
8
|
+
`)+`
|
|
9
|
+
`,f=(0,t.join)(i,`robots.txt`);return await(0,e.mkdir)((0,t.dirname)(f),{recursive:!0}),await(0,e.writeFile)(f,d,`utf8`),f}function i(e){return`<script type="application/ld+json">${JSON.stringify(e).replace(/</g,`\\u003c`).replace(/>/g,`\\u003e`).replace(/&/g,`\\u0026`).replace(/\u2028/g,`\\u2028`).replace(/\u2029/g,`\\u2029`)}<\/script>`}function a(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}async function o(e){let{siteUrl:t,outDir:r,routes:i,extraUrls:a=[],maxUrlsPerSitemap:o=5e4}=e,c=[];for(let t of i.pages)t.params.length>0||t.path!==`/404`&&t.path!==`/500`&&(t.path.startsWith(`/_elur`)||t.path.startsWith(`/__elur-js`)||c.push({url:t.path,changefreq:e.defaultChangefreq,priority:e.defaultPriority}));let l=[...c,...a];return l.length<=o?[await n({siteUrl:t,outDir:r,urls:l})]:s({siteUrl:t,outDir:r,urls:l,maxUrlsPerSitemap:o})}async function s(e){let{siteUrl:t,outDir:n,urls:r,maxUrlsPerSitemap:i}=e,a=t.replace(/\/$/,``),o=[],s=[];for(let e=0;e<r.length;e+=i){let t=r.slice(e,e+i),l=`sitemap-${Math.floor(e/i)+1}.xml`,{writeFile:u,mkdir:d}=await import(`node:fs/promises`),{join:f,dirname:p}=await import(`node:path`),m=`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${t.map(e=>{let t=typeof e==`string`?{url:e}:e,n=[` <url>`,` <loc>${c(`${a}${t.url.startsWith(`/`)?``:`/`}${t.url}`)}</loc>`];return t.lastmod&&n.push(` <lastmod>${t.lastmod}</lastmod>`),t.changefreq&&n.push(` <changefreq>${t.changefreq}</changefreq>`),t.priority!==void 0&&n.push(` <priority>${t.priority.toFixed(1)}</priority>`),n.push(` </url>`),n.join(`
|
|
10
|
+
`)}).join(`
|
|
11
|
+
`)}\n</urlset>\n`,h=f(n,l);await d(p(h),{recursive:!0}),await u(h,m,`utf8`),o.push(h),s.push(`${a}/${l}`)}let{writeFile:l,mkdir:u}=await import(`node:fs/promises`),{join:d,dirname:f}=await import(`node:path`),p=`<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${s.map(e=>` <sitemap>\n <loc>${c(e)}</loc>\n </sitemap>`).join(`
|
|
12
|
+
`)}\n</sitemapindex>\n`,m=d(n,`sitemap.xml`);return await u(f(m),{recursive:!0}),await l(m,p,`utf8`),o.push(m),o}function c(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return o}});
|
|
13
|
+
//# sourceMappingURL=sitemap-from-routes-DbcLLL4G.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap-from-routes-DbcLLL4G.cjs","names":[],"sources":["../../src/seo/index.ts","../../src/seo/sitemap-from-routes.ts"],"sourcesContent":["/**\n * SEO utilities — sitemap.xml and robots.txt generation.\n *\n * @module\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\n// Sitemap generation from the scanned route manifest (wired into `build`).\nexport { generateSitemapFromRoutes, type SitemapFromRoutesOptions } from \"./sitemap-from-routes.js\";\n\n// Types\n\nexport interface SitemapEntry {\n /** URL path, e.g. \"/docs/getting-started/introduction\". */\n url: string;\n /** Last modification date (ISO 8601 or YYYY-MM-DD). */\n lastmod?: string;\n /** Change frequency: always, hourly, daily, weekly, monthly, yearly, never. */\n changefreq?: \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n /** Priority 0.0–1.0. */\n priority?: number;\n}\n\nexport interface SitemapConfig {\n /** Base URL of the site, e.g. \"https://example.com\". */\n siteUrl: string;\n /** List of URL entries to include in the sitemap. */\n urls: (SitemapEntry | string)[];\n /** Output directory where sitemap.xml will be written. */\n outDir: string;\n}\n\nexport interface RobotsConfig {\n /** Base URL of the site, e.g. \"https://example.com\". */\n siteUrl: string;\n /** Output directory where robots.txt will be written. */\n outDir: string;\n /** Rules for specific user agents. */\n rules?: RobotsRule[];\n /** Paths to disallow for all crawlers (shorthand for rules). */\n disallow?: string[];\n /** Sitemap URL override. If not set, defaults to `${siteUrl}/sitemap.xml`. */\n sitemapUrl?: string;\n}\n\nexport interface RobotsRule {\n /** User-agent, e.g. \"Googlebot\" or \"*\" for all. */\n userAgent: string;\n /** Paths to disallow. */\n disallow?: string[];\n /** Paths to allow. */\n allow?: string[];\n /** Crawl delay in seconds. */\n crawlDelay?: number;\n}\n\n// Sitemap generation\n\n/**\n * Generates a `sitemap.xml` file from a list of URLs.\n *\n * @example\n * ```ts\n * import { generateSitemap } from \"@elurjs/kit/seo\";\n *\n * await generateSitemap({\n * siteUrl: \"https://elur-kit.dev\",\n * outDir: \"./dist\",\n * urls: [\n * \"/\",\n * \"/docs/introduction\",\n * { url: \"/docs/routing\", changefreq: \"weekly\", priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport async function generateSitemap(config: SitemapConfig): Promise<string> {\n const { siteUrl, urls, outDir } = config;\n const base = siteUrl.replace(/\\/$/, \"\");\n\n const entries: string[] = urls.map((entry) => {\n const e = typeof entry === \"string\" ? { url: entry } : entry;\n const loc = `${base}${e.url.startsWith(\"/\") ? \"\" : \"/\"}${e.url}`;\n const lines = [` <url>`, ` <loc>${escapeXml(loc)}</loc>`];\n if (e.lastmod) lines.push(` <lastmod>${e.lastmod}</lastmod>`);\n if (e.changefreq) lines.push(` <changefreq>${e.changefreq}</changefreq>`);\n if (e.priority !== undefined) lines.push(` <priority>${e.priority.toFixed(1)}</priority>`);\n lines.push(` </url>`);\n return lines.join(\"\\n\");\n });\n\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${entries.join(\"\\n\")}\n</urlset>\n`;\n\n const filePath = join(outDir, \"sitemap.xml\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, xml, \"utf8\");\n return filePath;\n}\n\n// Robots.txt generation\n\n/**\n * Generates a `robots.txt` file.\n *\n * @example\n * ```ts\n * import { generateRobots } from \"@elurjs/kit/seo\";\n *\n * await generateRobots({\n * siteUrl: \"https://elur-kit.dev\",\n * outDir: \"./dist\",\n * disallow: [\"/api/\", \"/_elur/\"],\n * });\n * ```\n */\nexport async function generateRobots(config: RobotsConfig): Promise<string> {\n const { siteUrl, outDir, rules, disallow, sitemapUrl } = config;\n const base = siteUrl.replace(/\\/$/, \"\");\n const lines: string[] = [];\n\n if (rules && rules.length > 0) {\n for (const rule of rules) {\n lines.push(`User-agent: ${rule.userAgent}`);\n if (rule.allow) {\n for (const path of rule.allow) lines.push(`Allow: ${path}`);\n }\n if (rule.disallow) {\n for (const path of rule.disallow) lines.push(`Disallow: ${path}`);\n }\n if (rule.crawlDelay !== undefined) {\n lines.push(`Crawl-delay: ${rule.crawlDelay}`);\n }\n lines.push(\"\");\n }\n } else {\n lines.push(\"User-agent: *\");\n if (disallow && disallow.length > 0) {\n for (const path of disallow) lines.push(`Disallow: ${path}`);\n } else {\n lines.push(\"Disallow:\");\n }\n lines.push(\"\");\n }\n\n const sitemap = sitemapUrl ?? `${base}/sitemap.xml`;\n lines.push(`Sitemap: ${sitemap}`);\n\n const content = lines.join(\"\\n\") + \"\\n\";\n const filePath = join(outDir, \"robots.txt\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, content, \"utf8\");\n return filePath;\n}\n\n// JSON-LD / Structured data\n\nexport interface JsonLdSchema {\n [key: string]: unknown;\n}\n\n/**\n * Serializes a JSON-LD structured data object into a `<script type=\"application/ld+json\">` tag.\n *\n * @example\n * ```ts\n * import { jsonLd } from \"@elurjs/kit/seo\";\n *\n * const schema = jsonLd({\n * \"@context\": \"https://schema.org\",\n * \"@type\": \"TechArticle\",\n * headline: \"Routing\",\n * author: { \"@type\": \"Person\", name: \"Deiver Vasquez\" },\n * });\n * // Returns: <script type=\"application/ld+json\">{...}</script>\n * ```\n */\nexport function jsonLd(schema: JsonLdSchema | JsonLdSchema[]): string {\n const data = JSON.stringify(Array.isArray(schema) ? schema : schema);\n // Escape sequences that could close the <script> tag or introduce markup.\n // Per the HTML spec, inside a <script> block the only dangerous sequence\n // is \"</script\" (case-insensitive). We also escape \"<\" more broadly to\n // prevent any interpreter from seeing markup-like content, and escape\n // \"<!--\" to prevent HTML comment-based escapes.\n const safe = data\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n return `<script type=\"application/ld+json\">${safe}</script>`;\n}\n\n// Helpers\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","// --- Sitemap generation from route manifest (plan §11.3) ---\n//\n// Generates a sitemap.xml from the scanned routes, excluding:\n// - API routes\n// - Dynamic routes (they require data to generate URLs)\n// - Error pages (404, 500)\n// - Routes with noindex metadata\n//\n// For dynamic routes, the author should provide a `generateSitemapUrls()`\n// function in their page.data.ts that returns concrete URLs.\n//\n// Supports large sitemaps via sitemap index files (split at 50,000 URLs).\n\nimport type { ScannedRoutes } from \"../router/route-scanner.js\";\nimport { generateSitemap, type SitemapEntry } from \"./index.js\";\n\nexport interface SitemapFromRoutesOptions {\n siteUrl: string;\n outDir: string;\n routes: ScannedRoutes;\n /** Additional URLs to include (e.g. from dynamic routes). */\n extraUrls?: (SitemapEntry | string)[];\n /** Max URLs per sitemap file. Default: 50000. */\n maxUrlsPerSitemap?: number;\n /** Default changefreq for routes. */\n defaultChangefreq?: SitemapEntry[\"changefreq\"];\n /** Default priority for routes. */\n defaultPriority?: number;\n}\n\n/**\n * Generates a sitemap.xml from the route manifest.\n *\n * Static routes are included automatically. Dynamic routes require the author\n * to provide URLs via `extraUrls` or a `generateSitemapUrls()` export.\n *\n * For large sites (>50,000 URLs), a sitemap index is generated.\n */\nexport async function generateSitemapFromRoutes(\n options: SitemapFromRoutesOptions,\n): Promise<string[]> {\n const { siteUrl, outDir, routes, extraUrls = [], maxUrlsPerSitemap = 50000 } = options;\n\n // Collect static route URLs.\n const routeUrls: SitemapEntry[] = [];\n for (const page of routes.pages) {\n // Skip dynamic routes (they have params).\n if (page.params.length > 0) continue;\n // Skip error pages.\n if (page.path === \"/404\" || page.path === \"/500\") continue;\n // Skip internal namespaces.\n if (page.path.startsWith(\"/_elur\") || page.path.startsWith(\"/__elur-js\")) continue;\n\n routeUrls.push({\n url: page.path,\n changefreq: options.defaultChangefreq,\n priority: options.defaultPriority,\n });\n }\n\n // Merge with extra URLs.\n const allUrls = [...routeUrls, ...extraUrls];\n\n // If under the limit, generate a single sitemap.\n if (allUrls.length <= maxUrlsPerSitemap) {\n const path = await generateSitemap({ siteUrl, outDir, urls: allUrls });\n return [path];\n }\n\n // For large sitemaps, split into multiple files with an index.\n return generateSitemapIndex({ siteUrl, outDir, urls: allUrls, maxUrlsPerSitemap });\n}\n\n/**\n * Generates a sitemap index file that references multiple sitemap files.\n * Used for large sites (>50,000 URLs).\n */\nasync function generateSitemapIndex(\n options: { siteUrl: string; outDir: string; urls: (SitemapEntry | string)[]; maxUrlsPerSitemap: number },\n): Promise<string[]> {\n const { siteUrl, outDir, urls, maxUrlsPerSitemap } = options;\n const base = siteUrl.replace(/\\/$/, \"\");\n const files: string[] = [];\n const sitemapUrls: string[] = [];\n\n // Split URLs into chunks.\n for (let i = 0; i < urls.length; i += maxUrlsPerSitemap) {\n const chunk = urls.slice(i, i + maxUrlsPerSitemap);\n const filename = `sitemap-${Math.floor(i / maxUrlsPerSitemap) + 1}.xml`;\n const { writeFile, mkdir } = await import(\"node:fs/promises\");\n const { join, dirname } = await import(\"node:path\");\n\n // Generate the chunk sitemap.\n const entries = chunk.map((entry) => {\n const e = typeof entry === \"string\" ? { url: entry } : entry;\n const loc = `${base}${e.url.startsWith(\"/\") ? \"\" : \"/\"}${e.url}`;\n const lines = [` <url>`, ` <loc>${escapeXml(loc)}</loc>`];\n if (e.lastmod) lines.push(` <lastmod>${e.lastmod}</lastmod>`);\n if (e.changefreq) lines.push(` <changefreq>${e.changefreq}</changefreq>`);\n if (e.priority !== undefined) lines.push(` <priority>${e.priority.toFixed(1)}</priority>`);\n lines.push(` </url>`);\n return lines.join(\"\\n\");\n });\n\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${entries.join(\"\\n\")}\\n</urlset>\\n`;\n const filePath = join(outDir, filename);\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, xml, \"utf8\");\n files.push(filePath);\n sitemapUrls.push(`${base}/${filename}`);\n }\n\n // Generate the index file.\n const { writeFile, mkdir } = await import(\"node:fs/promises\");\n const { join, dirname } = await import(\"node:path\");\n\n const indexEntries = sitemapUrls.map((url) => ` <sitemap>\\n <loc>${escapeXml(url)}</loc>\\n </sitemap>`).join(\"\\n\");\n const indexXml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${indexEntries}\\n</sitemapindex>\\n`;\n const indexPath = join(outDir, \"sitemap.xml\");\n await mkdir(dirname(indexPath), { recursive: true });\n await writeFile(indexPath, indexXml, \"utf8\");\n files.push(indexPath);\n\n return files;\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ export interface RenderPageOptions {
|
|
|
6
6
|
route: PageRoute;
|
|
7
7
|
params?: RouteParams;
|
|
8
8
|
searchParams?: URLSearchParams;
|
|
9
|
-
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint">;
|
|
9
|
+
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint" | "router" | "js">;
|
|
10
10
|
/** Custom module loader. Defaults to native dynamic import. */
|
|
11
11
|
importer?: (path: string) => Promise<unknown>;
|
|
12
12
|
/** Per-page action names exposed in the HTML shell. */
|
|
@@ -27,6 +27,12 @@ export interface RenderPageResult {
|
|
|
27
27
|
head?: string;
|
|
28
28
|
/** Resolved page title (from metadata or fallback). */
|
|
29
29
|
resolvedTitle?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Loader data as rendered into `<script id="elur-data">`. Exposed so the
|
|
32
|
+
* SPA render endpoint can ship it in the payload and the client router can
|
|
33
|
+
* keep the serialized data fresh across navigations.
|
|
34
|
+
*/
|
|
35
|
+
data?: unknown;
|
|
30
36
|
/**
|
|
31
37
|
* When a loader or layout throws a `Response` (e.g. `throw new Response(...,
|
|
32
38
|
* { status: 404 })`), it is captured here as a first-class response instead
|
|
@@ -52,7 +58,7 @@ export interface RenderErrorPageOptions {
|
|
|
52
58
|
routes: ScannedRoutes;
|
|
53
59
|
status: 404 | 500;
|
|
54
60
|
error?: unknown;
|
|
55
|
-
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint">;
|
|
61
|
+
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint" | "router" | "js">;
|
|
56
62
|
actions?: Record<string, string[]>;
|
|
57
63
|
importer?: (path: string) => Promise<unknown>;
|
|
58
64
|
}
|
package/dist/lib/ssr/render.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface RenderPageOptions {
|
|
|
6
6
|
route: PageRoute;
|
|
7
7
|
params?: RouteParams;
|
|
8
8
|
searchParams?: URLSearchParams;
|
|
9
|
-
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint">;
|
|
9
|
+
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint" | "router" | "js">;
|
|
10
10
|
/** Custom module loader. Defaults to native dynamic import. */
|
|
11
11
|
importer?: (path: string) => Promise<unknown>;
|
|
12
12
|
/** Per-page action names exposed in the HTML shell. */
|
|
@@ -27,6 +27,12 @@ export interface RenderPageResult {
|
|
|
27
27
|
head?: string;
|
|
28
28
|
/** Resolved page title (from metadata or fallback). */
|
|
29
29
|
resolvedTitle?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Loader data as rendered into `<script id="elur-data">`. Exposed so the
|
|
32
|
+
* SPA render endpoint can ship it in the payload and the client router can
|
|
33
|
+
* keep the serialized data fresh across navigations.
|
|
34
|
+
*/
|
|
35
|
+
data?: unknown;
|
|
30
36
|
/**
|
|
31
37
|
* When a loader or layout throws a `Response` (e.g. `throw new Response(...,
|
|
32
38
|
* { status: 404 })`), it is captured here as a first-class response instead
|
|
@@ -52,7 +58,7 @@ export interface RenderErrorPageOptions {
|
|
|
52
58
|
routes: ScannedRoutes;
|
|
53
59
|
status: 404 | 500;
|
|
54
60
|
error?: unknown;
|
|
55
|
-
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint">;
|
|
61
|
+
config: Pick<BuildConfig, "lang" | "clientEntry" | "renderEndpoint" | "router" | "js">;
|
|
56
62
|
actions?: Record<string, string[]>;
|
|
57
63
|
importer?: (path: string) => Promise<unknown>;
|
|
58
64
|
}
|
|
@@ -31,6 +31,13 @@ export interface SsrServer {
|
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Create an SSR server that renders pages on demand and serves static files.
|
|
34
|
+
*
|
|
35
|
+
* @deprecated Legacy pipeline superseded by the unified Web handler
|
|
36
|
+
* (`createWebHandler` from `@elurjs/kit/runtime`), which adds structured
|
|
37
|
+
* logging, security headers, redirects/rewrites, streaming SSR and the
|
|
38
|
+
* pluggable ISR cache. The CLI no longer uses this server (`dev`, `preview`
|
|
39
|
+
* and `start` all run through `createWebHandler`); it remains exported for
|
|
40
|
+
* backward compatibility and will be removed in a future major release.
|
|
34
41
|
*/
|
|
35
42
|
export declare function createSsrServer(options: SsrServerOptions): Promise<SsrServer>;
|
|
36
43
|
/**
|
package/dist/lib/ssr/server.d.ts
CHANGED
|
@@ -31,6 +31,13 @@ export interface SsrServer {
|
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Create an SSR server that renders pages on demand and serves static files.
|
|
34
|
+
*
|
|
35
|
+
* @deprecated Legacy pipeline superseded by the unified Web handler
|
|
36
|
+
* (`createWebHandler` from `@elurjs/kit/runtime`), which adds structured
|
|
37
|
+
* logging, security headers, redirects/rewrites, streaming SSR and the
|
|
38
|
+
* pluggable ISR cache. The CLI no longer uses this server (`dev`, `preview`
|
|
39
|
+
* and `start` all run through `createWebHandler`); it remains exported for
|
|
40
|
+
* backward compatibility and will be removed in a future major release.
|
|
34
41
|
*/
|
|
35
42
|
export declare function createSsrServer(options: SsrServerOptions): Promise<SsrServer>;
|
|
36
43
|
/**
|