@absolutejs/absolute 0.19.0-beta.268 → 0.19.0-beta.269
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.js.map +1 -1
- package/dist/cli/index.js +28 -4
- package/dist/dev/client/constants.ts +1 -0
- package/dist/dev/client/cssUtils.ts +2 -1
- package/dist/dev/client/domDiff.ts +1 -0
- package/dist/dev/client/domState.ts +2 -1
- package/dist/dev/client/domTracker.ts +1 -0
- package/dist/dev/client/errorOverlay.ts +2 -1
- package/dist/dev/client/frameworkDetect.ts +1 -0
- package/dist/dev/client/handlers/angular.ts +1 -0
- package/dist/dev/client/handlers/angularRuntime.ts +1 -0
- package/dist/dev/client/handlers/html.ts +2 -1
- package/dist/dev/client/handlers/htmx.ts +2 -1
- package/dist/dev/client/handlers/react.ts +1 -0
- package/dist/dev/client/handlers/rebuild.ts +1 -0
- package/dist/dev/client/handlers/svelte.ts +1 -0
- package/dist/dev/client/handlers/vue.ts +2 -1
- package/dist/dev/client/headPatch.ts +1 -0
- package/dist/dev/client/hmrClient.ts +2 -1
- package/dist/dev/client/moduleVersions.ts +1 -0
- package/dist/dev/client/reactRefreshSetup.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/vue/pageHandler.d.ts +1 -1
- package/dist/types/globals.d.ts +95 -0
- package/dist/vue/index.js.map +1 -1
- package/package.json +1 -1
package/dist/build.js.map
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"const ESCAPE_LOOKUP: Record<string, string> = {\n\t'\\u2028': '\\\\u2028',\n\t'\\u2029': '\\\\u2029',\n\t'&': '\\\\u0026',\n\t'<': '\\\\u003C',\n\t'>': '\\\\u003E'\n};\n\nconst ESCAPE_REGEX = /[&><\\u2028\\u2029]/g;\n\nexport const escapeScriptContent = (content: string) =>\n\tcontent.replace(ESCAPE_REGEX, (char) => {\n\t\tconst escaped = ESCAPE_LOOKUP[char];\n\n\t\treturn escaped !== undefined ? escaped : char;\n\t});\n",
|
|
72
72
|
"import type { Component } from 'svelte';\nimport { DEFAULT_CHUNK_SIZE } from '../constants';\nimport { escapeScriptContent } from '../utils/escapeScriptContent';\n\nexport type RenderStreamOptions = {\n\tbootstrapScriptContent?: string;\n\tbootstrapScripts?: string[];\n\tbootstrapModules?: string[];\n\tnonce?: string;\n\tonError?: (error: unknown) => void;\n\tprogressiveChunkSize?: number;\n\tsignal?: AbortSignal;\n\theadContent?: string;\n\tbodyContent?: string;\n};\n\nexport const renderToReadableStream = async <\n\tProps extends Record<string, unknown> = Record<string, never>\n>(\n\tcomponent: Component<Props>,\n\tprops?: Props,\n\t{\n\t\tbootstrapScriptContent,\n\t\tbootstrapScripts = [],\n\t\tbootstrapModules = [],\n\t\tnonce,\n\t\tonError = console.error,\n\t\tprogressiveChunkSize = DEFAULT_CHUNK_SIZE,\n\t\tsignal,\n\t\theadContent,\n\t\tbodyContent\n\t}: RenderStreamOptions = {}\n) => {\n\ttry {\n\t\tconst { render } = await import('svelte/server');\n\t\tconst { head: rawHead, body } =\n\t\t\ttypeof props === 'undefined'\n\t\t\t\t? // @ts-expect-error Svelte's render function can't determine which overload to choose when the component is generic\n\t\t\t\t\trender(component)\n\t\t\t\t: render(component, { props });\n\t\t// Svelte SSR extracts <title> from <svelte:head> and appends it\n\t\t// after the component end marker (<!---->). The client renderer\n\t\t// places it at its template position, causing a hydration mismatch.\n\t\t// Fix: move <title> back inside the first component marker so both\n\t\t// SSR and client agree on its position.\n\t\tconst head = rawHead.replace(\n\t\t\t/(<!--[a-z0-9]+-->)([\\s\\S]*?)(<!---->)\\s*(<title>[\\s\\S]*?<\\/title>)/,\n\t\t\t'$1$4$2$3'\n\t\t);\n\t\tconst nonceAttr = nonce ? ` nonce=\"${nonce}\"` : '';\n\t\tconst scripts =\n\t\t\t(bootstrapScriptContent\n\t\t\t\t? `<script${nonceAttr}>${escapeScriptContent(bootstrapScriptContent)}</script>`\n\t\t\t\t: '') +\n\t\t\tbootstrapScripts\n\t\t\t\t.map((src) => `<script${nonceAttr} src=\"${src}\"></script>`)\n\t\t\t\t.join('') +\n\t\t\tbootstrapModules\n\t\t\t\t.map(\n\t\t\t\t\t(src) =>\n\t\t\t\t\t\t`<script${nonceAttr} type=\"module\" src=\"${src}\"></script>`\n\t\t\t\t)\n\t\t\t\t.join('');\n\t\tconst encoder = new TextEncoder();\n\t\t// Warning: this encodes the entire document into memory in one buffer\n\t\tconst full = encoder.encode(\n\t\t\t`<!DOCTYPE html><html lang=\"en\"><head>${head}${headContent ?? ''}</head><body>${body}${scripts}${bodyContent ?? ''}</body></html>`\n\t\t);\n\n\t\tlet offset = 0;\n\n\t\treturn new ReadableStream<Uint8Array>({\n\t\t\ttype: 'bytes',\n\t\t\tcancel(reason) {\n\t\t\t\tonError?.(reason);\n\t\t\t},\n\t\t\tpull(controller) {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tcontroller.close();\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (offset >= full.length) {\n\t\t\t\t\tcontroller.close();\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst end = Math.min(\n\t\t\t\t\toffset + progressiveChunkSize,\n\t\t\t\t\tfull.length\n\t\t\t\t);\n\t\t\t\tcontroller.enqueue(full.subarray(offset, end));\n\t\t\t\toffset = end;\n\t\t\t}\n\t\t});\n\t} catch (error) {\n\t\tonError?.(error);\n\t\tthrow error;\n\t}\n};\n",
|
|
73
73
|
"import type { Component as SvelteComponent } from 'svelte';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (indexPath: string, props?: unknown) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(props)};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst scriptTag = indexPath\n\t\t? `<script type=\"module\" src=\"${indexPath}\"></script>`\n\t\t: '';\n\tconst html = `<!DOCTYPE html><html><head></head><body><script>${propsScript}${dirtyFlag}</script>${scriptTag}</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport type HandleSveltePageRequest = {\n\t(\n\t\tPageComponent: SvelteComponent<Record<string, never>>,\n\t\tpagePath: string,\n\t\tindexPath: string\n\t): Promise<Response>;\n\t<P extends Record<string, unknown>>(\n\t\tPageComponent: SvelteComponent<P>,\n\t\tpagePath: string,\n\t\tindexPath: string,\n\t\tprops: NoInfer<P>\n\t): Promise<Response>;\n};\n\nexport const handleSveltePageRequest: HandleSveltePageRequest = async <\n\tP extends Record<string, unknown>\n>(\n\t_PageComponent: SvelteComponent<P>,\n\tpagePath: string,\n\tindexPath: string,\n\tprops?: P\n) => {\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(indexPath, props);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { renderToReadableStream } = await import(\n\t\t\t'./renderToReadableStream'\n\t\t);\n\n\t\tconst stream = await renderToReadableStream(\n\t\t\tImportedPageComponent,\n\t\t\tprops,\n\t\t\t{\n\t\t\t\tbootstrapModules: indexPath ? [indexPath] : [],\n\t\t\t\tbootstrapScriptContent: `window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\t\t\tprops\n\t\t\t\t)}`\n\t\t\t}\n\t\t);\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Svelte render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'svelte',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('svelte', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateSvelteSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
74
|
-
"import type { Component as VueComponent } from 'vue';\nimport type { VuePropsOf } from '../../types/vue';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (\n\theadTag: string,\n\tindexPath: string,\n\tmaybeProps: Record<string, unknown> | undefined\n) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps ?? {})};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst html =\n\t\t`<!DOCTYPE html><html>${headTag}<body><div id=\"root\"></div>` +\n\t\t`<script>${propsScript}${dirtyFlag}</script>` +\n\t\t`<script type=\"module\" src=\"${indexPath}\"></script>` +\n\t\t`</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport const handleVuePageRequest = async <Component extends VueComponent>(\n\t_PageComponent: Component,\n\tpagePath: string,\n\tindexPath: string,\n\theadTag: `<head>${string}</head>` = '<head></head>',\n\t...props: keyof VuePropsOf<Component> extends never\n\t\t? []\n\t\t: [props: NoInfer<VuePropsOf<Component>>]\n) => {\n\tconst [maybeProps] = props;\n\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(headTag, indexPath, maybeProps);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { createSSRApp, h } = await import('vue');\n\t\tconst { renderToWebStream } = await import('vue/server-renderer');\n\n\t\tconst app = createSSRApp({\n\t\t\trender: () => h(ImportedPageComponent, maybeProps ?? null)\n\t\t});\n\n\t\tconst bodyStream = renderToWebStream(app);\n\n\t\tconst head = `<!DOCTYPE html><html>${headTag}<body><div id=\"root\">`;\n\t\tconst tail = `</div><script>window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\tmaybeProps ?? {}\n\t\t)}</script><script type=\"module\" src=\"${indexPath}\"></script></body></html>`;\n\n\t\tconst stream = new ReadableStream({\n\t\t\tstart(controller) {\n\t\t\t\tcontroller.enqueue(head);\n\t\t\t\tconst reader = bodyStream.getReader();\n\t\t\t\tconst pumpLoop = () => {\n\t\t\t\t\treader\n\t\t\t\t\t\t.read()\n\t\t\t\t\t\t.then(({ done, value }) =>\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\t\t? (controller.enqueue(tail), controller.close())\n\t\t\t\t\t\t\t\t: (controller.enqueue(value), pumpLoop())\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.catch((err) => controller.error(err));\n\t\t\t\t};\n\t\t\t\tpumpLoop();\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Vue render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'vue',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('vue', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateVueSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
74
|
+
"import type { Component as VueComponent } from 'vue';\nimport type { VuePropsOf } from '../../types/vue';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (\n\theadTag: string,\n\tindexPath: string,\n\tmaybeProps: Record<string, unknown> | undefined\n) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps ?? {})};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst html =\n\t\t`<!DOCTYPE html><html>${headTag}<body><div id=\"root\"></div>` +\n\t\t`<script>${propsScript}${dirtyFlag}</script>` +\n\t\t`<script type=\"module\" src=\"${indexPath}\"></script>` +\n\t\t`</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport const handleVuePageRequest = async <Component extends VueComponent>(\n\t_PageComponent: Component,\n\tpagePath: string,\n\tindexPath: string,\n\theadTag: `<head>${string}</head>` = '<head></head>',\n\t...props: keyof VuePropsOf<Component> extends never\n\t\t? [props?: Record<string, never>]\n\t\t: [props: NoInfer<VuePropsOf<Component>>]\n) => {\n\tconst [maybeProps] = props;\n\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(headTag, indexPath, maybeProps);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { createSSRApp, h } = await import('vue');\n\t\tconst { renderToWebStream } = await import('vue/server-renderer');\n\n\t\tconst app = createSSRApp({\n\t\t\trender: () => h(ImportedPageComponent, maybeProps ?? null)\n\t\t});\n\n\t\tconst bodyStream = renderToWebStream(app);\n\n\t\tconst head = `<!DOCTYPE html><html>${headTag}<body><div id=\"root\">`;\n\t\tconst tail = `</div><script>window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\tmaybeProps ?? {}\n\t\t)}</script><script type=\"module\" src=\"${indexPath}\"></script></body></html>`;\n\n\t\tconst stream = new ReadableStream({\n\t\t\tstart(controller) {\n\t\t\t\tcontroller.enqueue(head);\n\t\t\t\tconst reader = bodyStream.getReader();\n\t\t\t\tconst pumpLoop = () => {\n\t\t\t\t\treader\n\t\t\t\t\t\t.read()\n\t\t\t\t\t\t.then(({ done, value }) =>\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\t\t? (controller.enqueue(tail), controller.close())\n\t\t\t\t\t\t\t\t: (controller.enqueue(value), pumpLoop())\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.catch((err) => controller.error(err));\n\t\t\t\t};\n\t\t\t\tpumpLoop();\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Vue render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'vue',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('vue', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateVueSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
75
75
|
"type CacheEntry = {\n\tcontent: string;\n\timports: string[];\n\tmtime: number;\n};\n\n// Persist across bun --hot reloads so HMR doesn't refetch everything\nconst cache = globalThis.__transformCache ?? new Map<string, CacheEntry>();\nglobalThis.__transformCache = cache;\n\n// Reverse map: importedFile → Set<files that import it>\n// Used to cascade invalidation up the import chain.\nconst importers =\n\tglobalThis.__transformImporters ?? new Map<string, Set<string>>();\nglobalThis.__transformImporters = importers;\n\n// Cache entries are invalidated by invalidateModule() when files\n// change — no need to re-stat on every read. If it's in the cache,\n// it's valid.\nexport const getTransformed = (filePath: string) =>\n\tcache.get(filePath)?.content;\n\nexport const setTransformed = (\n\tfilePath: string,\n\tcontent: string,\n\tmtime: number,\n\timports?: string[]\n) => {\n\tconst resolvedImports = imports ?? [];\n\tcache.set(filePath, { content, imports: resolvedImports, mtime });\n\n\t// Update reverse dependency map\n\tfor (const imp of resolvedImports) {\n\t\tconst set = importers.get(imp) ?? new Set<string>();\n\t\timporters.set(imp, set);\n\t\tset.add(filePath);\n\t}\n};\n\n// Per-file invalidation version. Incremented when a file's transform\n// cache is cleared due to a downstream import changing. Used by\n// srcUrl() to force browser re-fetch even if the file's mtime is same.\nconst invalidationVersions =\n\tglobalThis.__transformInvalidationVersions ?? new Map<string, number>();\nglobalThis.__transformInvalidationVersions = invalidationVersions;\n\nconst isComponentFile = (filePath: string) =>\n\tfilePath.endsWith('.tsx') || filePath.endsWith('.jsx');\n\nconst processParents = (parents: Set<string>, queue: string[]) => {\n\tconst component = [...parents].find(isComponentFile);\n\tif (component !== undefined) return component;\n\n\tfor (const parent of parents) queue.push(parent);\n\n\treturn undefined;\n};\n\nexport const findNearestComponent = (filePath: string) => {\n\tconst visited = new Set<string>();\n\tconst queue = [filePath];\n\n\twhile (queue.length > 0) {\n\t\tconst current = queue.shift();\n\t\tif (!current) break;\n\t\tif (visited.has(current)) continue;\n\t\tvisited.add(current);\n\n\t\tconst parents = importers.get(current);\n\t\tif (!parents) continue;\n\n\t\tconst found = processParents(parents, queue);\n\t\tif (found !== undefined) return found;\n\t}\n\n\treturn undefined;\n};\nexport const getInvalidationVersion = (filePath: string) =>\n\tinvalidationVersions.get(filePath) ?? 0;\nexport const invalidate = (filePath: string) => {\n\tcache.delete(filePath);\n\n\t// Bump the CHANGED file's version so when importers are\n\t// re-transpiled, srcUrl() generates a new ?v= for it.\n\tinvalidationVersions.set(\n\t\tfilePath,\n\t\t(invalidationVersions.get(filePath) ?? 0) + 1\n\t);\n\n\t// Clear transform cache for direct importers so they get\n\t// re-transpiled with the changed file's new ?v= param.\n\tfor (const parent of importers.get(filePath) ?? []) {\n\t\tcache.delete(parent);\n\t}\n};\nexport const invalidateAll = () => {\n\tcache.clear();\n\timporters.clear();\n};\n",
|
|
76
76
|
"import { BASE_36_RADIX, UNFOUND_INDEX } from '../constants';\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { basename, dirname, extname, resolve, relative } from 'node:path';\nimport {\n\tgetInvalidationVersion,\n\tgetTransformed,\n\tinvalidate,\n\tsetTransformed\n} from './transformCache';\n\nconst SRC_PREFIX = '/@src/';\n\nconst jsTranspiler = new Bun.Transpiler({\n\tloader: 'js',\n\ttrimUnusedImports: true\n});\n\n// Shared transpiler for TypeScript files — trimUnusedImports strips\n// type-only imports so the browser doesn't request unnecessary modules\n// Separate transpilers for .ts and .tsx — using 'tsx' for .ts files\n// causes parse errors on TypeScript generics like <T> (interpreted as JSX).\nconst tsTranspiler = new Bun.Transpiler({\n\tloader: 'ts',\n\ttrimUnusedImports: true\n});\n\nconst tsxTranspiler = new Bun.Transpiler({\n\tloader: 'tsx',\n\ttrimUnusedImports: true\n});\n\nconst TRANSPILABLE = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs']);\n\n// Regex to find all export names in original TypeScript source\nconst ALL_EXPORTS_RE =\n\t/export\\s+(?:type|interface|const|let|var|function|class|enum|abstract\\s+class)\\s+(\\w+)/g;\n\n// Strip string/template literal contents so regex doesn't match\n// export declarations inside code examples embedded as strings.\nconst STRING_CONTENTS_RE =\n\t/`(?:[^`\\\\]|\\\\.)*`|'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"/gs;\n\n// After transpilation, type exports are stripped. Inject stubs so\n// importing modules can resolve the names (as undefined).\nconst preserveTypeExports = (\n\toriginalSource: string,\n\ttranspiled: string,\n\tvalueExports: string[]\n) => {\n\tconst codeOnly = originalSource.replace(STRING_CONTENTS_RE, '\"\"');\n\tconst allExports: string[] = [];\n\tlet match;\n\tALL_EXPORTS_RE.lastIndex = 0;\n\twhile ((match = ALL_EXPORTS_RE.exec(codeOnly)) !== null) {\n\t\tif (match[1]) allExports.push(match[1]);\n\t}\n\n\tconst valueSet = new Set(valueExports);\n\tconst typeExports = allExports.filter((exp) => !valueSet.has(exp));\n\n\tif (typeExports.length === 0) return transpiled;\n\n\tconst stubs = typeExports\n\t\t.map((name) => `export const ${name} = undefined;`)\n\t\t.join('\\n');\n\n\treturn `${transpiled}\\n${stubs}\\n`;\n};\n// Try known extensions to resolve an extensionless path. Returns\n// the original path if none match (existsSync-based probing).\nconst resolveRelativeExtension = (\n\tsrcPath: string,\n\tprojectRoot: string,\n\textensions: string[]\n) => {\n\tconst found = extensions.find((ext) =>\n\t\texistsSync(resolve(projectRoot, srcPath + ext))\n\t);\n\n\treturn found ? srcPath + found : srcPath;\n};\n\nconst IMPORT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.svelte', '.vue'];\nconst SIDE_EFFECT_EXTENSIONS = [\n\t'.tsx',\n\t'.ts',\n\t'.jsx',\n\t'.js',\n\t'.css',\n\t'.svelte',\n\t'.vue'\n];\nconst MODULE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.svelte', '.vue'];\n\nconst REACT_EXTENSIONS = new Set(['.tsx', '.jsx']);\n\ntype ModuleServerConfig = {\n\tprojectRoot: string;\n\tvendorPaths: Record<string, string>;\n\tframeworkDirs?: {\n\t\tvue?: string;\n\t};\n};\n\nconst escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nconst buildImportRewriter = (vendorPaths: Record<string, string>) => {\n\tconst entries = Object.entries(vendorPaths).sort(\n\t\t([a], [b]) => b.length - a.length\n\t);\n\tif (entries.length === 0) return null;\n\n\tconst alt = entries.map(([spec]) => escapeRegex(spec)).join('|');\n\tconst lookup = new Map(entries);\n\n\t// Single combined regex for all vendor import patterns:\n\t// from 'pkg', import 'pkg', import('pkg')\n\tconst vendorRegex = new RegExp(\n\t\t`((?:from|import)\\\\s*[\"']|import\\\\s*\\\\(\\\\s*[\"'])(${alt})([\"'](?:\\\\s*[;)]?)?)`,\n\t\t'g'\n\t);\n\n\treturn { lookup, vendorRegex };\n};\n\n// Mtime cache — avoids statSync on every import rewrite.\n// Invalidated by the file watcher via invalidateModule().\nconst mtimeCache = new Map<string, number>();\n\n// Append invalidation version if the file's transform cache was\n// cleared (e.g., a downstream import changed). This forces the\n// browser to re-fetch even though the file's own mtime is the same.\nconst buildVersion = (mtime: number, absPath: string) => {\n\tconst invalidationVersion = getInvalidationVersion(absPath);\n\n\treturn invalidationVersion > 0\n\t\t? `${mtime}.${invalidationVersion}`\n\t\t: `${mtime}`;\n};\n\n// Build a /@src/ URL with the file's mtime as a cache buster.\nconst srcUrl = (relPath: string, projectRoot: string) => {\n\tconst base = `${SRC_PREFIX}${relPath.replace(/\\\\/g, '/')}`;\n\tconst absPath = resolve(projectRoot, relPath);\n\n\tconst cached = mtimeCache.get(absPath);\n\tif (cached !== undefined)\n\t\treturn `${base}?v=${buildVersion(cached, absPath)}`;\n\n\ttry {\n\t\tconst mtime = Math.round(statSync(absPath).mtimeMs);\n\t\tmtimeCache.set(absPath, mtime);\n\n\t\treturn `${base}?v=${buildVersion(mtime, absPath)}`;\n\t} catch {\n\t\treturn base;\n\t}\n};\n\n// Resolve a relative import specifier to a /@src/ URL path.\n// Probes known extensions, resolves .svelte module files.\nconst resolveRelativeImport = (\n\trelPath: string,\n\tfileDir: string,\n\tprojectRoot: string,\n\textensions: string[]\n) => {\n\tconst absPath = resolve(fileDir, relPath);\n\tconst rel = relative(projectRoot, absPath);\n\tlet srcPath = extname(rel)\n\t\t? rel\n\t\t: resolveRelativeExtension(rel, projectRoot, extensions);\n\n\t// Resolve Svelte module files: .svelte → .svelte.ts / .svelte.js\n\tif (extname(srcPath) === '.svelte') {\n\t\tsrcPath = relative(\n\t\t\tprojectRoot,\n\t\t\tresolveSvelteModulePath(resolve(projectRoot, srcPath))\n\t\t);\n\t}\n\n\treturn srcUrl(srcPath, projectRoot);\n};\n\n// Resolve @absolutejs/absolute/* specifiers to project-relative paths.\n// Returns the relative path string on success, or undefined if resolution fails.\nconst resolveAbsoluteSpecifier = (specifier: string, projectRoot: string) => {\n\ttry {\n\t\tconst resolved = Bun.resolveSync(specifier, projectRoot);\n\n\t\t// Prefer browser-targeted build if available (server builds\n\t\t// import node:fs which can't run in browsers)\n\t\tconst browserPath = resolved.replace(\n\t\t\t/\\/index\\.js$/,\n\t\t\t'/browser/index.js'\n\t\t);\n\t\tconst target = existsSync(browserPath) ? browserPath : resolved;\n\n\t\treturn relative(projectRoot, target);\n\t} catch {\n\t\t// Resolution failed — caller falls through to stub\n\t\treturn undefined;\n\t}\n};\n\nconst rewriteImports = (\n\tcode: string,\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tlet result = code;\n\n\t// Step 1: Rewrite KNOWN vendor specifiers in a single pass.\n\tconst vendorReplace = (\n\t\t_match: string,\n\t\tprefix: string,\n\t\tspecifier: string,\n\t\tsuffix: string\n\t) => {\n\t\tconst webPath = rewriter?.lookup.get(specifier);\n\n\t\treturn webPath ? `${prefix}${webPath}${suffix}` : _match;\n\t};\n\n\tif (rewriter) {\n\t\trewriter.vendorRegex.lastIndex = 0;\n\t\tresult = result.replace(rewriter.vendorRegex, vendorReplace);\n\t}\n\n\t// Step 2: Rewrite remaining bare specifiers (unknown packages) to stubs.\n\t// Line-anchored to avoid matching inside string literals.\n\tconst stubReplace = (\n\t\t_match: string,\n\t\tprefix: string,\n\t\tspecifier: string,\n\t\tsuffix: string\n\t) => {\n\t\t// Skip if already rewritten to a path\n\t\tif (specifier.startsWith('/') || specifier.startsWith('.'))\n\t\t\treturn _match;\n\n\t\t// Serve @absolutejs/absolute client-safe exports as real modules\n\t\t// instead of stubbing them — they contain Image/Head/JsonLd components\n\t\t// needed for client-side hydration.\n\t\tif (!specifier.startsWith('@absolutejs/absolute/'))\n\t\t\treturn `${prefix}/@stub/${encodeURIComponent(specifier)}${suffix}`;\n\n\t\tconst resolved = resolveAbsoluteSpecifier(specifier, projectRoot);\n\t\tif (resolved) return `${prefix}/@src/${resolved}${suffix}`;\n\n\t\treturn `${prefix}/@stub/${encodeURIComponent(specifier)}${suffix}`;\n\t};\n\n\t// Combined: import/export from 'bare', import 'bare' (line-anchored)\n\t// Uses [\\s\\S]+? to match multi-line imports (e.g., import {\\n foo\\n} from 'pkg')\n\tresult = result.replace(\n\t\t/^((?:import\\s+[\\s\\S]+?\\s+from|export\\s+[\\s\\S]+?\\s+from|import)\\s*[\"'])([^\"'./][^\"']*)([\"'])/gm,\n\t\tstubReplace\n\t);\n\t// Dynamic: import('bare')\n\tresult = result.replace(\n\t\t/(import\\s*\\(\\s*[\"'])([^\"'./][^\"']*)([\"']\\s*\\))/g,\n\t\tstubReplace\n\t);\n\n\t// Rewrite relative imports to /@src/ absolute paths\n\tconst fileDir = dirname(filePath);\n\tresult = result.replace(\n\t\t/(from\\s*[\"'])(\\.\\.?\\/[^\"']+)([\"'])/g,\n\t\t(_match, prefix, relPath, suffix) =>\n\t\t\t`${prefix}${resolveRelativeImport(relPath, fileDir, projectRoot, IMPORT_EXTENSIONS)}${suffix}`\n\t);\n\n\t// Rewrite dynamic relative imports\n\tresult = result.replace(\n\t\t/(import\\s*\\(\\s*[\"'])(\\.\\.?\\/[^\"']+)([\"']\\s*\\))/g,\n\t\t(_match, prefix, relPath, suffix) =>\n\t\t\t`${prefix}${resolveRelativeImport(relPath, fileDir, projectRoot, IMPORT_EXTENSIONS)}${suffix}`\n\t);\n\n\t// Rewrite side-effect relative imports: import './foo'\n\tresult = result.replace(\n\t\t/(import\\s*[\"'])(\\.\\.?\\/[^\"']+)([\"']\\s*;?)/g,\n\t\t(_match, prefix, relPath, suffix) =>\n\t\t\t`${prefix}${resolveRelativeImport(relPath, fileDir, projectRoot, SIDE_EFFECT_EXTENSIONS)}${suffix}`\n\t);\n\n\t// Rewrite absolute filesystem paths (from generated index files that\n\t// import hmrClient, refreshSetup, etc. via absolute paths)\n\tresult = result.replace(\n\t\t/((?:from|import)\\s*[\"'])(\\/[^\"']+\\.(tsx?|jsx?|ts))([\"'])/g,\n\t\t(_match, prefix, absPath, _ext, suffix) => {\n\t\t\tif (absPath.startsWith(projectRoot)) {\n\t\t\t\tconst rel = relative(projectRoot, absPath).replace(/\\\\/g, '/');\n\n\t\t\t\treturn `${prefix}${srcUrl(rel, projectRoot)}${suffix}`;\n\t\t\t}\n\t\t\t// Path outside project root (e.g., node_modules package src)\n\t\t\t// Try to make it relative to project root anyway\n\t\t\tconst rel = relative(projectRoot, absPath).replace(/\\\\/g, '/');\n\t\t\tif (!rel.startsWith('..')) {\n\t\t\t\treturn `${prefix}${srcUrl(rel, projectRoot)}${suffix}`;\n\t\t\t}\n\n\t\t\treturn _match;\n\t\t}\n\t);\n\n\t// Rewrite new URL('./relative', import.meta.url) for web workers / assets\n\tresult = result.replace(\n\t\t/new\\s+URL\\(\\s*[\"'](\\.\\.?\\/[^\"']+)[\"']\\s*,\\s*import\\.meta\\.url\\s*\\)/g,\n\t\t(_match, relPath) => {\n\t\t\tconst absPath = resolve(fileDir, relPath);\n\t\t\tconst rel = relative(projectRoot, absPath);\n\n\t\t\treturn `new URL('${srcUrl(rel, projectRoot)}', import.meta.url)`;\n\t\t}\n\t);\n\n\t// Rewrite import.meta.resolve('./relative') for asset/worker references\n\tresult = result.replace(\n\t\t/import\\.meta\\.resolve\\(\\s*[\"'](\\.\\.?\\/[^\"']+)[\"']\\s*\\)/g,\n\t\t(_match, relPath) => {\n\t\t\tconst absPath = resolve(fileDir, relPath);\n\t\t\tconst rel = relative(projectRoot, absPath);\n\n\t\t\treturn `'${srcUrl(rel, projectRoot)}'`;\n\t\t}\n\t);\n\n\treturn result;\n};\n\n// Use Bun.Transpiler (~0.1ms) instead of Bun.build (~2-150ms) for\n// React files. Manually inject $RefreshReg$/$RefreshSig$ calls\n// after transpilation.\n// Bun.Transpiler uses an auto-generated name for JSX (jsxDEV_XXXXXXXX)\n// but doesn't emit the import statement. We need to detect the generated\n// name and add the import ourselves.\nconst JSX_AUTO_RE = /\\b(jsxDEV_[a-z0-9]+)\\b/;\nconst JSXS_AUTO_RE = /\\b(jsxs_[a-z0-9]+)\\b/;\nconst JSX_PROD_RE = /\\b(jsx_[a-z0-9]+)\\b/;\nconst FRAGMENT_RE = /\\b(Fragment_[a-z0-9]+)\\b/;\n\nconst addJsxImport = (code: string) => {\n\tconst imports: string[] = [];\n\n\tconst jsxDevMatch = JSX_AUTO_RE.exec(code);\n\tif (jsxDevMatch) {\n\t\timports.push(\n\t\t\t`import { jsxDEV as ${jsxDevMatch[1]} } from \"react/jsx-dev-runtime\";`\n\t\t);\n\t}\n\n\tconst jsxsMatch = JSXS_AUTO_RE.exec(code);\n\tif (jsxsMatch && (!jsxDevMatch || jsxsMatch[1] !== jsxDevMatch[1])) {\n\t\timports.push(\n\t\t\t`import { jsxs as ${jsxsMatch[1]} } from \"react/jsx-runtime\";`\n\t\t);\n\t}\n\n\tconst jsxProdMatch = JSX_PROD_RE.exec(code);\n\tif (jsxProdMatch) {\n\t\timports.push(\n\t\t\t`import { jsx as ${jsxProdMatch[1]} } from \"react/jsx-runtime\";`\n\t\t);\n\t}\n\n\tconst fragmentMatch = FRAGMENT_RE.exec(code);\n\tif (fragmentMatch) {\n\t\timports.push(\n\t\t\t`import { Fragment as ${fragmentMatch[1]} } from \"react\";`\n\t\t);\n\t}\n\n\tif (imports.length === 0) return code;\n\n\treturn `${imports.join('\\n')}\\n${code}`;\n};\n\n// With the patched Bun.Transpiler (PR #28312), reactFastRefresh: true\n// injects $RefreshReg$/$RefreshSig$ natively — no manual injection needed.\n// Falls back to plain transpilation if reactFastRefresh isn't available.\n// reactFastRefresh is available via patched Bun (PR #28312) but not\n// yet in the upstream type definitions, so we extend the options type.\nconst reactTranspilerOptions: ConstructorParameters<\n\ttypeof Bun.Transpiler\n>[0] & {\n\treactFastRefresh?: boolean;\n} = {\n\tloader: 'tsx',\n\treactFastRefresh: true,\n\ttrimUnusedImports: true\n};\nconst reactTranspiler = new Bun.Transpiler(reactTranspilerOptions);\n\nconst transformReactFile = (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\tconst valueExports = tsxTranspiler.scan(raw).exports;\n\tlet transpiled = reactTranspiler.transformSync(raw);\n\ttranspiled = preserveTypeExports(raw, transpiled, valueExports);\n\n\t// Bun.Transpiler auto-generates JSX function names (jsxDEV_XXXXXXXX)\n\t// but doesn't emit the import — it expects the bundler to resolve it.\n\ttranspiled = addJsxImport(transpiled);\n\n\t// The patched transpiler imports register/createSignatureFunctionForTransform\n\t// from react-refresh/runtime, creating a separate module instance. But the\n\t// initial bundled index uses window.$RefreshReg$/$RefreshSig$ globals.\n\t// Replace the import with globals so registrations go to the same runtime.\n\ttranspiled = transpiled.replace(\n\t\t/import\\s*\\{[^}]*\\}\\s*from\\s*[\"']react-refresh\\/runtime[\"'];?\\n?/,\n\t\t''\n\t);\n\t// Map the aliased names to the window globals\n\ttranspiled = transpiled.replace(\n\t\t/\\$RefreshReg\\$_[a-z0-9]+/g,\n\t\t'$RefreshReg$'\n\t);\n\ttranspiled = transpiled.replace(\n\t\t/\\$RefreshSig\\$_[a-z0-9]+/g,\n\t\t'$RefreshSig$'\n\t);\n\t// Prepend window global stubs for ESM scope\n\ttranspiled =\n\t\t`var $RefreshReg$ = window.$RefreshReg$ || function(){};\\n` +\n\t\t`var $RefreshSig$ = window.$RefreshSig$ || function(){ return function(t){ return t; }; };\\n${\n\t\t\ttranspiled\n\t\t}`;\n\n\t// Bun.Transpiler uses \"input.tsx\" as the default filename in\n\t// $RefreshReg$ IDs. Replace with the real relative path so IDs\n\t// match the initial bundled registration.\n\tconst relPath = relative(projectRoot, filePath).replace(/\\\\/g, '/');\n\ttranspiled = transpiled.replace(/\\binput\\.tsx:/g, `${relPath}:`);\n\n\treturn rewriteImports(transpiled, filePath, projectRoot, rewriter);\n};\n\n// Use Bun.Transpiler for non-React files (no refresh injection needed)\nconst transformPlainFile = (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\tconst ext = extname(filePath);\n\tconst isTS = ext === '.ts' || ext === '.tsx';\n\tconst isTSX = ext === '.tsx' || ext === '.jsx';\n\n\tlet transpiler = jsTranspiler;\n\tif (isTSX) transpiler = tsxTranspiler;\n\telse if (isTS) transpiler = tsTranspiler;\n\tconst valueExports = isTS ? transpiler.scan(raw).exports : [];\n\tlet transpiled = transpiler.transformSync(raw);\n\n\tif (isTS) {\n\t\ttranspiled = preserveTypeExports(raw, transpiled, valueExports);\n\t}\n\n\ttranspiled = rewriteImports(transpiled, filePath, projectRoot, rewriter);\n\n\t// Vue composable HMR state tracking: wrap exported use* functions\n\t// so ref values are captured and restored across HMR reloads.\n\tif (!vueDir || !filePath.startsWith(vueDir) || !isTS) return transpiled;\n\n\tconst useExports = valueExports.filter((e) => e.startsWith('use'));\n\tif (useExports.length === 0) return transpiled;\n\n\treturn injectComposableTracking(transpiled, filePath, useExports);\n};\n\n// Classify a character for brace-counting: returns the new string\n// context and whether to skip further processing.\nconst classifyChar = (\n\tchar: string,\n\tprevChar: string,\n\tinString: string | false\n): { nextString: string | false; skip: boolean } => {\n\tif (inString) {\n\t\tconst closed = char === inString && prevChar !== '\\\\';\n\n\t\treturn { nextString: closed ? false : inString, skip: true };\n\t}\n\tif (char === '\"' || char === \"'\" || char === '`')\n\t\treturn { nextString: char, skip: true };\n\n\treturn { nextString: false, skip: false };\n};\n\n// Find the end of a function expression by counting braces/parens,\n// skipping string literals. Returns the index of the trailing ';'.\nconst findFunctionEnd = (source: string, startPos: number) => {\n\tlet depth = 0;\n\tlet inString: string | false = false;\n\tfor (let idx = startPos; idx < source.length; idx++) {\n\t\tconst char = source[idx] ?? '';\n\t\tconst classified = classifyChar(char, source[idx - 1] ?? '', inString);\n\t\tinString = classified.nextString;\n\t\tif (classified.skip) continue;\n\n\t\tif (char === '{' || char === '(') depth++;\n\t\tif (char === '}' || char === ')') depth--;\n\t\tif (depth === 0 && char === ';') return idx;\n\t}\n\n\treturn startPos;\n};\n\n/** Inject HMR state tracking into Vue composable exports.\n * Wraps each use* export to capture/restore ref values across reloads. */\nconst injectComposableTracking = (\n\tcode: string,\n\tfilePath: string,\n\tuseExports: string[]\n) => {\n\tconst moduleId = JSON.stringify(filePath);\n\n\t// Inject the tracking runtime at the top\n\tconst runtime = [\n\t\t`var __hmr_cs = (globalThis.__HMR_COMPOSABLE_STATE__ ??= {});`,\n\t\t`var __hmr_mid = ${moduleId};`,\n\t\t`var __hmr_prev_refs = __hmr_cs[__hmr_mid];`,\n\t\t`var __hmr_idx = {};`,\n\t\t`__hmr_cs[__hmr_mid] = {};`,\n\t\t`function __hmr_wrap(name, fn) {`,\n\t\t` return function() {`,\n\t\t` var idx = (__hmr_idx[name] = (__hmr_idx[name] ?? -1) + 1);`,\n\t\t` var result = fn.apply(this, arguments);`,\n\t\t` if (result && typeof result === \"object\") {`,\n\t\t` var refs = {};`,\n\t\t` for (var k in result) {`,\n\t\t` var v = result[k];`,\n\t\t` if (v && typeof v === \"object\" && \"value\" in v && !v.effect && typeof v.value !== \"function\") {`,\n\t\t` refs[k] = v;`,\n\t\t` }`,\n\t\t` }`,\n\t\t` (__hmr_cs[__hmr_mid][name] ??= [])[idx] = refs;`,\n\t\t` if (__hmr_prev_refs && __hmr_prev_refs[name] && __hmr_prev_refs[name][idx]) {`,\n\t\t` var old = __hmr_prev_refs[name][idx];`,\n\t\t` for (var k in old) {`,\n\t\t` var nv = result[k];`,\n\t\t` var ov = old[k];`,\n\t\t` if (nv && ov && typeof nv === \"object\" && \"value\" in nv && !nv.effect && typeof nv.value === typeof ov.value) {`,\n\t\t` nv.value = ov.value;`,\n\t\t` }`,\n\t\t` }`,\n\t\t` }`,\n\t\t` }`,\n\t\t` return result;`,\n\t\t` };`,\n\t\t`}`\n\t].join('\\n');\n\n\tlet result = `${runtime}\\n${code}`;\n\n\t// Wrap each use* export with __hmr_wrap.\n\t// Find the export assignment, then use brace/paren counting to locate\n\t// the end of the function expression (handles nested braces in the body).\n\tfor (const name of useExports) {\n\t\tresult = wrapComposableExport(result, name);\n\t}\n\n\treturn result;\n};\n\n// Find and wrap a single use* export with __hmr_wrap().\nconst wrapComposableExport = (source: string, name: string) => {\n\tconst marker = new RegExp(\n\t\t`export\\\\s+(?:const|var|let)\\\\s+${name}\\\\s*=\\\\s*`\n\t);\n\tconst match = marker.exec(source);\n\tif (!match) return source;\n\n\tconst insertPos = match.index + match[0].length;\n\tconst endPos = findFunctionEnd(source, insertPos);\n\tconst funcBody = source.slice(insertPos, endPos);\n\n\treturn `${source.slice(0, insertPos)}__hmr_wrap(${JSON.stringify(name)}, ${funcBody})${source.slice(endPos)}`;\n};\n\n// Virtual CSS modules for Svelte's css:'external' mode.\n// Keyed by fake path (e.g., /path/to/Counter.svelte.css).\nconst svelteExternalCss = new Map<string, string>();\n\n// ─── Framework-specific transforms (Svelte, Vue) ────────────\n// Cached compiler references — avoid re-importing on every request.\n// Pre-set via warmCompilers() at startup to eliminate first-edit spike.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet svelteCompiler: any = null;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet vueCompiler: any = null;\n\nexport const warmCompilers = async (frameworks: {\n\tsvelte?: boolean;\n\tvue?: boolean;\n}) => {\n\tconst [svelteModule, vueModule] = await Promise.all([\n\t\tframeworks.svelte ? import('svelte/compiler') : undefined,\n\t\tframeworks.vue ? import('@vue/compiler-sfc') : undefined\n\t]);\n\tif (svelteModule) {\n\t\tsvelteCompiler = svelteModule;\n\t\t// JIT-warm the compile function with a trivial component so\n\t\t// the first real HMR compile doesn't pay the JIT cost (~60ms).\n\t\tsvelteModule.compile('<script>let x=$state(0)</script>{x}', {\n\t\t\tcss: 'external',\n\t\t\tdev: true,\n\t\t\tfilename: '_warm.svelte',\n\t\t\tgenerate: 'client',\n\t\t\thmr: true\n\t\t});\n\t}\n\tif (!vueModule) return;\n\n\tvueCompiler = vueModule;\n\t// Same for Vue — warm compileScript + compileTemplate\n\tconst { descriptor } = vueModule.parse(\n\t\t'<script setup>const x=1</script><template>{{x}}</template>',\n\t\t{ filename: '_warm.vue' }\n\t);\n\tvueModule.compileScript(descriptor, { id: 'w', inlineTemplate: false });\n\tif (!descriptor.template) return;\n\n\tvueModule.compileTemplate({\n\t\tfilename: '_warm.vue',\n\t\tid: 'w',\n\t\tsource: descriptor.template.content\n\t});\n};\n\n// Compile a .svelte.ts module file — transpile TS, then compileModule.\nconst compileSvelteModule = (raw: string, filePath: string) => {\n\tconst source = tsTranspiler.transformSync(raw);\n\n\treturn svelteCompiler.compileModule(source, {\n\t\tdev: true,\n\t\tfilename: filePath\n\t}).js.code;\n};\n\n// Compile a .svelte component file — hmr: true, css: 'external'.\nconst compileSvelteComponent = (\n\traw: string,\n\tfilePath: string,\n\tprojectRoot: string\n) => {\n\tconst compiled = svelteCompiler.compile(raw, {\n\t\tcss: 'external',\n\t\tdev: true,\n\t\tfilename: filePath,\n\t\tgenerate: 'client',\n\t\thmr: true\n\t});\n\tlet { code } = compiled.js;\n\n\t// If the component has styles, inject them as a virtual CSS\n\t// import. The handleCssRequest handler serves it as a <style>.\n\tif (compiled.css?.code) {\n\t\tconst cssPath = `${filePath}.css`;\n\t\tsvelteExternalCss.set(cssPath, compiled.css.code);\n\t\tconst cssUrl = srcUrl(relative(projectRoot, cssPath), projectRoot);\n\t\tcode = `import \"${cssUrl}\";\\n${code}`;\n\t}\n\n\t// ── import.meta.hot → accept registry ──\n\tconst moduleUrl = `${SRC_PREFIX}${relative(projectRoot, filePath).replace(/\\\\/g, '/')}`;\n\tcode = code.replace(\n\t\t/if\\s*\\(import\\.meta\\.hot\\)\\s*\\{/,\n\t\t`if (typeof window !== \"undefined\") {\\n` +\n\t\t\t` if (!window.__SVELTE_HMR_ACCEPT__) window.__SVELTE_HMR_ACCEPT__ = {};\\n` +\n\t\t\t` var __hmr_accept = function(cb) { window.__SVELTE_HMR_ACCEPT__[${JSON.stringify(moduleUrl)}] = cb; };`\n\t);\n\n\treturn code.replace(/import\\.meta\\.hot\\.accept\\(/g, '__hmr_accept(');\n};\n\n// Compile .svelte files to client JS using svelte/compiler.\n// Keeps .svelte extensions in imports so the module server handles children.\nconst transformSvelteFile = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\n\tif (!svelteCompiler) {\n\t\tsvelteCompiler = await import('svelte/compiler');\n\t}\n\n\tconst isModule =\n\t\tfilePath.endsWith('.svelte.ts') || filePath.endsWith('.svelte.js');\n\n\tconst code = isModule\n\t\t? compileSvelteModule(raw, filePath)\n\t\t: compileSvelteComponent(raw, filePath, projectRoot);\n\n\treturn rewriteImports(code, filePath, projectRoot, rewriter);\n};\n\ntype VueSFCDescriptor = {\n\tstyles: Array<{ content: string; scoped: boolean }>;\n\ttemplate?: { content: string } | null;\n};\n\ntype VueSFCCompiledScript = {\n\tbindings: Record<string, string>;\n\tcontent: string;\n};\n\n// Compile a Vue SFC template and attach the render function to the script.\nconst compileVueTemplate = (\n\tdescriptor: VueSFCDescriptor,\n\tcompiledScript: VueSFCCompiledScript,\n\tfilePath: string,\n\tcomponentId: string\n) => {\n\tconst scriptContent = compiledScript.content;\n\tif (!descriptor.template) return scriptContent;\n\n\tconst isScoped = descriptor.styles.some((style) => style.scoped);\n\tconst templateResult = vueCompiler.compileTemplate({\n\t\tcompilerOptions: {\n\t\t\tbindingMetadata: compiledScript.bindings,\n\t\t\tprefixIdentifiers: true\n\t\t},\n\t\tfilename: filePath,\n\t\tid: componentId,\n\t\tscoped: isScoped,\n\t\tsource: descriptor.template.content\n\t});\n\n\tlet code = scriptContent.replace('export default', 'const __script__ =');\n\tcode += `\\n${templateResult.code}`;\n\tcode += '\\n__script__.render = render;';\n\tcode += '\\nexport default __script__;';\n\n\treturn code;\n};\n\n// Compile and inject scoped CSS as inline <style> for a Vue SFC.\nconst compileVueStyles = (\n\tdescriptor: VueSFCDescriptor,\n\tfilePath: string,\n\tcomponentId: string,\n\tcode: string\n) => {\n\tif (descriptor.styles.length === 0) return code;\n\n\tconst cssCode = descriptor.styles\n\t\t.map(\n\t\t\t(style) =>\n\t\t\t\tvueCompiler.compileStyle({\n\t\t\t\t\tfilename: filePath,\n\t\t\t\t\tid: `data-v-${componentId}`,\n\t\t\t\t\tscoped: style.scoped,\n\t\t\t\t\tsource: style.content,\n\t\t\t\t\ttrim: true\n\t\t\t\t}).code\n\t\t)\n\t\t.join('\\n');\n\n\tconst escaped = cssCode\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/`/g, '\\\\`')\n\t\t.replace(/\\$/g, '\\\\$');\n\tconst hmrId = JSON.stringify(filePath);\n\tconst cssInjection = [\n\t\t`var __style=document.createElement('style');`,\n\t\t`__style.textContent=\\`${escaped}\\`;`,\n\t\t`__style.dataset.hmrId=${hmrId};`,\n\t\t`var __prev=document.querySelector('style[data-hmr-id=\"${filePath}\"]');`,\n\t\t`if(__prev)__prev.remove();`,\n\t\t`document.head.appendChild(__style);`\n\t].join('');\n\n\treturn `${cssInjection}\\n${code}`;\n};\n\n// Compile .vue SFC files to client JS using @vue/compiler-sfc.\nconst transformVueFile = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\n\tif (!vueCompiler) {\n\t\tvueCompiler = await import('@vue/compiler-sfc');\n\t}\n\n\tconst fileName = basename(filePath, '.vue');\n\tconst componentId = fileName.toLowerCase();\n\tconst { descriptor } = vueCompiler.parse(raw, { filename: filePath });\n\n\tconst compiledScript = vueCompiler.compileScript(descriptor, {\n\t\tid: componentId,\n\t\tinlineTemplate: false\n\t});\n\n\tlet code = compileVueTemplate(\n\t\tdescriptor,\n\t\tcompiledScript,\n\t\tfilePath,\n\t\tcomponentId\n\t);\n\tcode = compileVueStyles(descriptor, filePath, componentId, code);\n\n\t// Vue's compileScript strips user TypeScript but the generated\n\t// wrapper code still has `: any` annotations (e.g. __props: any,\n\t// _ctx: any). Run through the TS transpiler to strip those.\n\tcode = tsTranspiler.transformSync(code);\n\n\t// Inject Vue HMR — use rerender() to preserve reactive state.\n\t// rerender() only swaps the render function (like React Fast Refresh).\n\t// reload() would reset state by re-running setup().\n\tcode = injectVueHmr(code, filePath, projectRoot, vueDir);\n\n\treturn rewriteImports(code, filePath, projectRoot, rewriter);\n};\n\n// Inject Vue HMR runtime registration and rerender call.\nconst injectVueHmr = (\n\tcode: string,\n\tfilePath: string,\n\tprojectRoot: string,\n\tvueDir?: string\n) => {\n\tconst hmrBase = vueDir ? resolve(vueDir) : projectRoot;\n\tconst hmrId = relative(hmrBase, filePath)\n\t\t.replace(/\\\\/g, '/')\n\t\t.replace(/\\.vue$/, '');\n\tlet result = code.replace(/export\\s+default\\s+/, 'var __hmr_comp__ = ');\n\tresult += [\n\t\t'',\n\t\t`__hmr_comp__.__hmrId = ${JSON.stringify(hmrId)};`,\n\t\t`if (typeof __VUE_HMR_RUNTIME__ !== \"undefined\") {`,\n\t\t` __VUE_HMR_RUNTIME__.createRecord(${JSON.stringify(hmrId)}, __hmr_comp__);`,\n\t\t` __VUE_HMR_RUNTIME__.rerender(${JSON.stringify(hmrId)}, __hmr_comp__.render);`,\n\t\t`}`,\n\t\t'export default __hmr_comp__;'\n\t].join('\\n');\n\n\treturn result;\n};\n\n// Resolve .svelte module files that may exist as .svelte.ts or .svelte.js\nconst resolveSvelteModulePath = (path: string) => {\n\tif (existsSync(path)) return path;\n\tif (existsSync(`${path}.ts`)) return `${path}.ts`;\n\tif (existsSync(`${path}.js`)) return `${path}.js`;\n\n\treturn path;\n};\n\n// Shared response builder for transformed modules\nconst jsResponse = (body: string) => {\n\tconst etag = `\"${Bun.hash(body).toString(BASE_36_RADIX)}\"`;\n\n\treturn new Response(body, {\n\t\theaders: {\n\t\t\t'Cache-Control': 'no-cache',\n\t\t\t'Content-Type': 'application/javascript',\n\t\t\tETag: etag\n\t\t}\n\t});\n};\n\nconst handleCssRequest = (filePath: string) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\tconst escaped = raw\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/`/g, '\\\\`')\n\t\t.replace(/\\$/g, '\\\\$');\n\n\treturn [\n\t\t`const style = document.createElement('style');`,\n\t\t`style.textContent = \\`${escaped}\\`;`,\n\t\t`style.dataset.hmrId = ${JSON.stringify(filePath)};`,\n\t\t`const existing = document.querySelector(\\`style[data-hmr-id=\"${filePath}\"]\\`);`,\n\t\t`if (existing) existing.remove();`,\n\t\t`document.head.appendChild(style);`\n\t].join('\\n');\n};\n\n// Generate HMR bootstrap wrapper for Svelte.\n// Uses dynamic import() with a cache-busting timestamp so the\n// browser fetches the freshly compiled component every time.\nconst generateSvelteHmrBootstrap = (\n\tmoduleUrl: string,\n\tvendorPaths: Record<string, string>,\n\ttimestamp: string\n) => {\n\tconst sveltePath = vendorPaths['svelte'] || '/svelte/vendor/svelte.js';\n\n\treturn [\n\t\t`import { mount, unmount } from \"${sveltePath}\";`,\n\t\t`const { default: Component } = await import(\"${moduleUrl}?t=${timestamp}\");`,\n\t\t``,\n\t\t`// Extract count from DOM before unmount (survives across runtime instances)`,\n\t\t`var countBtn = document.querySelector(\"button\");`,\n\t\t`var countMatch = countBtn && countBtn.textContent && countBtn.textContent.match(/(\\\\d+)/);`,\n\t\t`var domCount = countMatch ? parseInt(countMatch[1], 10) : null;`,\n\t\t``,\n\t\t`var preservedState = window.__HMR_PRESERVED_STATE__ || {};`,\n\t\t`if (domCount !== null && preservedState.initialCount === undefined) {`,\n\t\t` preservedState.initialCount = domCount;`,\n\t\t`}`,\n\t\t`var initialProps = window.__INITIAL_PROPS__ || {};`,\n\t\t`var mergedProps = Object.assign({}, initialProps, preservedState);`,\n\t\t``,\n\t\t`// Update __INITIAL_PROPS__ so subsequent HMR cycles start with current state`,\n\t\t`if (domCount !== null) window.__INITIAL_PROPS__ = Object.assign({}, initialProps, { initialCount: domCount });`,\n\t\t``,\n\t\t`if (typeof window.__SVELTE_UNMOUNT__ === \"function\") {`,\n\t\t` try { window.__SVELTE_UNMOUNT__(); } catch (err) { /* ignore */ }`,\n\t\t`}`,\n\t\t``,\n\t\t`var component = mount(Component, { target: document.body, props: mergedProps });`,\n\t\t`window.__SVELTE_COMPONENT__ = component;`,\n\t\t`window.__SVELTE_UNMOUNT__ = function() { unmount(component); };`,\n\t\t`window.__HMR_PRESERVED_STATE__ = undefined;`\n\t].join('\\n');\n};\n\n// Generate HMR bootstrap wrapper for Vue.\n// Same approach as Svelte — full remount via vendor Vue's createApp.\nconst generateVueHmrBootstrap = (\n\tmoduleUrl: string,\n\tvendorPaths: Record<string, string>,\n\ttimestamp: string\n) => {\n\tconst vuePath = vendorPaths['vue'] || '/vue/vendor/vue.js';\n\n\treturn [\n\t\t`import { createApp } from \"${vuePath}\";`,\n\t\t`const { default: Component } = await import(\"${moduleUrl}?t=${timestamp}\");`,\n\t\t``,\n\t\t`// Extract count from DOM before unmount (works across Vue instances)`,\n\t\t`var countBtn = document.querySelector(\"button\");`,\n\t\t`var countMatch = countBtn && countBtn.textContent && countBtn.textContent.match(/(\\\\d+)/);`,\n\t\t`var domCount = countMatch ? parseInt(countMatch[1], 10) : null;`,\n\t\t``,\n\t\t`var preservedState = window.__HMR_PRESERVED_STATE__ || {};`,\n\t\t`if (domCount !== null && preservedState.initialCount === undefined) {`,\n\t\t` preservedState.initialCount = domCount;`,\n\t\t`}`,\n\t\t`var initialProps = window.__INITIAL_PROPS__ || {};`,\n\t\t`var mergedProps = Object.assign({}, initialProps, preservedState);`,\n\t\t``,\n\t\t`// Update __INITIAL_PROPS__ so subsequent HMR cycles start with current state`,\n\t\t`if (domCount !== null) window.__INITIAL_PROPS__ = Object.assign({}, initialProps, { initialCount: domCount });`,\n\t\t``,\n\t\t`var root = document.getElementById(\"root\");`,\n\t\t`var savedHTML = root ? root.innerHTML : \"\";`,\n\t\t`if (window.__VUE_APP__) {`,\n\t\t` window.__VUE_APP__.unmount();`,\n\t\t` window.__VUE_APP__ = null;`,\n\t\t`}`,\n\t\t`if (root) root.innerHTML = savedHTML;`,\n\t\t``,\n\t\t`var app = createApp(Component, mergedProps);`,\n\t\t`app.mount(root);`,\n\t\t`window.__VUE_APP__ = app;`,\n\t\t`window.__HMR_PRESERVED_STATE__ = undefined;`\n\t].join('\\n');\n};\n\n// Generate a stub module for a server-only package so browser imports resolve.\nconst handleStubRequest = async (pathname: string) => {\n\tconst specifier = decodeURIComponent(pathname.slice('/@stub/'.length));\n\tconst stubCode = await buildStubCode(specifier);\n\n\treturn new Response(stubCode, {\n\t\theaders: {\n\t\t\t'Cache-Control': 'public, max-age=31536000, immutable',\n\t\t\t'Content-Type': 'application/javascript'\n\t\t}\n\t});\n};\n\n// Introspect a module's exports and generate noop stubs for each.\nconst buildStubCode = async (specifier: string) => {\n\ttry {\n\t\tconst mod = await import(specifier);\n\t\tconst names = Object.keys(mod).filter(\n\t\t\t(key) => key !== 'default' && key !== '__esModule'\n\t\t);\n\t\tif (names.length === 0) return 'export default {};\\n';\n\n\t\tconst noops = names\n\t\t\t.map((n) => `export const ${n} = () => {};`)\n\t\t\t.join('\\n');\n\n\t\treturn `${noops}\\nexport default {};\\n`;\n\t} catch {\n\t\treturn 'export default {};\\n';\n\t}\n};\n\n// Handle HMR bootstrap wrappers for non-React frameworks.\nconst handleHmrBootstrap = (\n\tpathname: string,\n\tvendorPaths: Record<string, string>\n) => {\n\tconst rest = pathname.slice('/@hmr/'.length);\n\tconst slashIdx = rest.indexOf('/');\n\tif (slashIdx === UNFOUND_INDEX) return undefined;\n\n\tconst framework = rest.slice(0, slashIdx);\n\tconst componentRelPath = rest.slice(slashIdx + 1);\n\tconst url = `${SRC_PREFIX}${componentRelPath}`;\n\tconst timestamp = String(Date.now());\n\n\tconst generators: Record<string, typeof generateSvelteHmrBootstrap> = {\n\t\tsvelte: generateSvelteHmrBootstrap,\n\t\tvue: generateVueHmrBootstrap\n\t};\n\tconst generate = generators[framework];\n\tif (!generate) return undefined;\n\n\treturn jsResponse(generate(url, vendorPaths, timestamp));\n};\n\n// Serve a virtual Svelte CSS module (css:'external' output).\nconst handleVirtualSvelteCss = (cssCheckPath: string) => {\n\tconst virtualCss = svelteExternalCss.get(cssCheckPath);\n\tif (!virtualCss) return undefined;\n\n\tconst escaped = virtualCss\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/`/g, '\\\\`')\n\t\t.replace(/\\$/g, '\\\\$');\n\n\treturn jsResponse(\n\t\t`var s=document.createElement('style');` +\n\t\t\t`s.textContent=\\`${escaped}\\`;` +\n\t\t\t`s.dataset.svelteHmr=${JSON.stringify(cssCheckPath)};` +\n\t\t\t`var p=document.querySelector('style[data-svelte-hmr=\"${cssCheckPath}\"]');` +\n\t\t\t`if(p)p.remove();` +\n\t\t\t`document.head.appendChild(s);`\n\t);\n};\n\n// Resolve a /@src/ path to an absolute file path and extension,\n// probing known extensions if the path has none.\nconst resolveSourcePath = (relPath: string, projectRoot: string) => {\n\tconst filePath = resolve(projectRoot, relPath);\n\tconst ext = extname(filePath);\n\n\tif (ext === '.svelte')\n\t\treturn { ext, filePath: resolveSvelteModulePath(filePath) };\n\tif (ext) return { ext, filePath };\n\n\t// No extension — probe known extensions\n\tconst found = MODULE_EXTENSIONS.find((candidate) =>\n\t\texistsSync(filePath + candidate)\n\t);\n\tif (!found) return { ext, filePath };\n\n\tconst resolved = filePath + found;\n\tif (found === '.svelte')\n\t\treturn { ext: found, filePath: resolveSvelteModulePath(resolved) };\n\n\treturn { ext: found, filePath: resolved };\n};\n\n// Transform and cache a source file, returning a JS Response.\nconst transformAndCache = async (\n\tfilePath: string,\n\text: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tif (ext === '.css') return jsResponse(handleCssRequest(filePath));\n\n\tconst isSvelte =\n\t\text === '.svelte' ||\n\t\tfilePath.endsWith('.svelte.ts') ||\n\t\tfilePath.endsWith('.svelte.js');\n\n\tconst cached = getTransformed(filePath);\n\tif (cached) return jsResponse(cached);\n\n\tif (isSvelte)\n\t\treturn transformAndCacheSvelte(filePath, projectRoot, rewriter);\n\tif (ext === '.vue')\n\t\treturn transformAndCacheVue(filePath, projectRoot, rewriter, vueDir);\n\tif (!TRANSPILABLE.has(ext)) return undefined;\n\n\tconst stat = statSync(filePath);\n\tconst resolvedVueDir = vueDir ? resolve(vueDir) : undefined;\n\tconst content = REACT_EXTENSIONS.has(ext)\n\t\t? transformReactFile(filePath, projectRoot, rewriter)\n\t\t: transformPlainFile(filePath, projectRoot, rewriter, resolvedVueDir);\n\n\tsetTransformed(\n\t\tfilePath,\n\t\tcontent,\n\t\tstat.mtimeMs,\n\t\textractImportedFiles(content, projectRoot)\n\t);\n\n\treturn jsResponse(content);\n};\n\nconst transformAndCacheSvelte = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tconst stat = statSync(filePath);\n\tconst content = await transformSvelteFile(filePath, projectRoot, rewriter);\n\tsetTransformed(\n\t\tfilePath,\n\t\tcontent,\n\t\tstat.mtimeMs,\n\t\textractImportedFiles(content, projectRoot)\n\t);\n\n\treturn jsResponse(content);\n};\n\nconst transformAndCacheVue = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tconst stat = statSync(filePath);\n\tconst content = await transformVueFile(\n\t\tfilePath,\n\t\tprojectRoot,\n\t\trewriter,\n\t\tvueDir\n\t);\n\tsetTransformed(\n\t\tfilePath,\n\t\tcontent,\n\t\tstat.mtimeMs,\n\t\textractImportedFiles(content, projectRoot)\n\t);\n\n\treturn jsResponse(content);\n};\n\n// Build a transform-error response for the browser console.\nconst transformErrorResponse = (err: unknown) => {\n\tconst errMsg = err instanceof Error ? err.message : String(err);\n\n\treturn new Response(\n\t\t`console.error('[ModuleServer] Transform error:', ${JSON.stringify(errMsg)});`,\n\t\t{\n\t\t\theaders: { 'Content-Type': 'application/javascript' },\n\t\t\tstatus: 500\n\t\t}\n\t);\n};\n\nexport const createModuleServer = (config: ModuleServerConfig) => {\n\tconst { projectRoot, vendorPaths, frameworkDirs } = config;\n\tconst rewriter = buildImportRewriter(vendorPaths);\n\n\treturn async (pathname: string) => {\n\t\tif (pathname.startsWith('/@stub/')) return handleStubRequest(pathname);\n\t\tif (pathname.startsWith('/@hmr/'))\n\t\t\treturn handleHmrBootstrap(pathname, vendorPaths);\n\t\tif (!pathname.startsWith(SRC_PREFIX)) return undefined;\n\n\t\tconst relPath = pathname.slice(SRC_PREFIX.length);\n\n\t\tconst virtualCssResponse = handleVirtualSvelteCss(\n\t\t\tresolve(projectRoot, relPath)\n\t\t);\n\t\tif (virtualCssResponse) return virtualCssResponse;\n\n\t\tconst { filePath, ext } = resolveSourcePath(relPath, projectRoot);\n\n\t\ttry {\n\t\t\treturn await transformAndCache(\n\t\t\t\tfilePath,\n\t\t\t\text,\n\t\t\t\tprojectRoot,\n\t\t\t\trewriter,\n\t\t\t\tframeworkDirs?.vue\n\t\t\t);\n\t\t} catch (err) {\n\t\t\treturn transformErrorResponse(err);\n\t\t}\n\t};\n};\n\n// Extract absolute file paths from /@src/ imports in transformed code.\n// Used to build the runtime module graph for chain invalidation.\nconst SRC_IMPORT_RE = /\\/@src\\/([^\"'?\\s]+)/g;\nconst extractImportedFiles = (content: string, projectRoot: string) => {\n\tconst files: string[] = [];\n\tlet match;\n\tSRC_IMPORT_RE.lastIndex = 0;\n\twhile ((match = SRC_IMPORT_RE.exec(content)) !== null) {\n\t\tif (match[1]) files.push(resolve(projectRoot, match[1]));\n\t}\n\n\treturn files;\n};\n\nexport const invalidateModule = (filePath: string) => {\n\t// invalidate() cascades up the import chain — clearing transform\n\t// caches for all transitive importers so they get re-transpiled\n\t// with fresh ?v= params. Also clear mtime caches for the changed\n\t// file so srcUrl() re-reads its mtime from disk.\n\tconst resolved = resolve(filePath);\n\tinvalidate(filePath);\n\tif (resolved !== filePath) invalidate(resolved);\n\tmtimeCache.delete(filePath);\n\tmtimeCache.delete(resolved);\n\t// Note: we only clear mtime for the changed file. Importers'\n\t// mtimes haven't changed — their transform caches are cleared\n\t// by invalidate() so they get re-transpiled with new ?v= for\n\t// the changed file's updated mtime.\n};\n\n// Pre-transpile a /@src/ URL and cache the result so the browser\n// fetch is instant. Called before sending the WebSocket HMR message.\nexport const warmCache = (pathname: string) => {\n\tif (!pathname.startsWith(SRC_PREFIX)) return;\n\tif (!globalModuleServer) return;\n\t// Trigger the handler — the result is cached by setTransformed\n\tglobalModuleServer(pathname);\n};\n\n// Store the module server handler globally so warmCache can access it\nlet globalModuleServer:\n\t| ((\n\t\t\tpathname: string\n\t ) => Promise<Response | undefined> | Response | undefined)\n\t| null = null;\n\nexport const SRC_URL_PREFIX = SRC_PREFIX;\n\nexport const setGlobalModuleServer = (handler: typeof globalModuleServer) => {\n\tglobalModuleServer = handler;\n};\n",
|
|
77
77
|
"/* Simple HTML HMR Implementation\n Lightweight approach: read HTML file → send HTML patch */\n\nimport { resolve } from 'node:path';\n\n/* Simple HTML HMR handler for server-side\n When an HTML file changes:\n 1. Read the HTML file\n 2. Extract body content (or return full HTML)\n 3. Return the HTML for patching */\nexport const handleHTMLUpdate = async (htmlFilePath: string) => {\n\tlet htmlContent: string;\n\ttry {\n\t\tconst resolvedPath = resolve(htmlFilePath);\n\t\tconst file = Bun.file(resolvedPath);\n\t\tif (!(await file.exists())) {\n\t\t\treturn null;\n\t\t}\n\t\thtmlContent = await file.text();\n\t} catch {\n\t\treturn null;\n\t}\n\n\tconst headMatch = htmlContent.match(/<head[^>]*>([\\s\\S]*?)<\\/head>/i);\n\tconst bodyMatch = htmlContent.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n\n\tif (bodyMatch && bodyMatch[1]) {\n\t\treturn {\n\t\t\tbody: bodyMatch[1].trim(),\n\t\t\thead: headMatch && headMatch[1] ? headMatch[1].trim() : null\n\t\t};\n\t}\n\n\treturn htmlContent;\n};\n",
|
package/dist/cli/index.js
CHANGED
|
@@ -1074,28 +1074,52 @@ Found ${errorCount} error${suffix}.`;
|
|
|
1074
1074
|
console.error("\x1B[31m\u2717\x1B[0m vue-tsc is required for Vue type checking. Install it: bun add -d vue-tsc");
|
|
1075
1075
|
process.exit(1);
|
|
1076
1076
|
}
|
|
1077
|
-
|
|
1077
|
+
const vueTsconfigPath = join7(cacheDir, "tsconfig.vue-check.json");
|
|
1078
|
+
const exclude = [
|
|
1079
|
+
"**/.absolutejs/**/*",
|
|
1080
|
+
"**/build/**/*",
|
|
1081
|
+
"**/dist/**/*",
|
|
1082
|
+
"**/generated/**/*"
|
|
1083
|
+
];
|
|
1084
|
+
return writeFile(vueTsconfigPath, JSON.stringify({
|
|
1085
|
+
extends: resolve7("tsconfig.json"),
|
|
1086
|
+
exclude
|
|
1087
|
+
}, null, "\t")).then(() => run("vue-tsc", [
|
|
1078
1088
|
vueTscBin,
|
|
1079
1089
|
"--noEmit",
|
|
1090
|
+
"--project",
|
|
1091
|
+
resolve7(vueTsconfigPath),
|
|
1080
1092
|
"--incremental",
|
|
1081
1093
|
"--tsBuildInfoFile",
|
|
1082
1094
|
join7(cacheDir, "vue-tsc.tsbuildinfo"),
|
|
1083
1095
|
"--pretty"
|
|
1084
|
-
]);
|
|
1096
|
+
]));
|
|
1085
1097
|
}, buildTscCheck = (cacheDir) => {
|
|
1086
1098
|
const tscBin = findBin("tsc");
|
|
1087
1099
|
if (!tscBin) {
|
|
1088
1100
|
console.error("\x1B[31m\u2717\x1B[0m typescript is required for type checking. Install it: bun add -d typescript");
|
|
1089
1101
|
process.exit(1);
|
|
1090
1102
|
}
|
|
1091
|
-
|
|
1103
|
+
const tscConfigPath = join7(cacheDir, "tsconfig.typecheck.json");
|
|
1104
|
+
const exclude = [
|
|
1105
|
+
"**/.absolutejs/**/*",
|
|
1106
|
+
"**/build/**/*",
|
|
1107
|
+
"**/dist/**/*",
|
|
1108
|
+
"**/generated/**/*"
|
|
1109
|
+
];
|
|
1110
|
+
return writeFile(tscConfigPath, JSON.stringify({
|
|
1111
|
+
extends: resolve7("tsconfig.json"),
|
|
1112
|
+
exclude
|
|
1113
|
+
}, null, "\t")).then(() => run("tsc", [
|
|
1092
1114
|
tscBin,
|
|
1093
1115
|
"--noEmit",
|
|
1116
|
+
"--project",
|
|
1117
|
+
resolve7(tscConfigPath),
|
|
1094
1118
|
"--incremental",
|
|
1095
1119
|
"--tsBuildInfoFile",
|
|
1096
1120
|
join7(cacheDir, "tsc.tsbuildinfo"),
|
|
1097
1121
|
"--pretty"
|
|
1098
|
-
]);
|
|
1122
|
+
]));
|
|
1099
1123
|
}, buildSvelteCheck = async (cacheDir, svelteDir) => {
|
|
1100
1124
|
const svelteBin = findBin("svelte-check");
|
|
1101
1125
|
if (!svelteBin) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type {} from '../../types/globals';
|
|
1
2
|
/* CSS reload/preload utilities for HMR */
|
|
2
3
|
|
|
3
|
-
import { type CSSUpdateResult, hmrState } from '
|
|
4
|
+
import { type CSSUpdateResult, hmrState } from '../../types/client';
|
|
4
5
|
import {
|
|
5
6
|
CSS_ERROR_RESOLVE_DELAY_MS,
|
|
6
7
|
CSS_MAX_CHECK_ATTEMPTS,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type {} from '../../types/globals';
|
|
1
2
|
/* DOM state snapshot/restore to preserve user-visible state across HMR */
|
|
2
3
|
|
|
3
|
-
import type { DOMStateEntry, DOMStateSnapshot } from '
|
|
4
|
+
import type { DOMStateEntry, DOMStateSnapshot } from '../../types/client';
|
|
4
5
|
import {
|
|
5
6
|
FOCUS_ID_PREFIX_LENGTH,
|
|
6
7
|
FOCUS_IDX_PREFIX_LENGTH,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type {} from '../../types/globals';
|
|
1
2
|
/* AbsoluteJS Error Overlay - branded, per-framework, modern styling */
|
|
2
3
|
|
|
3
|
-
import type { ErrorOverlayOptions } from '
|
|
4
|
+
import type { ErrorOverlayOptions } from '../../types/client';
|
|
4
5
|
import { OVERLAY_FADE_DURATION_MS } from './constants';
|
|
5
6
|
|
|
6
7
|
let errorOverlayElement: HTMLDivElement | null = null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {} from '../../../types/globals';
|
|
1
2
|
/* HTML + script HMR update handlers */
|
|
2
3
|
|
|
3
4
|
import { DOM_UPDATE_DELAY_MS } from '../constants';
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
import { processCSSLinks, waitForCSSAndUpdate } from '../cssUtils';
|
|
14
15
|
import { patchHeadInPlace } from '../headPatch';
|
|
15
16
|
import { detectCurrentFramework } from '../frameworkDetect';
|
|
16
|
-
import { type ScriptInfo, hmrState } from '
|
|
17
|
+
import { type ScriptInfo, hmrState } from '../../../types/client';
|
|
17
18
|
import { restoreDOMChanges, snapshotDOMChanges } from '../domTracker';
|
|
18
19
|
|
|
19
20
|
const parseHTMLMessage = (
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {} from '../../../types/globals';
|
|
1
2
|
/* HTMX HMR update handler */
|
|
2
3
|
|
|
3
4
|
import { DOM_UPDATE_DELAY_MS } from '../constants';
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
type HTMXSavedState,
|
|
18
19
|
type ScriptInfo,
|
|
19
20
|
hmrState
|
|
20
|
-
} from '
|
|
21
|
+
} from '../../../types/client';
|
|
21
22
|
|
|
22
23
|
const parseHTMXMessage = (
|
|
23
24
|
html: string | { body?: string; head?: string } | null | undefined
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type {} from '../../../types/globals';
|
|
1
2
|
/* Vue HMR update handler */
|
|
2
3
|
|
|
3
|
-
import type { VueComponentInstance, VueVNode } from '
|
|
4
|
+
import type { VueComponentInstance, VueVNode } from '../../../types/vue';
|
|
4
5
|
import { saveDOMState, restoreDOMState } from '../domState';
|
|
5
6
|
import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
|
|
6
7
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type {} from '../../types/globals';
|
|
1
2
|
/* AbsoluteJS HMR Client - Entry point
|
|
2
3
|
Initializes WebSocket connection, dispatches messages to framework handlers */
|
|
3
4
|
|
|
4
|
-
import { hmrState } from '
|
|
5
|
+
import { hmrState } from '../../types/client';
|
|
5
6
|
import {
|
|
6
7
|
HMR_UPDATE_TIMEOUT_MS,
|
|
7
8
|
MAX_RECONNECT_ATTEMPTS,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {} from '../../types/globals';
|
|
1
2
|
/* React Refresh runtime setup — must be imported before any component modules.
|
|
2
3
|
Bun's reactFastRefresh flag injects $RefreshSig$/$RefreshReg$ calls into
|
|
3
4
|
component code. This module ensures those globals exist before components
|
package/dist/index.js.map
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"const ESCAPE_LOOKUP: Record<string, string> = {\n\t'\\u2028': '\\\\u2028',\n\t'\\u2029': '\\\\u2029',\n\t'&': '\\\\u0026',\n\t'<': '\\\\u003C',\n\t'>': '\\\\u003E'\n};\n\nconst ESCAPE_REGEX = /[&><\\u2028\\u2029]/g;\n\nexport const escapeScriptContent = (content: string) =>\n\tcontent.replace(ESCAPE_REGEX, (char) => {\n\t\tconst escaped = ESCAPE_LOOKUP[char];\n\n\t\treturn escaped !== undefined ? escaped : char;\n\t});\n",
|
|
72
72
|
"import type { Component } from 'svelte';\nimport { DEFAULT_CHUNK_SIZE } from '../constants';\nimport { escapeScriptContent } from '../utils/escapeScriptContent';\n\nexport type RenderStreamOptions = {\n\tbootstrapScriptContent?: string;\n\tbootstrapScripts?: string[];\n\tbootstrapModules?: string[];\n\tnonce?: string;\n\tonError?: (error: unknown) => void;\n\tprogressiveChunkSize?: number;\n\tsignal?: AbortSignal;\n\theadContent?: string;\n\tbodyContent?: string;\n};\n\nexport const renderToReadableStream = async <\n\tProps extends Record<string, unknown> = Record<string, never>\n>(\n\tcomponent: Component<Props>,\n\tprops?: Props,\n\t{\n\t\tbootstrapScriptContent,\n\t\tbootstrapScripts = [],\n\t\tbootstrapModules = [],\n\t\tnonce,\n\t\tonError = console.error,\n\t\tprogressiveChunkSize = DEFAULT_CHUNK_SIZE,\n\t\tsignal,\n\t\theadContent,\n\t\tbodyContent\n\t}: RenderStreamOptions = {}\n) => {\n\ttry {\n\t\tconst { render } = await import('svelte/server');\n\t\tconst { head: rawHead, body } =\n\t\t\ttypeof props === 'undefined'\n\t\t\t\t? // @ts-expect-error Svelte's render function can't determine which overload to choose when the component is generic\n\t\t\t\t\trender(component)\n\t\t\t\t: render(component, { props });\n\t\t// Svelte SSR extracts <title> from <svelte:head> and appends it\n\t\t// after the component end marker (<!---->). The client renderer\n\t\t// places it at its template position, causing a hydration mismatch.\n\t\t// Fix: move <title> back inside the first component marker so both\n\t\t// SSR and client agree on its position.\n\t\tconst head = rawHead.replace(\n\t\t\t/(<!--[a-z0-9]+-->)([\\s\\S]*?)(<!---->)\\s*(<title>[\\s\\S]*?<\\/title>)/,\n\t\t\t'$1$4$2$3'\n\t\t);\n\t\tconst nonceAttr = nonce ? ` nonce=\"${nonce}\"` : '';\n\t\tconst scripts =\n\t\t\t(bootstrapScriptContent\n\t\t\t\t? `<script${nonceAttr}>${escapeScriptContent(bootstrapScriptContent)}</script>`\n\t\t\t\t: '') +\n\t\t\tbootstrapScripts\n\t\t\t\t.map((src) => `<script${nonceAttr} src=\"${src}\"></script>`)\n\t\t\t\t.join('') +\n\t\t\tbootstrapModules\n\t\t\t\t.map(\n\t\t\t\t\t(src) =>\n\t\t\t\t\t\t`<script${nonceAttr} type=\"module\" src=\"${src}\"></script>`\n\t\t\t\t)\n\t\t\t\t.join('');\n\t\tconst encoder = new TextEncoder();\n\t\t// Warning: this encodes the entire document into memory in one buffer\n\t\tconst full = encoder.encode(\n\t\t\t`<!DOCTYPE html><html lang=\"en\"><head>${head}${headContent ?? ''}</head><body>${body}${scripts}${bodyContent ?? ''}</body></html>`\n\t\t);\n\n\t\tlet offset = 0;\n\n\t\treturn new ReadableStream<Uint8Array>({\n\t\t\ttype: 'bytes',\n\t\t\tcancel(reason) {\n\t\t\t\tonError?.(reason);\n\t\t\t},\n\t\t\tpull(controller) {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tcontroller.close();\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (offset >= full.length) {\n\t\t\t\t\tcontroller.close();\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst end = Math.min(\n\t\t\t\t\toffset + progressiveChunkSize,\n\t\t\t\t\tfull.length\n\t\t\t\t);\n\t\t\t\tcontroller.enqueue(full.subarray(offset, end));\n\t\t\t\toffset = end;\n\t\t\t}\n\t\t});\n\t} catch (error) {\n\t\tonError?.(error);\n\t\tthrow error;\n\t}\n};\n",
|
|
73
73
|
"import type { Component as SvelteComponent } from 'svelte';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (indexPath: string, props?: unknown) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(props)};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst scriptTag = indexPath\n\t\t? `<script type=\"module\" src=\"${indexPath}\"></script>`\n\t\t: '';\n\tconst html = `<!DOCTYPE html><html><head></head><body><script>${propsScript}${dirtyFlag}</script>${scriptTag}</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport type HandleSveltePageRequest = {\n\t(\n\t\tPageComponent: SvelteComponent<Record<string, never>>,\n\t\tpagePath: string,\n\t\tindexPath: string\n\t): Promise<Response>;\n\t<P extends Record<string, unknown>>(\n\t\tPageComponent: SvelteComponent<P>,\n\t\tpagePath: string,\n\t\tindexPath: string,\n\t\tprops: NoInfer<P>\n\t): Promise<Response>;\n};\n\nexport const handleSveltePageRequest: HandleSveltePageRequest = async <\n\tP extends Record<string, unknown>\n>(\n\t_PageComponent: SvelteComponent<P>,\n\tpagePath: string,\n\tindexPath: string,\n\tprops?: P\n) => {\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(indexPath, props);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { renderToReadableStream } = await import(\n\t\t\t'./renderToReadableStream'\n\t\t);\n\n\t\tconst stream = await renderToReadableStream(\n\t\t\tImportedPageComponent,\n\t\t\tprops,\n\t\t\t{\n\t\t\t\tbootstrapModules: indexPath ? [indexPath] : [],\n\t\t\t\tbootstrapScriptContent: `window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\t\t\tprops\n\t\t\t\t)}`\n\t\t\t}\n\t\t);\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Svelte render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'svelte',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('svelte', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateSvelteSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
74
|
-
"import type { Component as VueComponent } from 'vue';\nimport type { VuePropsOf } from '../../types/vue';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (\n\theadTag: string,\n\tindexPath: string,\n\tmaybeProps: Record<string, unknown> | undefined\n) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps ?? {})};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst html =\n\t\t`<!DOCTYPE html><html>${headTag}<body><div id=\"root\"></div>` +\n\t\t`<script>${propsScript}${dirtyFlag}</script>` +\n\t\t`<script type=\"module\" src=\"${indexPath}\"></script>` +\n\t\t`</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport const handleVuePageRequest = async <Component extends VueComponent>(\n\t_PageComponent: Component,\n\tpagePath: string,\n\tindexPath: string,\n\theadTag: `<head>${string}</head>` = '<head></head>',\n\t...props: keyof VuePropsOf<Component> extends never\n\t\t? []\n\t\t: [props: NoInfer<VuePropsOf<Component>>]\n) => {\n\tconst [maybeProps] = props;\n\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(headTag, indexPath, maybeProps);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { createSSRApp, h } = await import('vue');\n\t\tconst { renderToWebStream } = await import('vue/server-renderer');\n\n\t\tconst app = createSSRApp({\n\t\t\trender: () => h(ImportedPageComponent, maybeProps ?? null)\n\t\t});\n\n\t\tconst bodyStream = renderToWebStream(app);\n\n\t\tconst head = `<!DOCTYPE html><html>${headTag}<body><div id=\"root\">`;\n\t\tconst tail = `</div><script>window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\tmaybeProps ?? {}\n\t\t)}</script><script type=\"module\" src=\"${indexPath}\"></script></body></html>`;\n\n\t\tconst stream = new ReadableStream({\n\t\t\tstart(controller) {\n\t\t\t\tcontroller.enqueue(head);\n\t\t\t\tconst reader = bodyStream.getReader();\n\t\t\t\tconst pumpLoop = () => {\n\t\t\t\t\treader\n\t\t\t\t\t\t.read()\n\t\t\t\t\t\t.then(({ done, value }) =>\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\t\t? (controller.enqueue(tail), controller.close())\n\t\t\t\t\t\t\t\t: (controller.enqueue(value), pumpLoop())\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.catch((err) => controller.error(err));\n\t\t\t\t};\n\t\t\t\tpumpLoop();\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Vue render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'vue',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('vue', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateVueSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
74
|
+
"import type { Component as VueComponent } from 'vue';\nimport type { VuePropsOf } from '../../types/vue';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (\n\theadTag: string,\n\tindexPath: string,\n\tmaybeProps: Record<string, unknown> | undefined\n) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps ?? {})};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst html =\n\t\t`<!DOCTYPE html><html>${headTag}<body><div id=\"root\"></div>` +\n\t\t`<script>${propsScript}${dirtyFlag}</script>` +\n\t\t`<script type=\"module\" src=\"${indexPath}\"></script>` +\n\t\t`</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport const handleVuePageRequest = async <Component extends VueComponent>(\n\t_PageComponent: Component,\n\tpagePath: string,\n\tindexPath: string,\n\theadTag: `<head>${string}</head>` = '<head></head>',\n\t...props: keyof VuePropsOf<Component> extends never\n\t\t? [props?: Record<string, never>]\n\t\t: [props: NoInfer<VuePropsOf<Component>>]\n) => {\n\tconst [maybeProps] = props;\n\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(headTag, indexPath, maybeProps);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { createSSRApp, h } = await import('vue');\n\t\tconst { renderToWebStream } = await import('vue/server-renderer');\n\n\t\tconst app = createSSRApp({\n\t\t\trender: () => h(ImportedPageComponent, maybeProps ?? null)\n\t\t});\n\n\t\tconst bodyStream = renderToWebStream(app);\n\n\t\tconst head = `<!DOCTYPE html><html>${headTag}<body><div id=\"root\">`;\n\t\tconst tail = `</div><script>window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\tmaybeProps ?? {}\n\t\t)}</script><script type=\"module\" src=\"${indexPath}\"></script></body></html>`;\n\n\t\tconst stream = new ReadableStream({\n\t\t\tstart(controller) {\n\t\t\t\tcontroller.enqueue(head);\n\t\t\t\tconst reader = bodyStream.getReader();\n\t\t\t\tconst pumpLoop = () => {\n\t\t\t\t\treader\n\t\t\t\t\t\t.read()\n\t\t\t\t\t\t.then(({ done, value }) =>\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\t\t? (controller.enqueue(tail), controller.close())\n\t\t\t\t\t\t\t\t: (controller.enqueue(value), pumpLoop())\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.catch((err) => controller.error(err));\n\t\t\t\t};\n\t\t\t\tpumpLoop();\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Vue render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'vue',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('vue', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateVueSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
75
75
|
"type CacheEntry = {\n\tcontent: string;\n\timports: string[];\n\tmtime: number;\n};\n\n// Persist across bun --hot reloads so HMR doesn't refetch everything\nconst cache = globalThis.__transformCache ?? new Map<string, CacheEntry>();\nglobalThis.__transformCache = cache;\n\n// Reverse map: importedFile → Set<files that import it>\n// Used to cascade invalidation up the import chain.\nconst importers =\n\tglobalThis.__transformImporters ?? new Map<string, Set<string>>();\nglobalThis.__transformImporters = importers;\n\n// Cache entries are invalidated by invalidateModule() when files\n// change — no need to re-stat on every read. If it's in the cache,\n// it's valid.\nexport const getTransformed = (filePath: string) =>\n\tcache.get(filePath)?.content;\n\nexport const setTransformed = (\n\tfilePath: string,\n\tcontent: string,\n\tmtime: number,\n\timports?: string[]\n) => {\n\tconst resolvedImports = imports ?? [];\n\tcache.set(filePath, { content, imports: resolvedImports, mtime });\n\n\t// Update reverse dependency map\n\tfor (const imp of resolvedImports) {\n\t\tconst set = importers.get(imp) ?? new Set<string>();\n\t\timporters.set(imp, set);\n\t\tset.add(filePath);\n\t}\n};\n\n// Per-file invalidation version. Incremented when a file's transform\n// cache is cleared due to a downstream import changing. Used by\n// srcUrl() to force browser re-fetch even if the file's mtime is same.\nconst invalidationVersions =\n\tglobalThis.__transformInvalidationVersions ?? new Map<string, number>();\nglobalThis.__transformInvalidationVersions = invalidationVersions;\n\nconst isComponentFile = (filePath: string) =>\n\tfilePath.endsWith('.tsx') || filePath.endsWith('.jsx');\n\nconst processParents = (parents: Set<string>, queue: string[]) => {\n\tconst component = [...parents].find(isComponentFile);\n\tif (component !== undefined) return component;\n\n\tfor (const parent of parents) queue.push(parent);\n\n\treturn undefined;\n};\n\nexport const findNearestComponent = (filePath: string) => {\n\tconst visited = new Set<string>();\n\tconst queue = [filePath];\n\n\twhile (queue.length > 0) {\n\t\tconst current = queue.shift();\n\t\tif (!current) break;\n\t\tif (visited.has(current)) continue;\n\t\tvisited.add(current);\n\n\t\tconst parents = importers.get(current);\n\t\tif (!parents) continue;\n\n\t\tconst found = processParents(parents, queue);\n\t\tif (found !== undefined) return found;\n\t}\n\n\treturn undefined;\n};\nexport const getInvalidationVersion = (filePath: string) =>\n\tinvalidationVersions.get(filePath) ?? 0;\nexport const invalidate = (filePath: string) => {\n\tcache.delete(filePath);\n\n\t// Bump the CHANGED file's version so when importers are\n\t// re-transpiled, srcUrl() generates a new ?v= for it.\n\tinvalidationVersions.set(\n\t\tfilePath,\n\t\t(invalidationVersions.get(filePath) ?? 0) + 1\n\t);\n\n\t// Clear transform cache for direct importers so they get\n\t// re-transpiled with the changed file's new ?v= param.\n\tfor (const parent of importers.get(filePath) ?? []) {\n\t\tcache.delete(parent);\n\t}\n};\nexport const invalidateAll = () => {\n\tcache.clear();\n\timporters.clear();\n};\n",
|
|
76
76
|
"import { BASE_36_RADIX, UNFOUND_INDEX } from '../constants';\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport { basename, dirname, extname, resolve, relative } from 'node:path';\nimport {\n\tgetInvalidationVersion,\n\tgetTransformed,\n\tinvalidate,\n\tsetTransformed\n} from './transformCache';\n\nconst SRC_PREFIX = '/@src/';\n\nconst jsTranspiler = new Bun.Transpiler({\n\tloader: 'js',\n\ttrimUnusedImports: true\n});\n\n// Shared transpiler for TypeScript files — trimUnusedImports strips\n// type-only imports so the browser doesn't request unnecessary modules\n// Separate transpilers for .ts and .tsx — using 'tsx' for .ts files\n// causes parse errors on TypeScript generics like <T> (interpreted as JSX).\nconst tsTranspiler = new Bun.Transpiler({\n\tloader: 'ts',\n\ttrimUnusedImports: true\n});\n\nconst tsxTranspiler = new Bun.Transpiler({\n\tloader: 'tsx',\n\ttrimUnusedImports: true\n});\n\nconst TRANSPILABLE = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs']);\n\n// Regex to find all export names in original TypeScript source\nconst ALL_EXPORTS_RE =\n\t/export\\s+(?:type|interface|const|let|var|function|class|enum|abstract\\s+class)\\s+(\\w+)/g;\n\n// Strip string/template literal contents so regex doesn't match\n// export declarations inside code examples embedded as strings.\nconst STRING_CONTENTS_RE =\n\t/`(?:[^`\\\\]|\\\\.)*`|'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"/gs;\n\n// After transpilation, type exports are stripped. Inject stubs so\n// importing modules can resolve the names (as undefined).\nconst preserveTypeExports = (\n\toriginalSource: string,\n\ttranspiled: string,\n\tvalueExports: string[]\n) => {\n\tconst codeOnly = originalSource.replace(STRING_CONTENTS_RE, '\"\"');\n\tconst allExports: string[] = [];\n\tlet match;\n\tALL_EXPORTS_RE.lastIndex = 0;\n\twhile ((match = ALL_EXPORTS_RE.exec(codeOnly)) !== null) {\n\t\tif (match[1]) allExports.push(match[1]);\n\t}\n\n\tconst valueSet = new Set(valueExports);\n\tconst typeExports = allExports.filter((exp) => !valueSet.has(exp));\n\n\tif (typeExports.length === 0) return transpiled;\n\n\tconst stubs = typeExports\n\t\t.map((name) => `export const ${name} = undefined;`)\n\t\t.join('\\n');\n\n\treturn `${transpiled}\\n${stubs}\\n`;\n};\n// Try known extensions to resolve an extensionless path. Returns\n// the original path if none match (existsSync-based probing).\nconst resolveRelativeExtension = (\n\tsrcPath: string,\n\tprojectRoot: string,\n\textensions: string[]\n) => {\n\tconst found = extensions.find((ext) =>\n\t\texistsSync(resolve(projectRoot, srcPath + ext))\n\t);\n\n\treturn found ? srcPath + found : srcPath;\n};\n\nconst IMPORT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.svelte', '.vue'];\nconst SIDE_EFFECT_EXTENSIONS = [\n\t'.tsx',\n\t'.ts',\n\t'.jsx',\n\t'.js',\n\t'.css',\n\t'.svelte',\n\t'.vue'\n];\nconst MODULE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.svelte', '.vue'];\n\nconst REACT_EXTENSIONS = new Set(['.tsx', '.jsx']);\n\ntype ModuleServerConfig = {\n\tprojectRoot: string;\n\tvendorPaths: Record<string, string>;\n\tframeworkDirs?: {\n\t\tvue?: string;\n\t};\n};\n\nconst escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nconst buildImportRewriter = (vendorPaths: Record<string, string>) => {\n\tconst entries = Object.entries(vendorPaths).sort(\n\t\t([a], [b]) => b.length - a.length\n\t);\n\tif (entries.length === 0) return null;\n\n\tconst alt = entries.map(([spec]) => escapeRegex(spec)).join('|');\n\tconst lookup = new Map(entries);\n\n\t// Single combined regex for all vendor import patterns:\n\t// from 'pkg', import 'pkg', import('pkg')\n\tconst vendorRegex = new RegExp(\n\t\t`((?:from|import)\\\\s*[\"']|import\\\\s*\\\\(\\\\s*[\"'])(${alt})([\"'](?:\\\\s*[;)]?)?)`,\n\t\t'g'\n\t);\n\n\treturn { lookup, vendorRegex };\n};\n\n// Mtime cache — avoids statSync on every import rewrite.\n// Invalidated by the file watcher via invalidateModule().\nconst mtimeCache = new Map<string, number>();\n\n// Append invalidation version if the file's transform cache was\n// cleared (e.g., a downstream import changed). This forces the\n// browser to re-fetch even though the file's own mtime is the same.\nconst buildVersion = (mtime: number, absPath: string) => {\n\tconst invalidationVersion = getInvalidationVersion(absPath);\n\n\treturn invalidationVersion > 0\n\t\t? `${mtime}.${invalidationVersion}`\n\t\t: `${mtime}`;\n};\n\n// Build a /@src/ URL with the file's mtime as a cache buster.\nconst srcUrl = (relPath: string, projectRoot: string) => {\n\tconst base = `${SRC_PREFIX}${relPath.replace(/\\\\/g, '/')}`;\n\tconst absPath = resolve(projectRoot, relPath);\n\n\tconst cached = mtimeCache.get(absPath);\n\tif (cached !== undefined)\n\t\treturn `${base}?v=${buildVersion(cached, absPath)}`;\n\n\ttry {\n\t\tconst mtime = Math.round(statSync(absPath).mtimeMs);\n\t\tmtimeCache.set(absPath, mtime);\n\n\t\treturn `${base}?v=${buildVersion(mtime, absPath)}`;\n\t} catch {\n\t\treturn base;\n\t}\n};\n\n// Resolve a relative import specifier to a /@src/ URL path.\n// Probes known extensions, resolves .svelte module files.\nconst resolveRelativeImport = (\n\trelPath: string,\n\tfileDir: string,\n\tprojectRoot: string,\n\textensions: string[]\n) => {\n\tconst absPath = resolve(fileDir, relPath);\n\tconst rel = relative(projectRoot, absPath);\n\tlet srcPath = extname(rel)\n\t\t? rel\n\t\t: resolveRelativeExtension(rel, projectRoot, extensions);\n\n\t// Resolve Svelte module files: .svelte → .svelte.ts / .svelte.js\n\tif (extname(srcPath) === '.svelte') {\n\t\tsrcPath = relative(\n\t\t\tprojectRoot,\n\t\t\tresolveSvelteModulePath(resolve(projectRoot, srcPath))\n\t\t);\n\t}\n\n\treturn srcUrl(srcPath, projectRoot);\n};\n\n// Resolve @absolutejs/absolute/* specifiers to project-relative paths.\n// Returns the relative path string on success, or undefined if resolution fails.\nconst resolveAbsoluteSpecifier = (specifier: string, projectRoot: string) => {\n\ttry {\n\t\tconst resolved = Bun.resolveSync(specifier, projectRoot);\n\n\t\t// Prefer browser-targeted build if available (server builds\n\t\t// import node:fs which can't run in browsers)\n\t\tconst browserPath = resolved.replace(\n\t\t\t/\\/index\\.js$/,\n\t\t\t'/browser/index.js'\n\t\t);\n\t\tconst target = existsSync(browserPath) ? browserPath : resolved;\n\n\t\treturn relative(projectRoot, target);\n\t} catch {\n\t\t// Resolution failed — caller falls through to stub\n\t\treturn undefined;\n\t}\n};\n\nconst rewriteImports = (\n\tcode: string,\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tlet result = code;\n\n\t// Step 1: Rewrite KNOWN vendor specifiers in a single pass.\n\tconst vendorReplace = (\n\t\t_match: string,\n\t\tprefix: string,\n\t\tspecifier: string,\n\t\tsuffix: string\n\t) => {\n\t\tconst webPath = rewriter?.lookup.get(specifier);\n\n\t\treturn webPath ? `${prefix}${webPath}${suffix}` : _match;\n\t};\n\n\tif (rewriter) {\n\t\trewriter.vendorRegex.lastIndex = 0;\n\t\tresult = result.replace(rewriter.vendorRegex, vendorReplace);\n\t}\n\n\t// Step 2: Rewrite remaining bare specifiers (unknown packages) to stubs.\n\t// Line-anchored to avoid matching inside string literals.\n\tconst stubReplace = (\n\t\t_match: string,\n\t\tprefix: string,\n\t\tspecifier: string,\n\t\tsuffix: string\n\t) => {\n\t\t// Skip if already rewritten to a path\n\t\tif (specifier.startsWith('/') || specifier.startsWith('.'))\n\t\t\treturn _match;\n\n\t\t// Serve @absolutejs/absolute client-safe exports as real modules\n\t\t// instead of stubbing them — they contain Image/Head/JsonLd components\n\t\t// needed for client-side hydration.\n\t\tif (!specifier.startsWith('@absolutejs/absolute/'))\n\t\t\treturn `${prefix}/@stub/${encodeURIComponent(specifier)}${suffix}`;\n\n\t\tconst resolved = resolveAbsoluteSpecifier(specifier, projectRoot);\n\t\tif (resolved) return `${prefix}/@src/${resolved}${suffix}`;\n\n\t\treturn `${prefix}/@stub/${encodeURIComponent(specifier)}${suffix}`;\n\t};\n\n\t// Combined: import/export from 'bare', import 'bare' (line-anchored)\n\t// Uses [\\s\\S]+? to match multi-line imports (e.g., import {\\n foo\\n} from 'pkg')\n\tresult = result.replace(\n\t\t/^((?:import\\s+[\\s\\S]+?\\s+from|export\\s+[\\s\\S]+?\\s+from|import)\\s*[\"'])([^\"'./][^\"']*)([\"'])/gm,\n\t\tstubReplace\n\t);\n\t// Dynamic: import('bare')\n\tresult = result.replace(\n\t\t/(import\\s*\\(\\s*[\"'])([^\"'./][^\"']*)([\"']\\s*\\))/g,\n\t\tstubReplace\n\t);\n\n\t// Rewrite relative imports to /@src/ absolute paths\n\tconst fileDir = dirname(filePath);\n\tresult = result.replace(\n\t\t/(from\\s*[\"'])(\\.\\.?\\/[^\"']+)([\"'])/g,\n\t\t(_match, prefix, relPath, suffix) =>\n\t\t\t`${prefix}${resolveRelativeImport(relPath, fileDir, projectRoot, IMPORT_EXTENSIONS)}${suffix}`\n\t);\n\n\t// Rewrite dynamic relative imports\n\tresult = result.replace(\n\t\t/(import\\s*\\(\\s*[\"'])(\\.\\.?\\/[^\"']+)([\"']\\s*\\))/g,\n\t\t(_match, prefix, relPath, suffix) =>\n\t\t\t`${prefix}${resolveRelativeImport(relPath, fileDir, projectRoot, IMPORT_EXTENSIONS)}${suffix}`\n\t);\n\n\t// Rewrite side-effect relative imports: import './foo'\n\tresult = result.replace(\n\t\t/(import\\s*[\"'])(\\.\\.?\\/[^\"']+)([\"']\\s*;?)/g,\n\t\t(_match, prefix, relPath, suffix) =>\n\t\t\t`${prefix}${resolveRelativeImport(relPath, fileDir, projectRoot, SIDE_EFFECT_EXTENSIONS)}${suffix}`\n\t);\n\n\t// Rewrite absolute filesystem paths (from generated index files that\n\t// import hmrClient, refreshSetup, etc. via absolute paths)\n\tresult = result.replace(\n\t\t/((?:from|import)\\s*[\"'])(\\/[^\"']+\\.(tsx?|jsx?|ts))([\"'])/g,\n\t\t(_match, prefix, absPath, _ext, suffix) => {\n\t\t\tif (absPath.startsWith(projectRoot)) {\n\t\t\t\tconst rel = relative(projectRoot, absPath).replace(/\\\\/g, '/');\n\n\t\t\t\treturn `${prefix}${srcUrl(rel, projectRoot)}${suffix}`;\n\t\t\t}\n\t\t\t// Path outside project root (e.g., node_modules package src)\n\t\t\t// Try to make it relative to project root anyway\n\t\t\tconst rel = relative(projectRoot, absPath).replace(/\\\\/g, '/');\n\t\t\tif (!rel.startsWith('..')) {\n\t\t\t\treturn `${prefix}${srcUrl(rel, projectRoot)}${suffix}`;\n\t\t\t}\n\n\t\t\treturn _match;\n\t\t}\n\t);\n\n\t// Rewrite new URL('./relative', import.meta.url) for web workers / assets\n\tresult = result.replace(\n\t\t/new\\s+URL\\(\\s*[\"'](\\.\\.?\\/[^\"']+)[\"']\\s*,\\s*import\\.meta\\.url\\s*\\)/g,\n\t\t(_match, relPath) => {\n\t\t\tconst absPath = resolve(fileDir, relPath);\n\t\t\tconst rel = relative(projectRoot, absPath);\n\n\t\t\treturn `new URL('${srcUrl(rel, projectRoot)}', import.meta.url)`;\n\t\t}\n\t);\n\n\t// Rewrite import.meta.resolve('./relative') for asset/worker references\n\tresult = result.replace(\n\t\t/import\\.meta\\.resolve\\(\\s*[\"'](\\.\\.?\\/[^\"']+)[\"']\\s*\\)/g,\n\t\t(_match, relPath) => {\n\t\t\tconst absPath = resolve(fileDir, relPath);\n\t\t\tconst rel = relative(projectRoot, absPath);\n\n\t\t\treturn `'${srcUrl(rel, projectRoot)}'`;\n\t\t}\n\t);\n\n\treturn result;\n};\n\n// Use Bun.Transpiler (~0.1ms) instead of Bun.build (~2-150ms) for\n// React files. Manually inject $RefreshReg$/$RefreshSig$ calls\n// after transpilation.\n// Bun.Transpiler uses an auto-generated name for JSX (jsxDEV_XXXXXXXX)\n// but doesn't emit the import statement. We need to detect the generated\n// name and add the import ourselves.\nconst JSX_AUTO_RE = /\\b(jsxDEV_[a-z0-9]+)\\b/;\nconst JSXS_AUTO_RE = /\\b(jsxs_[a-z0-9]+)\\b/;\nconst JSX_PROD_RE = /\\b(jsx_[a-z0-9]+)\\b/;\nconst FRAGMENT_RE = /\\b(Fragment_[a-z0-9]+)\\b/;\n\nconst addJsxImport = (code: string) => {\n\tconst imports: string[] = [];\n\n\tconst jsxDevMatch = JSX_AUTO_RE.exec(code);\n\tif (jsxDevMatch) {\n\t\timports.push(\n\t\t\t`import { jsxDEV as ${jsxDevMatch[1]} } from \"react/jsx-dev-runtime\";`\n\t\t);\n\t}\n\n\tconst jsxsMatch = JSXS_AUTO_RE.exec(code);\n\tif (jsxsMatch && (!jsxDevMatch || jsxsMatch[1] !== jsxDevMatch[1])) {\n\t\timports.push(\n\t\t\t`import { jsxs as ${jsxsMatch[1]} } from \"react/jsx-runtime\";`\n\t\t);\n\t}\n\n\tconst jsxProdMatch = JSX_PROD_RE.exec(code);\n\tif (jsxProdMatch) {\n\t\timports.push(\n\t\t\t`import { jsx as ${jsxProdMatch[1]} } from \"react/jsx-runtime\";`\n\t\t);\n\t}\n\n\tconst fragmentMatch = FRAGMENT_RE.exec(code);\n\tif (fragmentMatch) {\n\t\timports.push(\n\t\t\t`import { Fragment as ${fragmentMatch[1]} } from \"react\";`\n\t\t);\n\t}\n\n\tif (imports.length === 0) return code;\n\n\treturn `${imports.join('\\n')}\\n${code}`;\n};\n\n// With the patched Bun.Transpiler (PR #28312), reactFastRefresh: true\n// injects $RefreshReg$/$RefreshSig$ natively — no manual injection needed.\n// Falls back to plain transpilation if reactFastRefresh isn't available.\n// reactFastRefresh is available via patched Bun (PR #28312) but not\n// yet in the upstream type definitions, so we extend the options type.\nconst reactTranspilerOptions: ConstructorParameters<\n\ttypeof Bun.Transpiler\n>[0] & {\n\treactFastRefresh?: boolean;\n} = {\n\tloader: 'tsx',\n\treactFastRefresh: true,\n\ttrimUnusedImports: true\n};\nconst reactTranspiler = new Bun.Transpiler(reactTranspilerOptions);\n\nconst transformReactFile = (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\tconst valueExports = tsxTranspiler.scan(raw).exports;\n\tlet transpiled = reactTranspiler.transformSync(raw);\n\ttranspiled = preserveTypeExports(raw, transpiled, valueExports);\n\n\t// Bun.Transpiler auto-generates JSX function names (jsxDEV_XXXXXXXX)\n\t// but doesn't emit the import — it expects the bundler to resolve it.\n\ttranspiled = addJsxImport(transpiled);\n\n\t// The patched transpiler imports register/createSignatureFunctionForTransform\n\t// from react-refresh/runtime, creating a separate module instance. But the\n\t// initial bundled index uses window.$RefreshReg$/$RefreshSig$ globals.\n\t// Replace the import with globals so registrations go to the same runtime.\n\ttranspiled = transpiled.replace(\n\t\t/import\\s*\\{[^}]*\\}\\s*from\\s*[\"']react-refresh\\/runtime[\"'];?\\n?/,\n\t\t''\n\t);\n\t// Map the aliased names to the window globals\n\ttranspiled = transpiled.replace(\n\t\t/\\$RefreshReg\\$_[a-z0-9]+/g,\n\t\t'$RefreshReg$'\n\t);\n\ttranspiled = transpiled.replace(\n\t\t/\\$RefreshSig\\$_[a-z0-9]+/g,\n\t\t'$RefreshSig$'\n\t);\n\t// Prepend window global stubs for ESM scope\n\ttranspiled =\n\t\t`var $RefreshReg$ = window.$RefreshReg$ || function(){};\\n` +\n\t\t`var $RefreshSig$ = window.$RefreshSig$ || function(){ return function(t){ return t; }; };\\n${\n\t\t\ttranspiled\n\t\t}`;\n\n\t// Bun.Transpiler uses \"input.tsx\" as the default filename in\n\t// $RefreshReg$ IDs. Replace with the real relative path so IDs\n\t// match the initial bundled registration.\n\tconst relPath = relative(projectRoot, filePath).replace(/\\\\/g, '/');\n\ttranspiled = transpiled.replace(/\\binput\\.tsx:/g, `${relPath}:`);\n\n\treturn rewriteImports(transpiled, filePath, projectRoot, rewriter);\n};\n\n// Use Bun.Transpiler for non-React files (no refresh injection needed)\nconst transformPlainFile = (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\tconst ext = extname(filePath);\n\tconst isTS = ext === '.ts' || ext === '.tsx';\n\tconst isTSX = ext === '.tsx' || ext === '.jsx';\n\n\tlet transpiler = jsTranspiler;\n\tif (isTSX) transpiler = tsxTranspiler;\n\telse if (isTS) transpiler = tsTranspiler;\n\tconst valueExports = isTS ? transpiler.scan(raw).exports : [];\n\tlet transpiled = transpiler.transformSync(raw);\n\n\tif (isTS) {\n\t\ttranspiled = preserveTypeExports(raw, transpiled, valueExports);\n\t}\n\n\ttranspiled = rewriteImports(transpiled, filePath, projectRoot, rewriter);\n\n\t// Vue composable HMR state tracking: wrap exported use* functions\n\t// so ref values are captured and restored across HMR reloads.\n\tif (!vueDir || !filePath.startsWith(vueDir) || !isTS) return transpiled;\n\n\tconst useExports = valueExports.filter((e) => e.startsWith('use'));\n\tif (useExports.length === 0) return transpiled;\n\n\treturn injectComposableTracking(transpiled, filePath, useExports);\n};\n\n// Classify a character for brace-counting: returns the new string\n// context and whether to skip further processing.\nconst classifyChar = (\n\tchar: string,\n\tprevChar: string,\n\tinString: string | false\n): { nextString: string | false; skip: boolean } => {\n\tif (inString) {\n\t\tconst closed = char === inString && prevChar !== '\\\\';\n\n\t\treturn { nextString: closed ? false : inString, skip: true };\n\t}\n\tif (char === '\"' || char === \"'\" || char === '`')\n\t\treturn { nextString: char, skip: true };\n\n\treturn { nextString: false, skip: false };\n};\n\n// Find the end of a function expression by counting braces/parens,\n// skipping string literals. Returns the index of the trailing ';'.\nconst findFunctionEnd = (source: string, startPos: number) => {\n\tlet depth = 0;\n\tlet inString: string | false = false;\n\tfor (let idx = startPos; idx < source.length; idx++) {\n\t\tconst char = source[idx] ?? '';\n\t\tconst classified = classifyChar(char, source[idx - 1] ?? '', inString);\n\t\tinString = classified.nextString;\n\t\tif (classified.skip) continue;\n\n\t\tif (char === '{' || char === '(') depth++;\n\t\tif (char === '}' || char === ')') depth--;\n\t\tif (depth === 0 && char === ';') return idx;\n\t}\n\n\treturn startPos;\n};\n\n/** Inject HMR state tracking into Vue composable exports.\n * Wraps each use* export to capture/restore ref values across reloads. */\nconst injectComposableTracking = (\n\tcode: string,\n\tfilePath: string,\n\tuseExports: string[]\n) => {\n\tconst moduleId = JSON.stringify(filePath);\n\n\t// Inject the tracking runtime at the top\n\tconst runtime = [\n\t\t`var __hmr_cs = (globalThis.__HMR_COMPOSABLE_STATE__ ??= {});`,\n\t\t`var __hmr_mid = ${moduleId};`,\n\t\t`var __hmr_prev_refs = __hmr_cs[__hmr_mid];`,\n\t\t`var __hmr_idx = {};`,\n\t\t`__hmr_cs[__hmr_mid] = {};`,\n\t\t`function __hmr_wrap(name, fn) {`,\n\t\t` return function() {`,\n\t\t` var idx = (__hmr_idx[name] = (__hmr_idx[name] ?? -1) + 1);`,\n\t\t` var result = fn.apply(this, arguments);`,\n\t\t` if (result && typeof result === \"object\") {`,\n\t\t` var refs = {};`,\n\t\t` for (var k in result) {`,\n\t\t` var v = result[k];`,\n\t\t` if (v && typeof v === \"object\" && \"value\" in v && !v.effect && typeof v.value !== \"function\") {`,\n\t\t` refs[k] = v;`,\n\t\t` }`,\n\t\t` }`,\n\t\t` (__hmr_cs[__hmr_mid][name] ??= [])[idx] = refs;`,\n\t\t` if (__hmr_prev_refs && __hmr_prev_refs[name] && __hmr_prev_refs[name][idx]) {`,\n\t\t` var old = __hmr_prev_refs[name][idx];`,\n\t\t` for (var k in old) {`,\n\t\t` var nv = result[k];`,\n\t\t` var ov = old[k];`,\n\t\t` if (nv && ov && typeof nv === \"object\" && \"value\" in nv && !nv.effect && typeof nv.value === typeof ov.value) {`,\n\t\t` nv.value = ov.value;`,\n\t\t` }`,\n\t\t` }`,\n\t\t` }`,\n\t\t` }`,\n\t\t` return result;`,\n\t\t` };`,\n\t\t`}`\n\t].join('\\n');\n\n\tlet result = `${runtime}\\n${code}`;\n\n\t// Wrap each use* export with __hmr_wrap.\n\t// Find the export assignment, then use brace/paren counting to locate\n\t// the end of the function expression (handles nested braces in the body).\n\tfor (const name of useExports) {\n\t\tresult = wrapComposableExport(result, name);\n\t}\n\n\treturn result;\n};\n\n// Find and wrap a single use* export with __hmr_wrap().\nconst wrapComposableExport = (source: string, name: string) => {\n\tconst marker = new RegExp(\n\t\t`export\\\\s+(?:const|var|let)\\\\s+${name}\\\\s*=\\\\s*`\n\t);\n\tconst match = marker.exec(source);\n\tif (!match) return source;\n\n\tconst insertPos = match.index + match[0].length;\n\tconst endPos = findFunctionEnd(source, insertPos);\n\tconst funcBody = source.slice(insertPos, endPos);\n\n\treturn `${source.slice(0, insertPos)}__hmr_wrap(${JSON.stringify(name)}, ${funcBody})${source.slice(endPos)}`;\n};\n\n// Virtual CSS modules for Svelte's css:'external' mode.\n// Keyed by fake path (e.g., /path/to/Counter.svelte.css).\nconst svelteExternalCss = new Map<string, string>();\n\n// ─── Framework-specific transforms (Svelte, Vue) ────────────\n// Cached compiler references — avoid re-importing on every request.\n// Pre-set via warmCompilers() at startup to eliminate first-edit spike.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet svelteCompiler: any = null;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet vueCompiler: any = null;\n\nexport const warmCompilers = async (frameworks: {\n\tsvelte?: boolean;\n\tvue?: boolean;\n}) => {\n\tconst [svelteModule, vueModule] = await Promise.all([\n\t\tframeworks.svelte ? import('svelte/compiler') : undefined,\n\t\tframeworks.vue ? import('@vue/compiler-sfc') : undefined\n\t]);\n\tif (svelteModule) {\n\t\tsvelteCompiler = svelteModule;\n\t\t// JIT-warm the compile function with a trivial component so\n\t\t// the first real HMR compile doesn't pay the JIT cost (~60ms).\n\t\tsvelteModule.compile('<script>let x=$state(0)</script>{x}', {\n\t\t\tcss: 'external',\n\t\t\tdev: true,\n\t\t\tfilename: '_warm.svelte',\n\t\t\tgenerate: 'client',\n\t\t\thmr: true\n\t\t});\n\t}\n\tif (!vueModule) return;\n\n\tvueCompiler = vueModule;\n\t// Same for Vue — warm compileScript + compileTemplate\n\tconst { descriptor } = vueModule.parse(\n\t\t'<script setup>const x=1</script><template>{{x}}</template>',\n\t\t{ filename: '_warm.vue' }\n\t);\n\tvueModule.compileScript(descriptor, { id: 'w', inlineTemplate: false });\n\tif (!descriptor.template) return;\n\n\tvueModule.compileTemplate({\n\t\tfilename: '_warm.vue',\n\t\tid: 'w',\n\t\tsource: descriptor.template.content\n\t});\n};\n\n// Compile a .svelte.ts module file — transpile TS, then compileModule.\nconst compileSvelteModule = (raw: string, filePath: string) => {\n\tconst source = tsTranspiler.transformSync(raw);\n\n\treturn svelteCompiler.compileModule(source, {\n\t\tdev: true,\n\t\tfilename: filePath\n\t}).js.code;\n};\n\n// Compile a .svelte component file — hmr: true, css: 'external'.\nconst compileSvelteComponent = (\n\traw: string,\n\tfilePath: string,\n\tprojectRoot: string\n) => {\n\tconst compiled = svelteCompiler.compile(raw, {\n\t\tcss: 'external',\n\t\tdev: true,\n\t\tfilename: filePath,\n\t\tgenerate: 'client',\n\t\thmr: true\n\t});\n\tlet { code } = compiled.js;\n\n\t// If the component has styles, inject them as a virtual CSS\n\t// import. The handleCssRequest handler serves it as a <style>.\n\tif (compiled.css?.code) {\n\t\tconst cssPath = `${filePath}.css`;\n\t\tsvelteExternalCss.set(cssPath, compiled.css.code);\n\t\tconst cssUrl = srcUrl(relative(projectRoot, cssPath), projectRoot);\n\t\tcode = `import \"${cssUrl}\";\\n${code}`;\n\t}\n\n\t// ── import.meta.hot → accept registry ──\n\tconst moduleUrl = `${SRC_PREFIX}${relative(projectRoot, filePath).replace(/\\\\/g, '/')}`;\n\tcode = code.replace(\n\t\t/if\\s*\\(import\\.meta\\.hot\\)\\s*\\{/,\n\t\t`if (typeof window !== \"undefined\") {\\n` +\n\t\t\t` if (!window.__SVELTE_HMR_ACCEPT__) window.__SVELTE_HMR_ACCEPT__ = {};\\n` +\n\t\t\t` var __hmr_accept = function(cb) { window.__SVELTE_HMR_ACCEPT__[${JSON.stringify(moduleUrl)}] = cb; };`\n\t);\n\n\treturn code.replace(/import\\.meta\\.hot\\.accept\\(/g, '__hmr_accept(');\n};\n\n// Compile .svelte files to client JS using svelte/compiler.\n// Keeps .svelte extensions in imports so the module server handles children.\nconst transformSvelteFile = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\n\tif (!svelteCompiler) {\n\t\tsvelteCompiler = await import('svelte/compiler');\n\t}\n\n\tconst isModule =\n\t\tfilePath.endsWith('.svelte.ts') || filePath.endsWith('.svelte.js');\n\n\tconst code = isModule\n\t\t? compileSvelteModule(raw, filePath)\n\t\t: compileSvelteComponent(raw, filePath, projectRoot);\n\n\treturn rewriteImports(code, filePath, projectRoot, rewriter);\n};\n\ntype VueSFCDescriptor = {\n\tstyles: Array<{ content: string; scoped: boolean }>;\n\ttemplate?: { content: string } | null;\n};\n\ntype VueSFCCompiledScript = {\n\tbindings: Record<string, string>;\n\tcontent: string;\n};\n\n// Compile a Vue SFC template and attach the render function to the script.\nconst compileVueTemplate = (\n\tdescriptor: VueSFCDescriptor,\n\tcompiledScript: VueSFCCompiledScript,\n\tfilePath: string,\n\tcomponentId: string\n) => {\n\tconst scriptContent = compiledScript.content;\n\tif (!descriptor.template) return scriptContent;\n\n\tconst isScoped = descriptor.styles.some((style) => style.scoped);\n\tconst templateResult = vueCompiler.compileTemplate({\n\t\tcompilerOptions: {\n\t\t\tbindingMetadata: compiledScript.bindings,\n\t\t\tprefixIdentifiers: true\n\t\t},\n\t\tfilename: filePath,\n\t\tid: componentId,\n\t\tscoped: isScoped,\n\t\tsource: descriptor.template.content\n\t});\n\n\tlet code = scriptContent.replace('export default', 'const __script__ =');\n\tcode += `\\n${templateResult.code}`;\n\tcode += '\\n__script__.render = render;';\n\tcode += '\\nexport default __script__;';\n\n\treturn code;\n};\n\n// Compile and inject scoped CSS as inline <style> for a Vue SFC.\nconst compileVueStyles = (\n\tdescriptor: VueSFCDescriptor,\n\tfilePath: string,\n\tcomponentId: string,\n\tcode: string\n) => {\n\tif (descriptor.styles.length === 0) return code;\n\n\tconst cssCode = descriptor.styles\n\t\t.map(\n\t\t\t(style) =>\n\t\t\t\tvueCompiler.compileStyle({\n\t\t\t\t\tfilename: filePath,\n\t\t\t\t\tid: `data-v-${componentId}`,\n\t\t\t\t\tscoped: style.scoped,\n\t\t\t\t\tsource: style.content,\n\t\t\t\t\ttrim: true\n\t\t\t\t}).code\n\t\t)\n\t\t.join('\\n');\n\n\tconst escaped = cssCode\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/`/g, '\\\\`')\n\t\t.replace(/\\$/g, '\\\\$');\n\tconst hmrId = JSON.stringify(filePath);\n\tconst cssInjection = [\n\t\t`var __style=document.createElement('style');`,\n\t\t`__style.textContent=\\`${escaped}\\`;`,\n\t\t`__style.dataset.hmrId=${hmrId};`,\n\t\t`var __prev=document.querySelector('style[data-hmr-id=\"${filePath}\"]');`,\n\t\t`if(__prev)__prev.remove();`,\n\t\t`document.head.appendChild(__style);`\n\t].join('');\n\n\treturn `${cssInjection}\\n${code}`;\n};\n\n// Compile .vue SFC files to client JS using @vue/compiler-sfc.\nconst transformVueFile = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\n\tif (!vueCompiler) {\n\t\tvueCompiler = await import('@vue/compiler-sfc');\n\t}\n\n\tconst fileName = basename(filePath, '.vue');\n\tconst componentId = fileName.toLowerCase();\n\tconst { descriptor } = vueCompiler.parse(raw, { filename: filePath });\n\n\tconst compiledScript = vueCompiler.compileScript(descriptor, {\n\t\tid: componentId,\n\t\tinlineTemplate: false\n\t});\n\n\tlet code = compileVueTemplate(\n\t\tdescriptor,\n\t\tcompiledScript,\n\t\tfilePath,\n\t\tcomponentId\n\t);\n\tcode = compileVueStyles(descriptor, filePath, componentId, code);\n\n\t// Vue's compileScript strips user TypeScript but the generated\n\t// wrapper code still has `: any` annotations (e.g. __props: any,\n\t// _ctx: any). Run through the TS transpiler to strip those.\n\tcode = tsTranspiler.transformSync(code);\n\n\t// Inject Vue HMR — use rerender() to preserve reactive state.\n\t// rerender() only swaps the render function (like React Fast Refresh).\n\t// reload() would reset state by re-running setup().\n\tcode = injectVueHmr(code, filePath, projectRoot, vueDir);\n\n\treturn rewriteImports(code, filePath, projectRoot, rewriter);\n};\n\n// Inject Vue HMR runtime registration and rerender call.\nconst injectVueHmr = (\n\tcode: string,\n\tfilePath: string,\n\tprojectRoot: string,\n\tvueDir?: string\n) => {\n\tconst hmrBase = vueDir ? resolve(vueDir) : projectRoot;\n\tconst hmrId = relative(hmrBase, filePath)\n\t\t.replace(/\\\\/g, '/')\n\t\t.replace(/\\.vue$/, '');\n\tlet result = code.replace(/export\\s+default\\s+/, 'var __hmr_comp__ = ');\n\tresult += [\n\t\t'',\n\t\t`__hmr_comp__.__hmrId = ${JSON.stringify(hmrId)};`,\n\t\t`if (typeof __VUE_HMR_RUNTIME__ !== \"undefined\") {`,\n\t\t` __VUE_HMR_RUNTIME__.createRecord(${JSON.stringify(hmrId)}, __hmr_comp__);`,\n\t\t` __VUE_HMR_RUNTIME__.rerender(${JSON.stringify(hmrId)}, __hmr_comp__.render);`,\n\t\t`}`,\n\t\t'export default __hmr_comp__;'\n\t].join('\\n');\n\n\treturn result;\n};\n\n// Resolve .svelte module files that may exist as .svelte.ts or .svelte.js\nconst resolveSvelteModulePath = (path: string) => {\n\tif (existsSync(path)) return path;\n\tif (existsSync(`${path}.ts`)) return `${path}.ts`;\n\tif (existsSync(`${path}.js`)) return `${path}.js`;\n\n\treturn path;\n};\n\n// Shared response builder for transformed modules\nconst jsResponse = (body: string) => {\n\tconst etag = `\"${Bun.hash(body).toString(BASE_36_RADIX)}\"`;\n\n\treturn new Response(body, {\n\t\theaders: {\n\t\t\t'Cache-Control': 'no-cache',\n\t\t\t'Content-Type': 'application/javascript',\n\t\t\tETag: etag\n\t\t}\n\t});\n};\n\nconst handleCssRequest = (filePath: string) => {\n\tconst raw = readFileSync(filePath, 'utf-8');\n\tconst escaped = raw\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/`/g, '\\\\`')\n\t\t.replace(/\\$/g, '\\\\$');\n\n\treturn [\n\t\t`const style = document.createElement('style');`,\n\t\t`style.textContent = \\`${escaped}\\`;`,\n\t\t`style.dataset.hmrId = ${JSON.stringify(filePath)};`,\n\t\t`const existing = document.querySelector(\\`style[data-hmr-id=\"${filePath}\"]\\`);`,\n\t\t`if (existing) existing.remove();`,\n\t\t`document.head.appendChild(style);`\n\t].join('\\n');\n};\n\n// Generate HMR bootstrap wrapper for Svelte.\n// Uses dynamic import() with a cache-busting timestamp so the\n// browser fetches the freshly compiled component every time.\nconst generateSvelteHmrBootstrap = (\n\tmoduleUrl: string,\n\tvendorPaths: Record<string, string>,\n\ttimestamp: string\n) => {\n\tconst sveltePath = vendorPaths['svelte'] || '/svelte/vendor/svelte.js';\n\n\treturn [\n\t\t`import { mount, unmount } from \"${sveltePath}\";`,\n\t\t`const { default: Component } = await import(\"${moduleUrl}?t=${timestamp}\");`,\n\t\t``,\n\t\t`// Extract count from DOM before unmount (survives across runtime instances)`,\n\t\t`var countBtn = document.querySelector(\"button\");`,\n\t\t`var countMatch = countBtn && countBtn.textContent && countBtn.textContent.match(/(\\\\d+)/);`,\n\t\t`var domCount = countMatch ? parseInt(countMatch[1], 10) : null;`,\n\t\t``,\n\t\t`var preservedState = window.__HMR_PRESERVED_STATE__ || {};`,\n\t\t`if (domCount !== null && preservedState.initialCount === undefined) {`,\n\t\t` preservedState.initialCount = domCount;`,\n\t\t`}`,\n\t\t`var initialProps = window.__INITIAL_PROPS__ || {};`,\n\t\t`var mergedProps = Object.assign({}, initialProps, preservedState);`,\n\t\t``,\n\t\t`// Update __INITIAL_PROPS__ so subsequent HMR cycles start with current state`,\n\t\t`if (domCount !== null) window.__INITIAL_PROPS__ = Object.assign({}, initialProps, { initialCount: domCount });`,\n\t\t``,\n\t\t`if (typeof window.__SVELTE_UNMOUNT__ === \"function\") {`,\n\t\t` try { window.__SVELTE_UNMOUNT__(); } catch (err) { /* ignore */ }`,\n\t\t`}`,\n\t\t``,\n\t\t`var component = mount(Component, { target: document.body, props: mergedProps });`,\n\t\t`window.__SVELTE_COMPONENT__ = component;`,\n\t\t`window.__SVELTE_UNMOUNT__ = function() { unmount(component); };`,\n\t\t`window.__HMR_PRESERVED_STATE__ = undefined;`\n\t].join('\\n');\n};\n\n// Generate HMR bootstrap wrapper for Vue.\n// Same approach as Svelte — full remount via vendor Vue's createApp.\nconst generateVueHmrBootstrap = (\n\tmoduleUrl: string,\n\tvendorPaths: Record<string, string>,\n\ttimestamp: string\n) => {\n\tconst vuePath = vendorPaths['vue'] || '/vue/vendor/vue.js';\n\n\treturn [\n\t\t`import { createApp } from \"${vuePath}\";`,\n\t\t`const { default: Component } = await import(\"${moduleUrl}?t=${timestamp}\");`,\n\t\t``,\n\t\t`// Extract count from DOM before unmount (works across Vue instances)`,\n\t\t`var countBtn = document.querySelector(\"button\");`,\n\t\t`var countMatch = countBtn && countBtn.textContent && countBtn.textContent.match(/(\\\\d+)/);`,\n\t\t`var domCount = countMatch ? parseInt(countMatch[1], 10) : null;`,\n\t\t``,\n\t\t`var preservedState = window.__HMR_PRESERVED_STATE__ || {};`,\n\t\t`if (domCount !== null && preservedState.initialCount === undefined) {`,\n\t\t` preservedState.initialCount = domCount;`,\n\t\t`}`,\n\t\t`var initialProps = window.__INITIAL_PROPS__ || {};`,\n\t\t`var mergedProps = Object.assign({}, initialProps, preservedState);`,\n\t\t``,\n\t\t`// Update __INITIAL_PROPS__ so subsequent HMR cycles start with current state`,\n\t\t`if (domCount !== null) window.__INITIAL_PROPS__ = Object.assign({}, initialProps, { initialCount: domCount });`,\n\t\t``,\n\t\t`var root = document.getElementById(\"root\");`,\n\t\t`var savedHTML = root ? root.innerHTML : \"\";`,\n\t\t`if (window.__VUE_APP__) {`,\n\t\t` window.__VUE_APP__.unmount();`,\n\t\t` window.__VUE_APP__ = null;`,\n\t\t`}`,\n\t\t`if (root) root.innerHTML = savedHTML;`,\n\t\t``,\n\t\t`var app = createApp(Component, mergedProps);`,\n\t\t`app.mount(root);`,\n\t\t`window.__VUE_APP__ = app;`,\n\t\t`window.__HMR_PRESERVED_STATE__ = undefined;`\n\t].join('\\n');\n};\n\n// Generate a stub module for a server-only package so browser imports resolve.\nconst handleStubRequest = async (pathname: string) => {\n\tconst specifier = decodeURIComponent(pathname.slice('/@stub/'.length));\n\tconst stubCode = await buildStubCode(specifier);\n\n\treturn new Response(stubCode, {\n\t\theaders: {\n\t\t\t'Cache-Control': 'public, max-age=31536000, immutable',\n\t\t\t'Content-Type': 'application/javascript'\n\t\t}\n\t});\n};\n\n// Introspect a module's exports and generate noop stubs for each.\nconst buildStubCode = async (specifier: string) => {\n\ttry {\n\t\tconst mod = await import(specifier);\n\t\tconst names = Object.keys(mod).filter(\n\t\t\t(key) => key !== 'default' && key !== '__esModule'\n\t\t);\n\t\tif (names.length === 0) return 'export default {};\\n';\n\n\t\tconst noops = names\n\t\t\t.map((n) => `export const ${n} = () => {};`)\n\t\t\t.join('\\n');\n\n\t\treturn `${noops}\\nexport default {};\\n`;\n\t} catch {\n\t\treturn 'export default {};\\n';\n\t}\n};\n\n// Handle HMR bootstrap wrappers for non-React frameworks.\nconst handleHmrBootstrap = (\n\tpathname: string,\n\tvendorPaths: Record<string, string>\n) => {\n\tconst rest = pathname.slice('/@hmr/'.length);\n\tconst slashIdx = rest.indexOf('/');\n\tif (slashIdx === UNFOUND_INDEX) return undefined;\n\n\tconst framework = rest.slice(0, slashIdx);\n\tconst componentRelPath = rest.slice(slashIdx + 1);\n\tconst url = `${SRC_PREFIX}${componentRelPath}`;\n\tconst timestamp = String(Date.now());\n\n\tconst generators: Record<string, typeof generateSvelteHmrBootstrap> = {\n\t\tsvelte: generateSvelteHmrBootstrap,\n\t\tvue: generateVueHmrBootstrap\n\t};\n\tconst generate = generators[framework];\n\tif (!generate) return undefined;\n\n\treturn jsResponse(generate(url, vendorPaths, timestamp));\n};\n\n// Serve a virtual Svelte CSS module (css:'external' output).\nconst handleVirtualSvelteCss = (cssCheckPath: string) => {\n\tconst virtualCss = svelteExternalCss.get(cssCheckPath);\n\tif (!virtualCss) return undefined;\n\n\tconst escaped = virtualCss\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/`/g, '\\\\`')\n\t\t.replace(/\\$/g, '\\\\$');\n\n\treturn jsResponse(\n\t\t`var s=document.createElement('style');` +\n\t\t\t`s.textContent=\\`${escaped}\\`;` +\n\t\t\t`s.dataset.svelteHmr=${JSON.stringify(cssCheckPath)};` +\n\t\t\t`var p=document.querySelector('style[data-svelte-hmr=\"${cssCheckPath}\"]');` +\n\t\t\t`if(p)p.remove();` +\n\t\t\t`document.head.appendChild(s);`\n\t);\n};\n\n// Resolve a /@src/ path to an absolute file path and extension,\n// probing known extensions if the path has none.\nconst resolveSourcePath = (relPath: string, projectRoot: string) => {\n\tconst filePath = resolve(projectRoot, relPath);\n\tconst ext = extname(filePath);\n\n\tif (ext === '.svelte')\n\t\treturn { ext, filePath: resolveSvelteModulePath(filePath) };\n\tif (ext) return { ext, filePath };\n\n\t// No extension — probe known extensions\n\tconst found = MODULE_EXTENSIONS.find((candidate) =>\n\t\texistsSync(filePath + candidate)\n\t);\n\tif (!found) return { ext, filePath };\n\n\tconst resolved = filePath + found;\n\tif (found === '.svelte')\n\t\treturn { ext: found, filePath: resolveSvelteModulePath(resolved) };\n\n\treturn { ext: found, filePath: resolved };\n};\n\n// Transform and cache a source file, returning a JS Response.\nconst transformAndCache = async (\n\tfilePath: string,\n\text: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tif (ext === '.css') return jsResponse(handleCssRequest(filePath));\n\n\tconst isSvelte =\n\t\text === '.svelte' ||\n\t\tfilePath.endsWith('.svelte.ts') ||\n\t\tfilePath.endsWith('.svelte.js');\n\n\tconst cached = getTransformed(filePath);\n\tif (cached) return jsResponse(cached);\n\n\tif (isSvelte)\n\t\treturn transformAndCacheSvelte(filePath, projectRoot, rewriter);\n\tif (ext === '.vue')\n\t\treturn transformAndCacheVue(filePath, projectRoot, rewriter, vueDir);\n\tif (!TRANSPILABLE.has(ext)) return undefined;\n\n\tconst stat = statSync(filePath);\n\tconst resolvedVueDir = vueDir ? resolve(vueDir) : undefined;\n\tconst content = REACT_EXTENSIONS.has(ext)\n\t\t? transformReactFile(filePath, projectRoot, rewriter)\n\t\t: transformPlainFile(filePath, projectRoot, rewriter, resolvedVueDir);\n\n\tsetTransformed(\n\t\tfilePath,\n\t\tcontent,\n\t\tstat.mtimeMs,\n\t\textractImportedFiles(content, projectRoot)\n\t);\n\n\treturn jsResponse(content);\n};\n\nconst transformAndCacheSvelte = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>\n) => {\n\tconst stat = statSync(filePath);\n\tconst content = await transformSvelteFile(filePath, projectRoot, rewriter);\n\tsetTransformed(\n\t\tfilePath,\n\t\tcontent,\n\t\tstat.mtimeMs,\n\t\textractImportedFiles(content, projectRoot)\n\t);\n\n\treturn jsResponse(content);\n};\n\nconst transformAndCacheVue = async (\n\tfilePath: string,\n\tprojectRoot: string,\n\trewriter: ReturnType<typeof buildImportRewriter>,\n\tvueDir?: string\n) => {\n\tconst stat = statSync(filePath);\n\tconst content = await transformVueFile(\n\t\tfilePath,\n\t\tprojectRoot,\n\t\trewriter,\n\t\tvueDir\n\t);\n\tsetTransformed(\n\t\tfilePath,\n\t\tcontent,\n\t\tstat.mtimeMs,\n\t\textractImportedFiles(content, projectRoot)\n\t);\n\n\treturn jsResponse(content);\n};\n\n// Build a transform-error response for the browser console.\nconst transformErrorResponse = (err: unknown) => {\n\tconst errMsg = err instanceof Error ? err.message : String(err);\n\n\treturn new Response(\n\t\t`console.error('[ModuleServer] Transform error:', ${JSON.stringify(errMsg)});`,\n\t\t{\n\t\t\theaders: { 'Content-Type': 'application/javascript' },\n\t\t\tstatus: 500\n\t\t}\n\t);\n};\n\nexport const createModuleServer = (config: ModuleServerConfig) => {\n\tconst { projectRoot, vendorPaths, frameworkDirs } = config;\n\tconst rewriter = buildImportRewriter(vendorPaths);\n\n\treturn async (pathname: string) => {\n\t\tif (pathname.startsWith('/@stub/')) return handleStubRequest(pathname);\n\t\tif (pathname.startsWith('/@hmr/'))\n\t\t\treturn handleHmrBootstrap(pathname, vendorPaths);\n\t\tif (!pathname.startsWith(SRC_PREFIX)) return undefined;\n\n\t\tconst relPath = pathname.slice(SRC_PREFIX.length);\n\n\t\tconst virtualCssResponse = handleVirtualSvelteCss(\n\t\t\tresolve(projectRoot, relPath)\n\t\t);\n\t\tif (virtualCssResponse) return virtualCssResponse;\n\n\t\tconst { filePath, ext } = resolveSourcePath(relPath, projectRoot);\n\n\t\ttry {\n\t\t\treturn await transformAndCache(\n\t\t\t\tfilePath,\n\t\t\t\text,\n\t\t\t\tprojectRoot,\n\t\t\t\trewriter,\n\t\t\t\tframeworkDirs?.vue\n\t\t\t);\n\t\t} catch (err) {\n\t\t\treturn transformErrorResponse(err);\n\t\t}\n\t};\n};\n\n// Extract absolute file paths from /@src/ imports in transformed code.\n// Used to build the runtime module graph for chain invalidation.\nconst SRC_IMPORT_RE = /\\/@src\\/([^\"'?\\s]+)/g;\nconst extractImportedFiles = (content: string, projectRoot: string) => {\n\tconst files: string[] = [];\n\tlet match;\n\tSRC_IMPORT_RE.lastIndex = 0;\n\twhile ((match = SRC_IMPORT_RE.exec(content)) !== null) {\n\t\tif (match[1]) files.push(resolve(projectRoot, match[1]));\n\t}\n\n\treturn files;\n};\n\nexport const invalidateModule = (filePath: string) => {\n\t// invalidate() cascades up the import chain — clearing transform\n\t// caches for all transitive importers so they get re-transpiled\n\t// with fresh ?v= params. Also clear mtime caches for the changed\n\t// file so srcUrl() re-reads its mtime from disk.\n\tconst resolved = resolve(filePath);\n\tinvalidate(filePath);\n\tif (resolved !== filePath) invalidate(resolved);\n\tmtimeCache.delete(filePath);\n\tmtimeCache.delete(resolved);\n\t// Note: we only clear mtime for the changed file. Importers'\n\t// mtimes haven't changed — their transform caches are cleared\n\t// by invalidate() so they get re-transpiled with new ?v= for\n\t// the changed file's updated mtime.\n};\n\n// Pre-transpile a /@src/ URL and cache the result so the browser\n// fetch is instant. Called before sending the WebSocket HMR message.\nexport const warmCache = (pathname: string) => {\n\tif (!pathname.startsWith(SRC_PREFIX)) return;\n\tif (!globalModuleServer) return;\n\t// Trigger the handler — the result is cached by setTransformed\n\tglobalModuleServer(pathname);\n};\n\n// Store the module server handler globally so warmCache can access it\nlet globalModuleServer:\n\t| ((\n\t\t\tpathname: string\n\t ) => Promise<Response | undefined> | Response | undefined)\n\t| null = null;\n\nexport const SRC_URL_PREFIX = SRC_PREFIX;\n\nexport const setGlobalModuleServer = (handler: typeof globalModuleServer) => {\n\tglobalModuleServer = handler;\n};\n",
|
|
77
77
|
"/* Simple HTML HMR Implementation\n Lightweight approach: read HTML file → send HTML patch */\n\nimport { resolve } from 'node:path';\n\n/* Simple HTML HMR handler for server-side\n When an HTML file changes:\n 1. Read the HTML file\n 2. Extract body content (or return full HTML)\n 3. Return the HTML for patching */\nexport const handleHTMLUpdate = async (htmlFilePath: string) => {\n\tlet htmlContent: string;\n\ttry {\n\t\tconst resolvedPath = resolve(htmlFilePath);\n\t\tconst file = Bun.file(resolvedPath);\n\t\tif (!(await file.exists())) {\n\t\t\treturn null;\n\t\t}\n\t\thtmlContent = await file.text();\n\t} catch {\n\t\treturn null;\n\t}\n\n\tconst headMatch = htmlContent.match(/<head[^>]*>([\\s\\S]*?)<\\/head>/i);\n\tconst bodyMatch = htmlContent.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n\n\tif (bodyMatch && bodyMatch[1]) {\n\t\treturn {\n\t\t\tbody: bodyMatch[1].trim(),\n\t\t\thead: headMatch && headMatch[1] ? headMatch[1].trim() : null\n\t\t};\n\t}\n\n\treturn htmlContent;\n};\n",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Component as VueComponent } from 'vue';
|
|
2
2
|
import type { VuePropsOf } from '../../types/vue';
|
|
3
|
-
export declare const handleVuePageRequest: <Component extends VueComponent>(_PageComponent: Component, pagePath: string, indexPath: string, headTag?: `<head>${string}</head>`, ...props: keyof VuePropsOf<Component> extends never ? [] : [props: NoInfer<VuePropsOf<Component>>]) => Promise<Response>;
|
|
3
|
+
export declare const handleVuePageRequest: <Component extends VueComponent>(_PageComponent: Component, pagePath: string, indexPath: string, headTag?: `<head>${string}</head>`, ...props: keyof VuePropsOf<Component> extends never ? [props?: Record<string, never>] : [props: NoInfer<VuePropsOf<Component>>]) => Promise<Response>;
|
|
4
4
|
export declare const invalidateVueSsrCache: () => void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
/* Server-side globals (Bun --hot reload state) */
|
|
3
|
+
var __hmrServerStartup: boolean | undefined;
|
|
4
|
+
var __hmrBuildDuration: number | undefined;
|
|
5
|
+
var __absoluteVersion: string | undefined;
|
|
6
|
+
var __hmrServerMtime: number | undefined;
|
|
7
|
+
var __hmrSkipServerRestart: boolean | undefined;
|
|
8
|
+
/** Pinned React module from initial devBuild — used to detect and bridge
|
|
9
|
+
* duplicate React instances after bun install invalidates the module cache. */
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
var __reactModuleRef: any;
|
|
12
|
+
var __depVendorPaths: Record<string, string> | undefined;
|
|
13
|
+
var __transformCache:
|
|
14
|
+
| Map<string, { content: string; imports: string[]; mtime: number }>
|
|
15
|
+
| undefined;
|
|
16
|
+
var __transformImporters: Map<string, Set<string>> | undefined;
|
|
17
|
+
var __transformInvalidationVersions: Map<string, number> | undefined;
|
|
18
|
+
var __http2Config:
|
|
19
|
+
| {
|
|
20
|
+
hmrState: import('../src/dev/clientManager').HMRState;
|
|
21
|
+
manifest: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
| undefined;
|
|
24
|
+
var __hmrDevResult:
|
|
25
|
+
| {
|
|
26
|
+
hmrState: import('../src/dev/clientManager').HMRState;
|
|
27
|
+
manifest: Record<string, string>;
|
|
28
|
+
conventions?: import('./conventions').ConventionsMap;
|
|
29
|
+
}
|
|
30
|
+
| undefined;
|
|
31
|
+
|
|
32
|
+
/* Client-side globals (Window extensions for HMR) */
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- declaration merging requires interface
|
|
34
|
+
interface Window {
|
|
35
|
+
$RefreshReg$?: (type: unknown, id: string) => void;
|
|
36
|
+
$RefreshRuntime$?: {
|
|
37
|
+
createSignatureFunctionForTransform: () => (
|
|
38
|
+
type: unknown
|
|
39
|
+
) => unknown;
|
|
40
|
+
injectIntoGlobalHook: (win: Window) => void;
|
|
41
|
+
performReactRefresh: () => void;
|
|
42
|
+
register: (type: unknown, id: string) => void;
|
|
43
|
+
};
|
|
44
|
+
$RefreshSig$?: () => (type: unknown) => unknown;
|
|
45
|
+
__HMR_FRAMEWORK__?: string;
|
|
46
|
+
__HMR_MANIFEST__?: Record<string, string>;
|
|
47
|
+
__HMR_MODULE_UPDATES__?: Array<unknown>;
|
|
48
|
+
__HMR_MODULE_VERSIONS__?: Record<string, number>;
|
|
49
|
+
__HMR_PRESERVED_STATE__?: Record<string, unknown>;
|
|
50
|
+
__HMR_SERVER_VERSIONS__?: Record<string, number>;
|
|
51
|
+
__HMR_UPDATE_COUNT__?: number;
|
|
52
|
+
__HMR_WS__?: WebSocket;
|
|
53
|
+
__ERROR_BOUNDARY__?: { reset: () => void };
|
|
54
|
+
__INITIAL_PROPS__?: Record<string, unknown>;
|
|
55
|
+
__REACT_COMPONENT_KEY__?: string;
|
|
56
|
+
__REACT_ROOT__?: { render: (element: unknown) => void };
|
|
57
|
+
__SVELTE_COMPONENT__?: Record<string, unknown>;
|
|
58
|
+
__SVELTE_UNMOUNT__?: () => void;
|
|
59
|
+
__ANGULAR_APP__?: { destroy: () => void; tick: () => void } | null;
|
|
60
|
+
__HMR_SKIP_HYDRATION__?: boolean;
|
|
61
|
+
__HMR_NEW_PAGE_CLASS__?: unknown;
|
|
62
|
+
__NG_REPLACE_METADATA__?: (...args: unknown[]) => void;
|
|
63
|
+
__ANGULAR_HMR__?: {
|
|
64
|
+
register: (id: string, ctor: unknown) => void;
|
|
65
|
+
applyUpdate: (id: string, newCtor: unknown) => boolean;
|
|
66
|
+
refresh: () => void;
|
|
67
|
+
getStats: () => { componentCount: number; updateCount: number };
|
|
68
|
+
getRegistry: () => Map<
|
|
69
|
+
string,
|
|
70
|
+
{
|
|
71
|
+
liveCtor: unknown;
|
|
72
|
+
id: string;
|
|
73
|
+
registeredAt: number;
|
|
74
|
+
updateCount: number;
|
|
75
|
+
}
|
|
76
|
+
>;
|
|
77
|
+
};
|
|
78
|
+
__VUE_APP__?:
|
|
79
|
+
| ({
|
|
80
|
+
unmount: () => void;
|
|
81
|
+
_instance?: import('./vue').VueComponentInstance;
|
|
82
|
+
} & Record<string, unknown>)
|
|
83
|
+
| null;
|
|
84
|
+
__VUE_HMR_COMPONENTS__?: Record<string, unknown>;
|
|
85
|
+
__VUE_HMR_RUNTIME__?: {
|
|
86
|
+
createRecord: (id: string, component: unknown) => void;
|
|
87
|
+
reload: (id: string, component: unknown) => void;
|
|
88
|
+
rerender: (id: string, render: unknown) => void;
|
|
89
|
+
};
|
|
90
|
+
__REFRESH_BUFFER__?: Array<[unknown, string]>;
|
|
91
|
+
htmx?: { process: (element: HTMLElement | Document) => void };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {};
|
package/dist/vue/index.js.map
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"export const ssrErrorPage = (framework: string, error: unknown) => {\n\tconst frameworkColors: Record<string, string> = {\n\t\tangular: '#dd0031',\n\t\thtml: '#e34c26',\n\t\thtmx: '#1a365d',\n\t\treact: '#61dafb',\n\t\tsvelte: '#ff3e00',\n\t\tvue: '#42b883'\n\t};\n\n\tconst accent = frameworkColors[framework] ?? '#94a3b8';\n\tconst label = framework.charAt(0).toUpperCase() + framework.slice(1);\n\tconst message = error instanceof Error ? error.message : String(error);\n\n\treturn `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>SSR Error - AbsoluteJS</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{min-height:100vh;background:linear-gradient(135deg,rgba(15,23,42,0.98) 0%,rgba(30,41,59,0.98) 100%);color:#e2e8f0;font-family:\"JetBrains Mono\",\"Fira Code\",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:14px;line-height:1.6;display:flex;align-items:flex-start;justify-content:center;padding:32px}\n.card{max-width:720px;width:100%;background:rgba(30,41,59,0.6);border:1px solid rgba(71,85,105,0.5);border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5),0 0 0 1px rgba(255,255,255,0.05);overflow:hidden}\n.header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 24px;background:rgba(15,23,42,0.5);border-bottom:1px solid rgba(71,85,105,0.4)}\n.brand{font-weight:700;font-size:20px;color:#fff;letter-spacing:-0.02em}\n.badge{padding:5px 10px;border-radius:8px;font-size:12px;font-weight:600;background:${accent};color:#fff;opacity:0.95;box-shadow:0 2px 4px rgba(0,0,0,0.2)}\n.kind{color:#94a3b8;font-size:13px;font-weight:500}\n.content{padding:24px}\n.label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin-bottom:8px}\n.message{margin:0;padding:16px 20px;background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.25);border-radius:10px;overflow-x:auto;white-space:pre-wrap;word-break:break-word;color:#fca5a5;font-size:13px;line-height:1.5}\n.hint{margin-top:20px;padding:12px 20px;background:rgba(71,85,105,0.3);border-radius:10px;border:1px solid rgba(71,85,105,0.4);color:#cbd5e1;font-size:13px}\n</style>\n</head>\n<body>\n<div class=\"card\">\n<div class=\"header\">\n<div style=\"display:flex;align-items:center;gap:12px\">\n<span class=\"brand\">AbsoluteJS</span>\n<span class=\"badge\">${label}</span>\n</div>\n<span class=\"kind\">Server Render Error</span>\n</div>\n<div class=\"content\">\n<div class=\"label\">What went wrong</div>\n<pre class=\"message\">${message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</pre>\n<div class=\"hint\">A component threw during server-side rendering. Check the terminal for the full stack trace.</div>\n</div>\n</div>\n</body>\n</html>`;\n};\n",
|
|
13
13
|
"const normalizeSlug = (str: string) =>\n\tstr\n\t\t.trim()\n\t\t.replace(/\\s+/g, '-')\n\t\t.replace(/[^A-Za-z0-9\\-_]+/g, '')\n\t\t.replace(/[-_]{2,}/g, '-');\n\nexport const toKebab = (str: string) =>\n\tnormalizeSlug(str)\n\t\t.replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n\t\t.toLowerCase();\nexport const toPascal = (str: string) => {\n\tif (!str.includes('-') && !str.includes('_')) {\n\t\treturn str.charAt(0).toUpperCase() + str.slice(1);\n\t}\n\n\treturn normalizeSlug(str)\n\t\t.split(/[-_]/)\n\t\t.filter(Boolean)\n\t\t.map(\n\t\t\t(segment) =>\n\t\t\t\tsegment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()\n\t\t)\n\t\t.join('');\n};\nexport const toScreamingSnake = (str: string) =>\n\tstr.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();\n",
|
|
14
14
|
"import { basename } from 'node:path';\nimport type { ConventionsMap } from '../../types/conventions';\nimport { toPascal } from './stringModifiers';\n\n// Use globalThis so the conventions map is shared across all bundles.\n// The main bundle (dist/index.js) calls setConventions, but framework\n// bundles (dist/svelte/index.js, etc.) need to read the same map.\nconst CONVENTIONS_KEY = '__absoluteConventions';\n\nconst isConventionsMap = (value: unknown): value is ConventionsMap =>\n\tBoolean(value) && typeof value === 'object';\n\nconst getMap = () => {\n\tconst value: unknown = Reflect.get(globalThis, CONVENTIONS_KEY);\n\tif (isConventionsMap(value)) return value;\n\n\tconst empty: ConventionsMap = {};\n\n\treturn empty;\n};\n\nexport const derivePageName = (pagePath: string) => {\n\tconst base = basename(pagePath);\n\t// Strip hash and extension: \"SvelteExample.abc123.js\" → \"SvelteExample\"\n\tconst dotIndex = base.indexOf('.');\n\tconst name = dotIndex > 0 ? base.slice(0, dotIndex) : base;\n\n\treturn toPascal(name);\n};\nexport const getConventions = () => getMap();\nexport const resolveErrorConventionPath = (\n\tframework: keyof ConventionsMap,\n\tpageName: string\n) => {\n\tconst conventions = getMap()[framework];\n\tif (!conventions) return undefined;\n\n\treturn conventions.pages?.[pageName]?.error ?? conventions.defaults?.error;\n};\nexport const resolveNotFoundConventionPath = (\n\tframework: keyof ConventionsMap\n) => getMap()[framework]?.defaults?.notFound;\nexport const setConventions = (map: ConventionsMap) => {\n\tReflect.set(globalThis, CONVENTIONS_KEY, map);\n};\n\nconst isDev = () => process.env.NODE_ENV === 'development';\n\nconst buildErrorProps = (error: unknown) => {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tconst stack = isDev() && error instanceof Error ? error.stack : undefined;\n\n\treturn { error: { message, stack } };\n};\n\nconst renderReactError = async (\n\tconventionPath: string,\n\terrorProps: ReturnType<typeof buildErrorProps>\n) => {\n\tconst { createElement } = await import('react');\n\tconst { renderToReadableStream } = await import('react-dom/server');\n\tconst mod = await import(conventionPath);\n\tconst [firstKey] = Object.keys(mod);\n\tconst ErrorComponent =\n\t\tmod.default ?? (firstKey ? mod[firstKey] : undefined);\n\tconst element = createElement(ErrorComponent, errorProps);\n\tconst stream = await renderToReadableStream(element);\n\n\treturn new Response(stream, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 500\n\t});\n};\n\nconst renderSvelteError = async (\n\tconventionPath: string,\n\terrorProps: ReturnType<typeof buildErrorProps>\n) => {\n\tconst { render } = await import('svelte/server');\n\tconst mod = await import(conventionPath);\n\tconst ErrorComponent = mod.default;\n\tconst { head, body } = render(ErrorComponent, {\n\t\tprops: errorProps\n\t});\n\tconst html = `<!DOCTYPE html><html><head>${head}</head><body>${body}</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 500\n\t});\n};\n\nconst unescapeVueStyles = (ssrBody: string) => {\n\tlet styles = '';\n\tconst body = ssrBody.replace(\n\t\t/<style>([\\s\\S]*?)<\\/style>/g,\n\t\t(_, css: string) => {\n\t\t\tstyles += `<style>${css\n\t\t\t\t.replace(/"/g, '\"')\n\t\t\t\t.replace(/&/g, '&')\n\t\t\t\t.replace(/</g, '<')\n\t\t\t\t.replace(/>/g, '>')}</style>`;\n\n\t\t\treturn '';\n\t\t}\n\t);\n\n\treturn { body, styles };\n};\n\nconst renderVueError = async (\n\tconventionPath: string,\n\terrorProps: ReturnType<typeof buildErrorProps>\n) => {\n\tconst { createSSRApp, h } = await import('vue');\n\tconst { renderToString } = await import('vue/server-renderer');\n\tconst mod = await import(conventionPath);\n\tconst ErrorComponent = mod.default;\n\tconst app = createSSRApp({\n\t\trender: () => h(ErrorComponent, errorProps)\n\t});\n\tconst rawBody = await renderToString(app);\n\n\t// Vue SSR escapes quotes inside <component is=\"style\"> tags.\n\t// Extract style content, unescape it, and move to <head>.\n\tconst { styles, body } = unescapeVueStyles(rawBody);\n\tconst html = `<!DOCTYPE html><html><head>${styles}</head><body><div id=\"root\">${body}</div></body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 500\n\t});\n};\n\nconst renderAngularError = async (\n\tconventionPath: string,\n\terrorProps: ReturnType<typeof buildErrorProps>\n) => {\n\t// Angular error pages are rendered as plain HTML templates\n\t// since the full Angular SSR pipeline is too heavy for error pages\n\tconst mod = await import(conventionPath);\n\tconst renderError = mod.default ?? mod.renderError;\n\tif (typeof renderError !== 'function') return null;\n\n\tconst html = renderError(errorProps);\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 500\n\t});\n};\n\nconst logConventionRenderError = (\n\tframework: keyof ConventionsMap,\n\tlabel: string,\n\trenderError: unknown\n) => {\n\tconst message = renderError instanceof Error ? renderError.message : '';\n\tif (\n\t\tmessage.includes('Cannot find module') ||\n\t\tmessage.includes('Cannot find package') ||\n\t\tmessage.includes('not found in module')\n\t) {\n\t\tconsole.error(\n\t\t\t`[SSR] Convention ${label} page for ${framework} failed: missing framework package. ` +\n\t\t\t\t`Ensure the ${framework} runtime is installed (e.g. bun add ${framework === 'react' ? 'react react-dom' : framework}).`\n\t\t);\n\n\t\treturn;\n\t}\n\n\tconsole.error(\n\t\t`[SSR] Failed to render ${framework} convention ${label} page:`,\n\t\trenderError\n\t);\n};\n\nconst ERROR_RENDERERS: Record<\n\tkeyof ConventionsMap,\n\t(\n\t\tconventionPath: string,\n\t\terrorProps: ReturnType<typeof buildErrorProps>\n\t) => Promise<Response | null>\n> = {\n\tangular: renderAngularError,\n\treact: renderReactError,\n\tsvelte: renderSvelteError,\n\tvue: renderVueError\n};\n\nexport const renderConventionError = async (\n\tframework: keyof ConventionsMap,\n\tpageName: string,\n\terror: unknown\n) => {\n\tconst conventionPath = resolveErrorConventionPath(framework, pageName);\n\tif (!conventionPath) return null;\n\n\tconst errorProps = buildErrorProps(error);\n\tconst renderer = ERROR_RENDERERS[framework];\n\tif (!renderer) return null;\n\n\ttry {\n\t\treturn await renderer(conventionPath, errorProps);\n\t} catch (renderError) {\n\t\tlogConventionRenderError(framework, 'error', renderError);\n\t}\n\n\treturn null;\n};\n\nconst renderReactNotFound = async (conventionPath: string) => {\n\tconst { createElement } = await import('react');\n\tconst { renderToReadableStream } = await import('react-dom/server');\n\tconst mod = await import(conventionPath);\n\tconst [nfKey] = Object.keys(mod);\n\tconst NotFoundComponent = mod.default ?? (nfKey ? mod[nfKey] : undefined);\n\tconst element = createElement(NotFoundComponent);\n\tconst stream = await renderToReadableStream(element);\n\n\treturn new Response(stream, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 404\n\t});\n};\n\nconst renderSvelteNotFound = async (conventionPath: string) => {\n\tconst { render } = await import('svelte/server');\n\tconst mod = await import(conventionPath);\n\tconst NotFoundComponent = mod.default;\n\tconst { head, body } = render(NotFoundComponent);\n\tconst html = `<!DOCTYPE html><html><head>${head}</head><body>${body}</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 404\n\t});\n};\n\nconst renderVueNotFound = async (conventionPath: string) => {\n\tconst { createSSRApp, h } = await import('vue');\n\tconst { renderToString } = await import('vue/server-renderer');\n\tconst mod = await import(conventionPath);\n\tconst NotFoundComponent = mod.default;\n\tconst app = createSSRApp({\n\t\trender: () => h(NotFoundComponent)\n\t});\n\tconst rawBody = await renderToString(app);\n\n\tconst { styles, body } = unescapeVueStyles(rawBody);\n\tconst html = `<!DOCTYPE html><html><head>${styles}</head><body><div id=\"root\">${body}</div></body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 404\n\t});\n};\n\nconst renderAngularNotFound = async (conventionPath: string) => {\n\tconst mod = await import(conventionPath);\n\tconst renderNotFound = mod.default ?? mod.renderNotFound;\n\tif (typeof renderNotFound !== 'function') return null;\n\n\tconst html = renderNotFound();\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' },\n\t\tstatus: 404\n\t});\n};\n\nconst NOT_FOUND_RENDERERS: Record<\n\tkeyof ConventionsMap,\n\t(conventionPath: string) => Promise<Response | null>\n> = {\n\tangular: renderAngularNotFound,\n\treact: renderReactNotFound,\n\tsvelte: renderSvelteNotFound,\n\tvue: renderVueNotFound\n};\n\nexport const renderConventionNotFound = async (\n\tframework: keyof ConventionsMap\n) => {\n\tconst conventionPath = resolveNotFoundConventionPath(framework);\n\tif (!conventionPath) return null;\n\n\tconst renderer = NOT_FOUND_RENDERERS[framework];\n\tif (!renderer) return null;\n\n\ttry {\n\t\treturn await renderer(conventionPath);\n\t} catch (renderError) {\n\t\tlogConventionRenderError(framework, 'not-found', renderError);\n\t}\n\n\treturn null;\n};\n\nconst NOT_FOUND_PRIORITY: (keyof ConventionsMap)[] = [\n\t'react',\n\t'svelte',\n\t'vue',\n\t'angular'\n];\n\nexport const renderFirstNotFound = async () => {\n\tfor (const framework of NOT_FOUND_PRIORITY) {\n\t\tif (!getMap()[framework]?.defaults?.notFound) continue;\n\t\t// eslint-disable-next-line no-await-in-loop -- frameworks must be tried sequentially; first match wins\n\t\tconst response = await renderConventionNotFound(framework);\n\t\tif (response) return response;\n\t}\n\n\treturn null;\n};\n",
|
|
15
|
-
"import type { Component as VueComponent } from 'vue';\nimport type { VuePropsOf } from '../../types/vue';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (\n\theadTag: string,\n\tindexPath: string,\n\tmaybeProps: Record<string, unknown> | undefined\n) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps ?? {})};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst html =\n\t\t`<!DOCTYPE html><html>${headTag}<body><div id=\"root\"></div>` +\n\t\t`<script>${propsScript}${dirtyFlag}</script>` +\n\t\t`<script type=\"module\" src=\"${indexPath}\"></script>` +\n\t\t`</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport const handleVuePageRequest = async <Component extends VueComponent>(\n\t_PageComponent: Component,\n\tpagePath: string,\n\tindexPath: string,\n\theadTag: `<head>${string}</head>` = '<head></head>',\n\t...props: keyof VuePropsOf<Component> extends never\n\t\t? []\n\t\t: [props: NoInfer<VuePropsOf<Component>>]\n) => {\n\tconst [maybeProps] = props;\n\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(headTag, indexPath, maybeProps);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { createSSRApp, h } = await import('vue');\n\t\tconst { renderToWebStream } = await import('vue/server-renderer');\n\n\t\tconst app = createSSRApp({\n\t\t\trender: () => h(ImportedPageComponent, maybeProps ?? null)\n\t\t});\n\n\t\tconst bodyStream = renderToWebStream(app);\n\n\t\tconst head = `<!DOCTYPE html><html>${headTag}<body><div id=\"root\">`;\n\t\tconst tail = `</div><script>window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\tmaybeProps ?? {}\n\t\t)}</script><script type=\"module\" src=\"${indexPath}\"></script></body></html>`;\n\n\t\tconst stream = new ReadableStream({\n\t\t\tstart(controller) {\n\t\t\t\tcontroller.enqueue(head);\n\t\t\t\tconst reader = bodyStream.getReader();\n\t\t\t\tconst pumpLoop = () => {\n\t\t\t\t\treader\n\t\t\t\t\t\t.read()\n\t\t\t\t\t\t.then(({ done, value }) =>\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\t\t? (controller.enqueue(tail), controller.close())\n\t\t\t\t\t\t\t\t: (controller.enqueue(value), pumpLoop())\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.catch((err) => controller.error(err));\n\t\t\t\t};\n\t\t\t\tpumpLoop();\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Vue render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'vue',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('vue', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateVueSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
15
|
+
"import type { Component as VueComponent } from 'vue';\nimport type { VuePropsOf } from '../../types/vue';\nimport { ssrErrorPage } from '../utils/ssrErrorPage';\nimport {\n\tderivePageName,\n\trenderConventionError\n} from '../utils/resolveConvention';\n\nlet ssrDirty = false;\n\nconst buildDirtyResponse = (\n\theadTag: string,\n\tindexPath: string,\n\tmaybeProps: Record<string, unknown> | undefined\n) => {\n\tconst propsScript = `window.__INITIAL_PROPS__=${JSON.stringify(maybeProps ?? {})};`;\n\tconst dirtyFlag = 'window.__SSR_DIRTY__=true;';\n\tconst html =\n\t\t`<!DOCTYPE html><html>${headTag}<body><div id=\"root\"></div>` +\n\t\t`<script>${propsScript}${dirtyFlag}</script>` +\n\t\t`<script type=\"module\" src=\"${indexPath}\"></script>` +\n\t\t`</body></html>`;\n\n\treturn new Response(html, {\n\t\theaders: { 'Content-Type': 'text/html' }\n\t});\n};\n\nexport const handleVuePageRequest = async <Component extends VueComponent>(\n\t_PageComponent: Component,\n\tpagePath: string,\n\tindexPath: string,\n\theadTag: `<head>${string}</head>` = '<head></head>',\n\t...props: keyof VuePropsOf<Component> extends never\n\t\t? [props?: Record<string, never>]\n\t\t: [props: NoInfer<VuePropsOf<Component>>]\n) => {\n\tconst [maybeProps] = props;\n\n\tif (ssrDirty) {\n\t\treturn buildDirtyResponse(headTag, indexPath, maybeProps);\n\t}\n\n\ttry {\n\t\tconst { default: ImportedPageComponent } = await import(pagePath);\n\t\tconst { createSSRApp, h } = await import('vue');\n\t\tconst { renderToWebStream } = await import('vue/server-renderer');\n\n\t\tconst app = createSSRApp({\n\t\t\trender: () => h(ImportedPageComponent, maybeProps ?? null)\n\t\t});\n\n\t\tconst bodyStream = renderToWebStream(app);\n\n\t\tconst head = `<!DOCTYPE html><html>${headTag}<body><div id=\"root\">`;\n\t\tconst tail = `</div><script>window.__INITIAL_PROPS__=${JSON.stringify(\n\t\t\tmaybeProps ?? {}\n\t\t)}</script><script type=\"module\" src=\"${indexPath}\"></script></body></html>`;\n\n\t\tconst stream = new ReadableStream({\n\t\t\tstart(controller) {\n\t\t\t\tcontroller.enqueue(head);\n\t\t\t\tconst reader = bodyStream.getReader();\n\t\t\t\tconst pumpLoop = () => {\n\t\t\t\t\treader\n\t\t\t\t\t\t.read()\n\t\t\t\t\t\t.then(({ done, value }) =>\n\t\t\t\t\t\t\tdone\n\t\t\t\t\t\t\t\t? (controller.enqueue(tail), controller.close())\n\t\t\t\t\t\t\t\t: (controller.enqueue(value), pumpLoop())\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.catch((err) => controller.error(err));\n\t\t\t\t};\n\t\t\t\tpumpLoop();\n\t\t\t}\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: { 'Content-Type': 'text/html' }\n\t\t});\n\t} catch (error) {\n\t\tconsole.error('[SSR] Vue render error:', error);\n\n\t\tconst pageName = derivePageName(pagePath);\n\t\tconst conventionResponse = await renderConventionError(\n\t\t\t'vue',\n\t\t\tpageName,\n\t\t\terror\n\t\t);\n\t\tif (conventionResponse) return conventionResponse;\n\n\t\treturn new Response(ssrErrorPage('vue', error), {\n\t\t\theaders: { 'Content-Type': 'text/html' },\n\t\t\tstatus: 500\n\t\t});\n\t}\n};\n\nexport const invalidateVueSsrCache = () => {\n\tssrDirty = true;\n};\n",
|
|
16
16
|
"import { defineComponent, h, onServerPrefetch } from 'vue';\nimport type {\n\tIslandRegistry,\n\tIslandRegistryInput,\n\tRuntimeIslandRenderProps,\n\tTypedIslandRenderProps\n} from '../../types/island';\nimport {\n\tgetIslandMarkerAttributes,\n\trenderIslandResult\n} from '../core/renderIslandMarkup';\n\ntype VueIslandComponent<T extends IslandRegistryInput> = new () => {\n\t$props: TypedIslandRenderProps<T>;\n};\n\nconst defineRuntimeIslandComponent = (\n\tsetup: (props: RuntimeIslandRenderProps) => () => ReturnType<typeof h>\n): any =>\n\tdefineComponent({\n\t\tname: 'AbsoluteIsland',\n\t\tprops: {\n\t\t\tcomponent: {\n\t\t\t\trequired: true,\n\t\t\t\ttype: String\n\t\t\t},\n\t\t\tframework: {\n\t\t\t\trequired: true,\n\t\t\t\ttype: String\n\t\t\t},\n\t\t\thydrate: {\n\t\t\t\trequired: false,\n\t\t\t\ttype: String\n\t\t\t},\n\t\t\tprops: {\n\t\t\t\trequired: true,\n\t\t\t\ttype: Object\n\t\t\t}\n\t\t},\n\t\tsetup\n\t});\n\nexport const createIsland = <T extends IslandRegistryInput>(\n\tregistry: IslandRegistry<T>\n): VueIslandComponent<T> =>\n\tdefineRuntimeIslandComponent((props) => {\n\t\tconst isBrowser = typeof window !== 'undefined';\n\t\tlet markerAttributes = getIslandMarkerAttributes(props);\n\t\tlet html = '';\n\n\t\tonServerPrefetch(async () => {\n\t\t\tconst result = await renderIslandResult(registry, props);\n\t\t\tmarkerAttributes = result.attributes;\n\t\t\thtml = result.html;\n\t\t});\n\n\t\treturn () =>\n\t\t\th('div', {\n\t\t\t\t...markerAttributes,\n\t\t\t\t'data-allow-mismatch': '',\n\t\t\t\tinnerHTML: isBrowser ? undefined : html\n\t\t\t});\n\t});\n",
|
|
17
17
|
"import { createElement } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport { render as renderSvelte } from 'svelte/server';\nimport { createSSRApp, h } from 'vue';\nimport { renderToString as renderVueToString } from 'vue/server-renderer';\nimport { renderAngularIslandToHtml } from '../angular/islands';\n\nexport const renderReactIslandToHtml = <\n\tProps extends Record<string, unknown>\n>(\n\tcomponent: React.ComponentType<Props>,\n\tprops: Props\n) => renderToStaticMarkup(createElement(component, props));\n\nexport const renderSvelteIslandToHtml = <\n\tProps extends Record<string, unknown>\n>(\n\tcomponent: import('svelte').Component<Props>,\n\tprops: Props\n) => {\n\tconst { body } = renderSvelte(component, { props });\n\n\treturn body;\n};\n\nexport const renderVueIslandToHtml = <Props extends Record<string, unknown>>(\n\tcomponent: import('vue').Component<Props>,\n\tprops: Props\n) => {\n\tconst app = createSSRApp({\n\t\trender: () => h(component, props)\n\t});\n\n\treturn renderVueToString(app);\n};\n\nexport { renderAngularIslandToHtml };\n",
|
|
18
18
|
"import { getAngularDeps } from './angularDeps';\n\nconst sanitizeIslandId = (value: string) =>\n\tvalue.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n\nexport const getAngularIslandSelector = (islandId: string) =>\n\t`abs-angular-island-${sanitizeIslandId(islandId)}`;\n\nconst getAngularComponentSelector = (\n\tcomponent: import('@angular/core').Type<object>,\n\tdeps: Awaited<ReturnType<typeof getAngularDeps>>\n) => {\n\tconst reflectedSelector = deps.reflectComponentType(component)?.selector;\n\tif (reflectedSelector) return reflectedSelector;\n\n\tconst maybeDef = Reflect.get(component, 'ɵcmp');\n\tif (typeof maybeDef !== 'object' || maybeDef === null) {\n\t\treturn null;\n\t}\n\n\tconst maybeSelectors = Reflect.get(maybeDef, 'selectors');\n\tif (!Array.isArray(maybeSelectors)) {\n\t\treturn null;\n\t}\n\n\tconst firstSelectorGroup = maybeSelectors[0];\n\tif (!Array.isArray(firstSelectorGroup)) {\n\t\treturn null;\n\t}\n\n\tconst selector = firstSelectorGroup[0];\n\tif (typeof selector === 'string' && selector.length > 0) {\n\t\treturn selector;\n\t}\n\n\treturn null;\n};\n\nconst getSelectorFromRenderedIsland = (rootElement: HTMLElement) => {\n\tconst firstChild = rootElement.firstElementChild;\n\tif (!(firstChild instanceof HTMLElement)) {\n\t\treturn null;\n\t}\n\n\tconst selector = firstChild.tagName.toLowerCase();\n\treturn selector.length > 0 ? selector : null;\n};\n\nconst getClientAngularComponentSelector = (\n\tcomponent: import('@angular/core').Type<object>\n) => {\n\tconst maybeDef = Reflect.get(component, 'ɵcmp');\n\tif (typeof maybeDef !== 'object' || maybeDef === null) {\n\t\treturn null;\n\t}\n\n\tconst maybeSelectors = Reflect.get(maybeDef, 'selectors');\n\tif (!Array.isArray(maybeSelectors)) {\n\t\treturn null;\n\t}\n\n\tconst firstSelectorGroup = maybeSelectors[0];\n\tif (!Array.isArray(firstSelectorGroup)) {\n\t\treturn null;\n\t}\n\n\tconst selector = firstSelectorGroup[0];\n\treturn typeof selector === 'string' && selector.length > 0\n\t\t? selector\n\t\t: null;\n};\n\nconst createAngularIslandWrapper = async <\n\tProps extends Record<string, unknown>\n>(\n\tcomponent: import('@angular/core').Type<object>,\n\tislandId: string,\n\tprops: Props\n) => {\n\tconst { Component } = await import('@angular/core');\n\tconst { NgComponentOutlet } = await import('@angular/common');\n\tconst deps = await getAngularDeps();\n\tconst islandProps = props;\n\tconst selector = getAngularIslandSelector(islandId);\n\n\tclass AngularIslandWrapperComponent {\n\t\tcomponent = component;\n\t\tprops = islandProps;\n\t}\n\n\treturn {\n\t\tdeps,\n\t\tselector,\n\t\tWrapperComponent: Component({\n\t\t\timports: [NgComponentOutlet, component],\n\t\t\tselector,\n\t\t\tstandalone: true,\n\t\t\ttemplate:\n\t\t\t\t'<ng-container *ngComponentOutlet=\"component; inputs: props\"></ng-container>'\n\t\t})(AngularIslandWrapperComponent)\n\t};\n};\n\nconst extractAngularIslandRoot = (html: string, selector: string) => {\n\tconst openTag = `<${selector}`;\n\tconst start = html.indexOf(openTag);\n\tif (start < 0) {\n\t\tthrow new Error(`Could not find Angular island root \"${selector}\".`);\n\t}\n\n\tconst endTag = `</${selector}>`;\n\tconst end = html.indexOf(endTag, start);\n\tif (end < 0) {\n\t\tthrow new Error(`Could not close Angular island root \"${selector}\".`);\n\t}\n\n\treturn html.slice(start, end + endTag.length);\n};\n\nexport const renderAngularIslandToHtml = async <\n\tProps extends Record<string, unknown>\n>(\n\tcomponent: import('@angular/core').Type<object>,\n\tprops: Props,\n\tislandId: string\n) => {\n\tconst { deps, selector, WrapperComponent } =\n\t\tawait createAngularIslandWrapper(component, islandId, props);\n\n\tconst providers = [\n\t\tdeps.provideServerRendering(),\n\t\tdeps.provideZonelessChangeDetection(),\n\t\t{ provide: deps.APP_BASE_HREF, useValue: '/' }\n\t];\n\tconst document = `<!DOCTYPE html><html><body><${selector}></${selector}></body></html>`;\n\tconst html = await deps.renderApplication(\n\t\t(context) =>\n\t\t\tdeps.bootstrapApplication(WrapperComponent, { providers }, context),\n\t\t{\n\t\t\tdocument,\n\t\t\tplatformProviders: [],\n\t\t\turl: '/'\n\t\t}\n\t);\n\n\treturn extractAngularIslandRoot(html, selector);\n};\n\nexport const mountAngularIsland = async <\n\tProps extends Record<string, unknown>\n>(\n\tcomponent: import('@angular/core').Type<object>,\n\telement: HTMLElement,\n\tprops: Props,\n\tislandId: string\n) => {\n\tawait import('@angular/compiler');\n\tconst { createComponent, inputBinding, provideZonelessChangeDetection } =\n\t\tawait import('@angular/core');\n\tconst { createApplication } = await import('@angular/platform-browser');\n\tconst selector = getAngularIslandSelector(islandId);\n\tconst app = await createApplication({\n\t\tproviders: [provideZonelessChangeDetection()]\n\t});\n\n\tlet rootElement = element.querySelector(selector);\n\tif (!(rootElement instanceof HTMLElement)) {\n\t\telement.innerHTML = `<${selector}></${selector}>`;\n\t\trootElement = element.querySelector(selector);\n\t}\n\tif (!(rootElement instanceof HTMLElement)) return app;\n\n\tconst componentSelector =\n\t\tgetClientAngularComponentSelector(component) ??\n\t\tgetSelectorFromRenderedIsland(rootElement);\n\tif (!componentSelector) return app;\n\n\tlet hostElement = rootElement.querySelector(componentSelector);\n\tif (!(hostElement instanceof HTMLElement)) {\n\t\trootElement.innerHTML = `<${componentSelector}></${componentSelector}>`;\n\t\thostElement = rootElement.querySelector(componentSelector);\n\t}\n\tif (!(hostElement instanceof HTMLElement)) return app;\n\n\thostElement.innerHTML = '';\n\n\tconst bindings = Object.entries(props).map(([key, value]) =>\n\t\tinputBinding(key, () => value)\n\t);\n\tconst componentRef = createComponent(component, {\n\t\tbindings,\n\t\tenvironmentInjector: app.injector,\n\t\thostElement\n\t});\n\tapp.attachView(componentRef.hostView);\n\tcomponentRef.changeDetectorRef.detectChanges();\n\t(\n\t\twindow as Window & {\n\t\t\t__ABS_ANGULAR_ISLAND_APPS__?: unknown[];\n\t\t}\n\t).__ABS_ANGULAR_ISLAND_APPS__ ??= [];\n\t(\n\t\twindow as Window & {\n\t\t\t__ABS_ANGULAR_ISLAND_APPS__?: unknown[];\n\t\t}\n\t).__ABS_ANGULAR_ISLAND_APPS__?.push(app);\n\n\treturn app;\n};\n",
|
package/package.json
CHANGED