@useavalon/avalon 0.3.2-canary.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +1 -1
- package/dist/src/build/integration-config.js +1 -1
- package/dist/src/build/island-client-bundler.js +1 -1
- package/dist/src/build/island-manifest.d.ts +3 -0
- package/dist/src/build/island-manifest.js +1 -1
- package/dist/src/build/island-ssr-flag.d.ts +14 -0
- package/dist/src/build/island-ssr-flag.js +1 -0
- package/dist/src/build/mdx-island-transform.js +2 -2
- package/dist/src/build/mdx-plugin.js +1 -1
- package/dist/src/build/page-island-await.d.ts +18 -0
- package/dist/src/build/page-island-await.js +2 -0
- package/dist/src/build/page-island-transform.d.ts +3 -1
- package/dist/src/build/page-island-transform.js +2 -2
- package/dist/src/client/css-hmr-handler.d.ts +7 -90
- package/dist/src/client/css-hmr-handler.js +1 -1
- package/dist/src/client/custom-directives.js +1 -1
- package/dist/src/client/hmr-coordinator.d.ts +0 -1
- package/dist/src/client/hmr-coordinator.js +1 -1
- package/dist/src/client/hmr-error-overlay.d.ts +18 -0
- package/dist/src/client/hydrate-runtime.d.ts +33 -0
- package/dist/src/client/hydrate-runtime.js +1 -0
- package/dist/src/client/hydration-mode.d.ts +11 -0
- package/dist/src/client/hydration-mode.js +1 -0
- package/dist/src/client/main-slim.js +1 -0
- package/dist/src/client/main.js +2 -15
- package/dist/src/client/router/a11y.d.ts +9 -0
- package/dist/src/client/router/a11y.js +1 -0
- package/dist/src/client/router/eligibility.d.ts +32 -0
- package/dist/src/client/router/eligibility.js +1 -0
- package/dist/src/client/router/events.d.ts +33 -0
- package/dist/src/client/router/events.js +1 -0
- package/dist/src/client/router/forms.d.ts +25 -0
- package/dist/src/client/router/forms.js +1 -0
- package/dist/src/client/router/index.d.ts +20 -0
- package/dist/src/client/router/index.js +1 -0
- package/dist/src/client/router/opt-out.d.ts +30 -0
- package/dist/src/client/router/opt-out.js +1 -0
- package/dist/src/client/router/persist.d.ts +24 -0
- package/dist/src/client/router/persist.js +1 -0
- package/dist/src/client/router/prefetch.d.ts +25 -0
- package/dist/src/client/router/prefetch.js +1 -0
- package/dist/src/client/router/swap.d.ts +31 -0
- package/dist/src/client/router/swap.js +1 -0
- package/dist/src/client/router/transitions.d.ts +22 -0
- package/dist/src/client/router/transitions.js +1 -0
- package/dist/src/client/server-islands-boot.d.ts +12 -0
- package/dist/src/client/server-islands-boot.js +1 -0
- package/dist/src/client/types/vite-virtual-modules.d.ts +7 -0
- package/dist/src/islands/builtin-directives.d.ts +1 -0
- package/dist/src/islands/builtin-directives.js +1 -1
- package/dist/src/islands/critical-css.js +1 -1
- package/dist/src/islands/framework-base-css.d.ts +5 -1
- package/dist/src/islands/framework-base-css.js +1 -1
- package/dist/src/islands/hydration-directives.js +1 -1
- package/dist/src/islands/island.d.ts +24 -3
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/per-island-script.js +1 -1
- package/dist/src/islands/types.d.ts +6 -0
- package/dist/src/nitro/renderer.d.ts +2 -1
- package/dist/src/nitro/renderer.js +20 -20
- package/dist/src/nitro/types.d.ts +5 -0
- package/dist/src/post-build/cloudflare-worker-patch.d.ts +32 -0
- package/dist/src/post-build/cloudflare-worker-patch.js +1 -0
- package/dist/src/post-build/fetch-handler-wrapper.d.ts +1 -0
- package/dist/src/post-build/fetch-handler-wrapper.js +56 -0
- package/dist/src/post-build/index.d.ts +16 -0
- package/dist/src/post-build/index.js +6 -51
- package/dist/src/post-build/isolated-island-builder.js +2 -2
- package/dist/src/render/collect-css.d.ts +29 -0
- package/dist/src/render/collect-css.js +3 -3
- package/dist/src/render/dev-css-select.d.ts +40 -0
- package/dist/src/render/dev-css-select.js +5 -0
- package/dist/src/schemas/routing/index.d.ts +2 -0
- package/dist/src/schemas/routing.d.ts +1 -0
- package/dist/src/schemas/routing.js +1 -1
- package/dist/src/server-islands/encryption.js +1 -1
- package/dist/src/server-islands/endpoint.js +3 -3
- package/dist/src/server-islands/renderer.js +6 -5
- package/dist/src/server-islands/route.d.ts +3 -1
- package/dist/src/server-islands/route.js +1 -1
- package/dist/src/types/island-prop.d.ts +28 -9
- package/dist/src/types/virtual-modules.d.ts +26 -6
- package/dist/src/types/vite-env.d.ts +4 -0
- package/dist/src/vite-plugin/config.js +1 -1
- package/dist/src/vite-plugin/dev-css-graph.d.ts +41 -0
- package/dist/src/vite-plugin/dev-css-graph.js +1 -0
- package/dist/src/vite-plugin/dev-css-hmr.d.ts +42 -0
- package/dist/src/vite-plugin/dev-css-hmr.js +2 -0
- package/dist/src/vite-plugin/dev-css-modules.d.ts +8 -0
- package/dist/src/vite-plugin/dev-css-modules.js +1 -0
- package/dist/src/vite-plugin/framework-vite-defaults.d.ts +29 -0
- package/dist/src/vite-plugin/framework-vite-defaults.js +1 -0
- package/dist/src/vite-plugin/nitro-integration.d.ts +4 -0
- package/dist/src/vite-plugin/nitro-integration.js +20 -20
- package/dist/src/vite-plugin/plugin.d.ts +2 -0
- package/dist/src/vite-plugin/plugin.js +1 -1
- package/dist/src/vite-plugin/server-islands-plugin.js +1 -1
- package/dist/src/vite-plugin/ssr-dom-shim-module.d.ts +27 -0
- package/dist/src/vite-plugin/ssr-dom-shim-module.js +121 -0
- package/dist/src/vite-plugin/stub-build-time-packages.d.ts +16 -0
- package/dist/src/vite-plugin/stub-build-time-packages.js +2 -0
- package/dist/src/vite-plugin/types.d.ts +12 -0
- package/package.json +13 -7
|
@@ -86,6 +86,7 @@ export interface RenderHandlerOptions {
|
|
|
86
86
|
*/
|
|
87
87
|
enableCustomErrorPages?: boolean;
|
|
88
88
|
}
|
|
89
|
+
type WrapWithLayouts = NonNullable<RenderHandlerOptions["wrapWithLayouts"]>;
|
|
89
90
|
/**
|
|
90
91
|
* Creates a render context from an H3 event
|
|
91
92
|
*
|
|
@@ -300,7 +301,7 @@ export interface NitroCatchAllOptions {
|
|
|
300
301
|
/** Optional layout resolver */
|
|
301
302
|
resolveLayouts?: (routePath: string, config: AvalonRuntimeConfig) => Promise<string[]>;
|
|
302
303
|
/** Wrap rendered page HTML with layout components (same as RenderHandlerOptions) */
|
|
303
|
-
wrapWithLayouts?:
|
|
304
|
+
wrapWithLayouts?: WrapWithLayouts;
|
|
304
305
|
/**
|
|
305
306
|
* Enable custom error pages (404.tsx, 500.tsx, _error.tsx)
|
|
306
307
|
* When enabled, the renderer will look for custom error pages in the pages directory
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{getRequestURL as e}from"h3";import{
|
|
1
|
+
import{getRequestURL as e}from"h3";import{clientNavigationResponseHeaders as t,isClientNavigationDisabled as n,stampClientNavigationOptOut as r}from"../client/router/opt-out.js";import{inlineCriticalCSS as i}from"../islands/critical-css.js";import{injectModulepreloadLinks as a}from"../islands/modulepreload-collector.js";import{discoverScopedMiddleware as o,executeScopedMiddleware as s}from"../middleware/index.js";import{renderShell as c}from"../render/shell-engine.js";import{discoverErrorPages as l,handleRenderError as u}from"./error-handler.js";import{createNotFoundError as d,isHttpError as f}from"./types.js";function p(e,t){return{...e.context.params??{},...t.params}}async function m(e,t,n,r){return e.context.route||(r?r(t,n):L(t,n))}export function createRenderContext(e,t={}){let n=getRequestURL(e);return{url:n,params:t,query:Object.fromEntries(n.searchParams),request:toRequest(e),event:e}}export function createRenderContextFromRequest(e,t={}){let n=new URL(e.url,`http://localhost`),r={method:e.method,path:n.pathname+n.search,context:{params:t}};return{url:n,params:t,query:Object.fromEntries(n.searchParams),request:e,event:r}}export function getRequestURL(t){return new URL(e(t).pathname,`http://localhost`)}export function toRequest(e){let t=getRequestURL(e);return new Request(t,{method:e.req.method,headers:getRequestHeaders(e)})}export function getRequestHeaders(e){return new Headers}export function setResponseHeader(e,t,n){e.context.responseHeaders||(e.context.responseHeaders={}),e.context.responseHeaders[t]=n}function g(e){return{"Content-Type":`text/html; charset=utf-8`,...t(n(e))}}function _(e,t){return a(i(injectHydrationScript(e,t)))}async function v(e,t,n,r){let i=await renderPage(e,t,{},r);return new Response(_(i.html,n),{status:i.statusCode,headers:i.headers})}async function y(e,t,n){let r=await renderPageStream(e,t,{onShellReady:()=>{for(let[t,r]of Object.entries(g(e)))setResponseHeader(n,t,r)}});return new Response(r,{headers:g(e)})}async function b(e,t,n,r,i,a){return i&&a?y(e,t,a):v(e,t,n,r)}async function x(e,t,n,r){try{return await e(t)}catch(i){try{return await e(n)}catch(e){return r&&(console.debug(`[renderer] Page not found: ${t}`,i),console.debug(`[renderer] Index fallback not found: ${n}`,e)),null}}}export function createErrorResponse(e,t){let n=f(e)?e.statusCode:500;if(t){let t=S(e,n);return new Response(t,{status:n,headers:{"Content-Type":`text/html; charset=utf-8`}})}let r=C(n);return new Response(r,{status:n,headers:{"Content-Type":`text/html; charset=utf-8`}})}function S(e,t){return`<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
@@ -56,15 +56,15 @@ import{getRequestURL as e}from"h3";import{inlineCriticalCSS as t}from"../islands
|
|
|
56
56
|
<body>
|
|
57
57
|
<div class="error-container">
|
|
58
58
|
<div class="status-code">${t}</div>
|
|
59
|
-
<h1>${
|
|
60
|
-
<p class="message">${
|
|
59
|
+
<h1>${w(t)}</h1>
|
|
60
|
+
<p class="message">${T(e.message)}</p>
|
|
61
61
|
${e.stack?`
|
|
62
62
|
<div class="stack-title">Stack Trace</div>
|
|
63
|
-
<pre>${
|
|
63
|
+
<pre>${T(e.stack)}</pre>
|
|
64
64
|
`:``}
|
|
65
65
|
</div>
|
|
66
66
|
</body>
|
|
67
|
-
</html>`}function
|
|
67
|
+
</html>`}function C(e){return`<!DOCTYPE html>
|
|
68
68
|
<html lang="en">
|
|
69
69
|
<head>
|
|
70
70
|
<meta charset="utf-8">
|
|
@@ -113,47 +113,47 @@ import{getRequestURL as e}from"h3";import{inlineCriticalCSS as t}from"../islands
|
|
|
113
113
|
<body>
|
|
114
114
|
<div class="error-container">
|
|
115
115
|
<div class="status-code">${e}</div>
|
|
116
|
-
<h1>${
|
|
116
|
+
<h1>${w(e)}</h1>
|
|
117
117
|
<p><a href="/">Return to home</a></p>
|
|
118
118
|
</div>
|
|
119
119
|
</body>
|
|
120
|
-
</html>`}function
|
|
121
|
-
`);return e.includes(`</body>`)?e.replace(`</body>`,`${
|
|
120
|
+
</html>`}function w(e){return{400:`Bad Request`,401:`Unauthorized`,403:`Forbidden`,404:`Page Not Found`,405:`Method Not Allowed`,500:`Internal Server Error`,502:`Bad Gateway`,503:`Service Unavailable`}[e]||`Error`}function T(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}function E(e,t){let n=`${t}="`,r=e.indexOf(n);if(r===-1)return;let i=r+n.length,a=e.indexOf(`"`,i);if(a!==-1)return e.slice(i,a)}export function extractIslandMarkers(e){let t=[],n=0;for(;n<e.length;){let r=e.indexOf(`data-framework="`,n);if(r===-1)break;let i=e.lastIndexOf(`<`,r),a=e.indexOf(`>`,r);if(i===-1||a===-1){n=r+16;continue}let o=e.slice(i,a+1),s=E(o,`data-framework`);if(!s){n=r+16;continue}t.push({framework:s,src:E(o,`data-src`)??``,props:E(o,`data-props`),hydrate:E(o,`data-hydrate`)}),n=a+1}return t}export function ensureHydrationMarkers(e,t){let n=e;return t.framework&&!n.includes(`data-framework=`)&&(n=n.replace(/>/,` data-framework="${t.framework}">`)),t.src&&!n.includes(`data-src=`)&&(n=n.replace(/>/,` data-src="${t.src}">`)),t.props!==void 0&&!n.includes(`data-props=`)&&(n=n.replace(/>/,` data-props="${t.props}">`)),n}function D(e){return e.replaceAll(/<div[^>]*\bdata-island-script\b[^>]*>(<script[\s\S]*?<\/script>)<\/div>/g,`$1`)}export function injectHydrationScript(e,t,n={}){e=D(e);let r=globalThis.__avalonHydrationMode;if(r===`per-island`||r===void 0&&!t||!(e.includes(`data-framework=`)||e.includes(`data-src=`))&&!n.forceInject||[`/src/client/main.js`,`/dist/client.js`,`client/main.js`,`entry-client`].some(t=>e.includes(t)))return e;let i=n.scriptPath||(t?`/src/client/main.js`:`/dist/client.js`),a=[];a.push(`<script type="module" src="${i}"><\/script>`),n.additionalScripts&&a.push(...n.additionalScripts);let o=a.join(`
|
|
121
|
+
`);return e.includes(`</body>`)?e.replace(`</body>`,`${o}\n</body>`):e+o}export function validateHydrationMarkers(e){let t=extractIslandMarkers(e),n=e.match(/data-framework="[^"]+"/g)||[],r=e.match(/data-src="[^"]+"/g)||[],i=e.match(/data-props="[^"]*"/g)||[],a=e.includes(`/src/client/main.js`)||e.includes(`/dist/client.js`)||e.includes(`client/main.js`),o=t.every(e=>e.framework&&e.src),s=t.length===0||o&&a;return{hasFrameworkAttr:n.length>0,hasSrcAttr:r.length>0,hasPropsAttr:i.length>0,islandCount:n.length,islands:t,hasClientScript:a,isValid:s}}export function processHydrationRequirements(e,t){let n=injectHydrationScript(e,t);return{html:n,validation:validateHydrationMarkers(n)}}export async function renderPage(e,t,i={},a){try{let o={};e.getServerSideProps&&(o=await e.getServerSideProps(t));let s=await O(e,o,t,i,a);return n(e)&&(s=r(s)),{html:s,statusCode:200,headers:g(e)}}catch(e){throw console.error(`[SSR Error]`,e),i.onError&&e instanceof Error&&i.onError(e),e}}async function O(e,t,n,r,i){let a=e.default,o=e.metadata||{},s=process.env.NODE_ENV!==`production`,l,u=!1;try{let e=a(t);l=e instanceof Promise?await e:e}catch(e){console.error(`[renderer] Error calling page component:`,e),u=!0}let d;if(u)d=`<div>Error rendering page</div>`;else try{d=c(l)}catch(e){console.error(`[renderer] Error rendering page shell:`,e),d=`<div>Error rendering page</div>`}if(i)return await i(d,e,n);let f=s?`
|
|
122
122
|
<script type="module" src="/src/client/main.js"><\/script>`:``;return`<!DOCTYPE html>
|
|
123
123
|
<html lang="en">
|
|
124
124
|
<head>
|
|
125
125
|
<meta charset="utf-8">
|
|
126
126
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
127
|
-
<title>${
|
|
128
|
-
${
|
|
127
|
+
<title>${T(String(o.title||`Avalon App`))}</title>
|
|
128
|
+
${o.description?`<meta name="description" content="${T(String(o.description))}">`:``}
|
|
129
129
|
</head>
|
|
130
130
|
<body>
|
|
131
131
|
<div id="app">
|
|
132
132
|
${d}
|
|
133
133
|
</div>${f}
|
|
134
134
|
</body>
|
|
135
|
-
</html>`}export async function renderPageStream(e,t,n={}){let r=new TextEncoder,i=null,
|
|
135
|
+
</html>`}function k(e,t,i,a,o,s,c){let l=j(i.metadata||{},a),u=n(i)?r(l):l;e.enqueue(t.encode(u)),o.shellSent=!0,s(),c?.()}async function A(e,t,n,r){let i=n.default;if(typeof i!=`function`){e.enqueue(t.encode(M(n,r)));return}let a=i(r);if(a&&typeof a.then==`function`)try{let n=c(await a);e.enqueue(t.encode(` <div id="app">${n}</div>\n`));return}catch(e){console.error(`[streaming] Async component error:`,e)}e.enqueue(t.encode(M(n,r)))}export async function renderPageStream(e,t,n={}){let r=new TextEncoder,i=null,a={shellSent:!1,contentSent:!1,closed:!1,error:null},o=n.shellReadyTimeout,s=n.allReadyTimeout,c=null,l=null,u=()=>{c&&=(clearTimeout(c),null),l&&=(clearTimeout(l),null)};function d(e,t,n,r,i,a,o){if(n.error=e,a(),console.error(`[Streaming Error]`,{message:e.message,stack:e.stack,shellSent:n.shellSent,isShellError:t,timestamp:new Date().toISOString()}),t&&o.onShellError&&o.onShellError(e),o.onError&&o.onError(e),!n.closed&&r){if(n.shellSent){let t=P(e);r.enqueue(i.encode(t));let n=N();r.enqueue(i.encode(n))}else{let t=S(e,500);r.enqueue(i.encode(t))}n.closed=!0,r.close()}}async function f(o){i=o;let f={};e.getServerSideProps&&(f=await e.getServerSideProps(t)),a.closed||k(o,r,e,t,a,()=>{c&&=(clearTimeout(c),null)},n.onShellReady),s&&s>0&&(l=setTimeout(()=>{!a.contentSent&&!a.closed&&d(Error(`All ready timeout after ${s}ms`),!1,a,i,r,u,n)},s)),a.closed||(await A(o,r,e,f),a.contentSent=!0),a.closed||o.enqueue(r.encode(N())),u(),n.onAllReady&&!a.closed&&n.onAllReady(),a.closed||(a.closed=!0,o.close())}return new ReadableStream({async start(e){i=e,o&&o>0&&(c=setTimeout(()=>{!a.shellSent&&!a.closed&&d(Error(`Shell ready timeout after ${o}ms`),!0,a,i,r,u,n)},o));try{await f(e)}catch(e){d(e instanceof Error?e:Error(String(e)),!a.shellSent,a,i,r,u,n)}},cancel(){if(u(),!a.closed&&i){a.closed=!0;try{i.close()}catch{}}}})}function j(e,t){return`<!DOCTYPE html>
|
|
136
136
|
<html lang="en">
|
|
137
137
|
<head>
|
|
138
138
|
<meta charset="utf-8">
|
|
139
139
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
140
|
-
<title>${
|
|
141
|
-
${e.description?`<meta name="description" content="${
|
|
140
|
+
<title>${T(String(e.title||`Avalon App`))}</title>
|
|
141
|
+
${e.description?`<meta name="description" content="${T(String(e.description))}">`:``}
|
|
142
142
|
</head>
|
|
143
143
|
<body>
|
|
144
|
-
`}function
|
|
144
|
+
`}function M(e,t){let n=e.default;if(n&&typeof n==`function`)try{let e=n(t);if(e&&typeof e.then==`function`){let e=n.name||`Page`;return` <div id="app" data-page="${T(String(e))}" data-props='${T(JSON.stringify(t))}'>
|
|
145
145
|
<!-- Async component — awaiting hydration -->
|
|
146
|
-
</div>\n`}return` <div id="app">${
|
|
146
|
+
</div>\n`}return` <div id="app">${c(e)}</div>\n`}catch(e){console.error(`[streaming] Error rendering page component:`,e)}let r=e.default?.name||`Page`;return` <div id="app" data-page="${T(String(r))}" data-props='${T(JSON.stringify(t))}'>
|
|
147
147
|
<!-- Component render fallback -->
|
|
148
|
-
</div>\n`}function
|
|
149
|
-
</html>`}function
|
|
148
|
+
</div>\n`}function N(){return` </body>
|
|
149
|
+
</html>`}function P(e){let t=process.env.NODE_ENV!==`production`,n=e.stack?`<pre style="
|
|
150
150
|
background: #f5f5f5;
|
|
151
151
|
padding: 10px;
|
|
152
152
|
border-radius: 4px;
|
|
153
153
|
overflow-x: auto;
|
|
154
154
|
font-size: 12px;
|
|
155
155
|
margin-top: 10px;
|
|
156
|
-
">${
|
|
156
|
+
">${T(e.stack)}</pre>`:``;return`
|
|
157
157
|
<div class="streaming-error-boundary" data-error-boundary="true" style="
|
|
158
158
|
background: #fff3cd;
|
|
159
159
|
border: 2px solid #ffc107;
|
|
@@ -175,10 +175,10 @@ import{getRequestURL as e}from"h3";import{inlineCriticalCSS as t}from"../islands
|
|
|
175
175
|
Error Details (Development Mode)
|
|
176
176
|
</summary>
|
|
177
177
|
<div style="margin-top: 10px;">
|
|
178
|
-
<p><strong>Error:</strong> ${
|
|
178
|
+
<p><strong>Error:</strong> ${T(e.message)}</p>
|
|
179
179
|
${n}
|
|
180
180
|
</div>
|
|
181
181
|
</details>
|
|
182
182
|
`:``}
|
|
183
183
|
</div>
|
|
184
|
-
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function
|
|
184
|
+
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function F(e,t,n){return async()=>(e.value??=await o({baseDir:t,devMode:n}),e.value)}function I(e,t,n){return async(r,i)=>e?u(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:r=!0}=e,i={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};r&&l(i).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let a=F({value:null},t.srcDir||`src`,n),o=I(r,i,n);async function c(r){let i=getRequestURL(r).pathname;if(i.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let c=await s(r,await a(),{devMode:n});if(c)return n&&console.log(`[renderer] Middleware terminated request for ${i}`),c;let l=await m(r,i,t.pagesDir,e.resolvePageRoute);if(!l)return o(d(`Page not found: ${i}`),r);let u=e.loadPageModule?await e.loadPageModule(l.filePath):await R(l.filePath),f=createRenderContext(r,p(r,l));return e.resolveLayouts&&(f.layoutContext={layouts:await e.resolveLayouts(i,t)}),b(u,f,n,e.wrapWithLayouts,!!(t.streaming&&!e.wrapWithLayouts),r)}catch(e){return console.error(`[Nitro Renderer Error]`,e),o(e instanceof Error?e:Error(String(e)),r)}}return Object.assign(c,{async fetch(r){let i=new URL(r.url,`http://localhost`).pathname;if(i.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let a=null;return a=e.resolvePageRoute?await e.resolvePageRoute(i,t.pagesDir):await L(i,t.pagesDir),a?v(e.loadPageModule?await e.loadPageModule(a.filePath):await R(a.filePath),createRenderContextFromRequest(r,a.params),n,e.wrapWithLayouts):createErrorResponse(d(`Page not found: ${i}`),n)}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function L(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function R(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:r,resolveLayouts:i,enableCustomErrorPages:a=!0}=e,o={isDev:n,avalonConfig:t,loadPageModule:r,pagesDir:t.pagesDir};a&&l(o).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let c=F({value:null},t.srcDir||`src`,n),u=I(a,o,n);async function f(a){let o=getRequestURL(a).pathname;if(o.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let l=await s(a,await c(),{devMode:n});if(l)return n&&console.log(`[renderer] Middleware terminated request for ${o}`),l;let f=a.context.params||{},p=f.slug||o.replace(/^\//,``)||`index`,m=await x(r,`${t.pagesDir}/${p}.tsx`,`${t.pagesDir}/${p}/index.tsx`,n);if(!m)return u(d(`Page not found: ${o}`),a);let h=createRenderContext(a,f);return i&&(h.layoutContext={layouts:await i(o,t)}),b(m,h,n,e.wrapWithLayouts,!!(t.streaming&&!e.wrapWithLayouts),a)}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),u(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(f,{async fetch(i){let a=new URL(i.url,`http://localhost`).pathname;if(a.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let o=a.replace(/^\//,``)||`index`,s=await x(r,`${t.pagesDir}/${o}.tsx`,`${t.pagesDir}/${o}/index.tsx`,!1);return s?v(s,createRenderContextFromRequest(i),n,e.wrapWithLayouts):createErrorResponse(d(`Page not found: ${a}`),n)}catch(e){return console.error(`[Nitro CatchAll .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
|
|
@@ -178,6 +178,11 @@ export interface PageModule {
|
|
|
178
178
|
getStaticProps?: () => Promise<Record<string, unknown>>;
|
|
179
179
|
/** Optional getServerSideProps for SSR */
|
|
180
180
|
getServerSideProps?: (context: NitroRenderContext) => Promise<Record<string, unknown>>;
|
|
181
|
+
/**
|
|
182
|
+
* When `false`, client navigation falls back to a full load for this route.
|
|
183
|
+
* MDX can set the same flag via frontmatter.
|
|
184
|
+
*/
|
|
185
|
+
clientNavigation?: boolean;
|
|
181
186
|
}
|
|
182
187
|
/**
|
|
183
188
|
* Page metadata for SEO and configuration
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Pages worker post-build patches.
|
|
3
|
+
*
|
|
4
|
+
* 1. Rolldown emits `createRequire(import.meta.url)` — undefined on workerd.
|
|
5
|
+
* 2. Lit SSR reads `document` at module eval; the inlined DOM stub runs too late
|
|
6
|
+
* because ESM static imports of `@lit-labs/ssr` evaluate first.
|
|
7
|
+
* 3. Nitro's `_routes.json` includes `/*`, so prerendered HTML hits the Function
|
|
8
|
+
* instead of ASSETS — amplify any SSR boot failure into a site-wide 500.
|
|
9
|
+
*/
|
|
10
|
+
/** Rewrite createRequire(import.meta.url) so workerd can boot the worker. */
|
|
11
|
+
export declare function patchCloudflareCreateRequire(workerDir: string): number;
|
|
12
|
+
export interface CloudflareWranglerPatch {
|
|
13
|
+
compatibilityDate: string;
|
|
14
|
+
compatibilityFlags: string[];
|
|
15
|
+
}
|
|
16
|
+
/** Merge compatibility_date / flags into Nitro's emitted wrangler.json. */
|
|
17
|
+
export declare function patchCloudflareWranglerJson(workerDir: string, patch: CloudflareWranglerPatch): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Install a separate DOM stub module and import it before Lit / the SSR graph.
|
|
20
|
+
* Inlined stubs cannot run before static `import` of `@lit-labs/ssr`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function injectCloudflareDomStub(workerDir: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Prefer ASSETS for prerendered HTML: only send dynamic routes to the Function.
|
|
25
|
+
* Nitro defaults to `include: ["/*"]`, which forces every page through SSR.
|
|
26
|
+
*/
|
|
27
|
+
export declare function patchCloudflareRoutesForStaticHtml(cwd: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Apply Cloudflare worker fixes under `dist/_worker.js` (file or directory).
|
|
30
|
+
* Returns false when no Cloudflare worker output is present.
|
|
31
|
+
*/
|
|
32
|
+
export declare function patchCloudflareWorkerOutput(cwd: string, wrangler?: CloudflareWranglerPatch): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as e,readdirSync as t,readFileSync as n,statSync as r,writeFileSync as i}from"node:fs";import{join as a}from"node:path";import{ssrDomStubFileSource as o}from"../vite-plugin/ssr-dom-shim-module.js";const s=/createRequire\(\s*import\.meta\.url\s*\)/g,c=`_dom_stub.mjs`;function l(n,r=[]){if(!e(n))return r;for(let e of t(n,{withFileTypes:!0})){let t=a(n,e.name);if(e.isDirectory()){l(t,r);continue}e.isFile()&&/\.(?:m?js)$/.test(e.name)&&r.push(t)}return r}export function patchCloudflareCreateRequire(e){let t=0;for(let r of l(e)){let e=n(r,`utf-8`);if(!s.test(e))continue;s.lastIndex=0;let a=e.replaceAll(s,`createRequire(import.meta.url || "file:///")`);a!==e&&(i(r,a),t+=1)}return t}export function patchCloudflareWranglerJson(t,o){let s=a(t,`wrangler.json`);if(!e(s)||!r(s).isFile())return!1;let c=JSON.parse(n(s,`utf-8`));c.compatibility_date=o.compatibilityDate;let l=new Set(c.compatibility_flags??[]);for(let e of o.compatibilityFlags)l.add(e);return c.compatibility_flags=[...l],i(s,`${JSON.stringify(c,null,2)}\n`),!0}function u(t,a){if(!e(t)||!r(t).isFile())return!1;let o=n(t,`utf-8`),s=`import "${a}";\n`;return o.startsWith(s)||o.includes(`import "${a}"`)?!1:(i(t,`${s}${o}`),!0)}export function injectCloudflareDomStub(n){i(a(n,c),`${o().trim()}\n`);let r=!1;r=u(a(n,`index.js`),`./${c}`)||r,r=u(a(n,`_ssr`,`ssr.mjs`),`../${c}`)||r;let s=a(n,`_libs`,`@lit-labs`);if(e(s))for(let e of t(s))e.startsWith(`ssr`)&&(r=u(a(s,e),`../../${c}`)||r);return r}export function patchCloudflareRoutesForStaticHtml(t){let n=a(t,`dist`,`_routes.json`);if(!e(n))return!1;let r=new Set([`/api/*`,`/_server-islands/*`,`/_actions/*`]);e(a(t,`dist`,`_worker.js`,`_routes`,`api`))&&r.add(`/api/*`);let o={version:1,include:[...r],exclude:[`/`,`/assets/*`,`/islands/*`,`/pagefind/*`,`/*.html`,`/*.svg`,`/*.ico`,`/*.txt`,`/*.xml`,`/*.css`,`/*.js`]};return i(n,`${JSON.stringify(o,null,2)}\n`),!0}export function patchCloudflareWorkerOutput(t,n={compatibilityDate:`2026-09-04`,compatibilityFlags:[`nodejs_compat`,`enable_nodejs_fs_module`]}){let i=a(t,`dist`,`_worker.js`);if(!e(i))return!1;let o=r(i).isDirectory()?i:null;if(!o)return!1;let s=patchCloudflareCreateRequire(o),c=patchCloudflareWranglerJson(o,n),l=injectCloudflareDomStub(o),u=patchCloudflareRoutesForStaticHtml(t);return s>0&&console.log(`[cloudflare] Patched createRequire(import.meta.url) in ${s} worker file(s)`),c&&console.log(`[cloudflare] Synced wrangler.json compatibility_date=${n.compatibilityDate}`),l&&console.log(`[cloudflare] Injected _dom_stub.mjs before Lit / SSR imports`),u&&console.log(`[cloudflare] Limited _routes.json include to dynamic SSR paths`),s>0||c||l||u}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fetchHandlerWrapperSource(importSpec: string, port: number): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import{ssrDomShimModuleSource as e}from"../vite-plugin/ssr-dom-shim-module.js";export function fetchHandlerWrapperSource(t,n){return`
|
|
2
|
+
import 'urlpattern-polyfill';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
|
|
5
|
+
${e()}
|
|
6
|
+
|
|
7
|
+
const mod = await import(${JSON.stringify(t)});
|
|
8
|
+
const handler = mod.handler || mod.default;
|
|
9
|
+
if (!handler) { console.error('No handler found in ${t}'); process.exit(1); }
|
|
10
|
+
|
|
11
|
+
const cfCtx = {
|
|
12
|
+
waitUntil(promise) {
|
|
13
|
+
if (promise && typeof promise.then === 'function') {
|
|
14
|
+
promise.catch(() => {});
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
passThroughOnException() {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const server = createServer(async (req, res) => {
|
|
21
|
+
const url = new URL(req.url, 'http://localhost:${n}');
|
|
22
|
+
try {
|
|
23
|
+
const hdrs = new Headers();
|
|
24
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
25
|
+
if (v) hdrs.set(k, Array.isArray(v) ? v.join(', ') : v);
|
|
26
|
+
}
|
|
27
|
+
const request = new Request(url.href, { method: req.method, headers: hdrs });
|
|
28
|
+
|
|
29
|
+
let response;
|
|
30
|
+
if (handler.fetch) {
|
|
31
|
+
response = await handler.fetch(request, {}, cfCtx);
|
|
32
|
+
} else if (typeof handler === 'function') {
|
|
33
|
+
response = await handler(request);
|
|
34
|
+
} else {
|
|
35
|
+
res.writeHead(500);
|
|
36
|
+
res.end('Unknown handler format');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const body = await response.text();
|
|
41
|
+
const resHdrs = {};
|
|
42
|
+
if (response.headers && typeof response.headers.forEach === 'function') {
|
|
43
|
+
response.headers.forEach((v, k) => { resHdrs[k] = v; });
|
|
44
|
+
} else if (response.headers && typeof response.headers === 'object') {
|
|
45
|
+
Object.assign(resHdrs, response.headers);
|
|
46
|
+
}
|
|
47
|
+
res.writeHead(response.status || 200, resHdrs);
|
|
48
|
+
res.end(body);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('Prerender request error:', err);
|
|
51
|
+
res.writeHead(500);
|
|
52
|
+
res.end('Internal Server Error');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
server.listen(${n}, '127.0.0.1', () => console.log('Listening on http://127.0.0.1:${n}'));
|
|
56
|
+
`}
|
|
@@ -41,5 +41,21 @@ export interface PostBuildOptions {
|
|
|
41
41
|
prerender?: PrerenderConfig | false;
|
|
42
42
|
/** Port for prerender server (default: 13172) */
|
|
43
43
|
prerenderPort?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Keep the shared `entry-client` script (and its modulepreload) in
|
|
46
|
+
* prerendered HTML. Required when `clientRouter` is enabled so client
|
|
47
|
+
* navigation can rescan islands after a swap.
|
|
48
|
+
*/
|
|
49
|
+
clientRouter?: boolean;
|
|
44
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Ensure the SSR client-assets `css` array lists global stylesheets (`index-*.css`
|
|
53
|
+
* / `entry-client-*.css`). Prerender strips `entry-client` CSS for per-island
|
|
54
|
+
* hydration, so without `index-*.css` in this array pages ship with no styles.
|
|
55
|
+
*
|
|
56
|
+
* Exported for unit tests.
|
|
57
|
+
*/
|
|
58
|
+
export declare function patchSSRBundleCSS(ssrBundlePath: string, distDir: string, cwd: string): boolean;
|
|
59
|
+
/** True when the built server lives under Netlify's functions tree. */
|
|
60
|
+
export declare function isNetlifyHandler(serverEntryPath: string): boolean;
|
|
45
61
|
export declare function runPostBuild(options?: PostBuildOptions): Promise<void>;
|
|
@@ -1,52 +1,7 @@
|
|
|
1
|
-
import{copyFileSync as e,cpSync as t,existsSync as n,mkdirSync as r,readdirSync as i,readFileSync as a,
|
|
1
|
+
import{copyFileSync as e,cpSync as t,existsSync as n,mkdirSync as r,readdirSync as i,readFileSync as a,statSync as o,unlinkSync as s,writeFileSync as c}from"node:fs";import{basename as l,dirname as u,join as d,relative as f}from"node:path";import{patchCloudflareWorkerOutput as p}from"./cloudflare-worker-patch.js";import{fetchHandlerWrapperSource as m}from"./fetch-handler-wrapper.js";function h(e,t,r=[]){if(!n(e))return r;for(let n of i(e,{withFileTypes:!0})){let i=d(e,n.name);n.isDirectory()?h(i,t,r):t(n.name)&&r.push(i)}return r}function g(e){try{return o(e).isFile()}catch{return!1}}function _(e){let t=d(e,`dist`,`_worker.js`);if(g(t))return t;if(!n(t))return null;for(let e of[`index.js`,`index.mjs`]){let n=d(t,e);if(g(n))return n}return null}function v(e){let t=d(e,`dist`,`_worker.js`,`_ssr`,`ssr.mjs`);return g(t)?t:null}function y(e){if(!n(e))return!1;let t=a(e,`utf-8`);return t.length<500&&!t.includes(`data-framework`)&&!t.includes(`avalon`)}function b(e){for(let t of[`dist/index.html`,`.netlify/functions-internal/server/public/index.html`,`.output/public/index.html`]){let r=d(e,t);y(r)?(s(r),console.log(`[cleanup] Removed stale Vite template ${t}`)):n(r)&&console.log(`[cleanup] Kept ${t} (not a Vite template)`)}}function x(e){return e.replaceAll(/\/\*[\s\S]*?\*\//g,``).replaceAll(/\s+/g,` `).replaceAll(/\s*([{}:;,])\s*/g,`$1`).trim()}export function patchSSRBundleCSS(e,t,r){if(!g(e))return!1;let i=[d(t,`assets`),d(r,`.netlify`,`functions-internal`,`server`,`public`,`assets`),d(r,`.output`,`public`,`assets`)].find(e=>n(e));if(!i)return!1;let o=h(i,e=>e.endsWith(`.css`)).filter(e=>{let t=(e.split(`/`).pop()||``).toLowerCase();return t.includes(`_isolated-island-entry`)?!1:!!(t.startsWith(`entry-client`)||t.startsWith(`index-`))}).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);if(!o.some(e=>/\/index-[^/]+\.css$/.test(e))){let e=h(i,e=>e.endsWith(`.css`)).filter(e=>(e.split(`/`).pop()||``).toLowerCase().startsWith(`ssr-index`)).map(e=>`/assets${e.substring(i.length).replaceAll(`\\`,`/`)}`);o.push(...e)}console.log(`[patch] Found ${o.length} CSS files in ${i}`);let s=a(e,`utf-8`),l=[{re:/"css"\s*:\s*\[((?:\s*\{\s*"href"\s*:\s*"[^"]+"\s*\}\s*,?)*)\]/,hrefRe:/"href"\s*:\s*"([^"]+)"/g,formatEntry:e=>`{ "href": "${e}" }`,rebuild:(e,t)=>{let n=e.trim().replace(/,\s*$/,``);return`"css": [${[...n?[n]:[],...t].join(`, `)}]`}},{re:/css:\[(\{href:`[^`]+`\}(?:,\{href:`[^`]+`\})*)\]/,hrefRe:/href:`([^`]+)`/g,formatEntry:e=>`{href:\`${e}\`}`,rebuild:(e,t)=>`css:[${e},${t.join(`,`)}]`},{re:/css:\[(\{href:"[^"]+"\}(?:,\{href:"[^"]+"\})*)\]/,hrefRe:/href:"([^"]+)"/g,formatEntry:e=>`{href:"${e}"}`,rebuild:(e,t)=>`css:[${e},${t.join(`,`)}]`}];for(let{re:t,hrefRe:n,formatEntry:r,rebuild:i}of l){let a=t.exec(s);if(!a)continue;let l=new Set([...a[1].matchAll(n)].map(e=>e[1])),u=o.filter(e=>!l.has(e));if(u.length===0)return console.log(`[patch] All CSS already included`),!0;let d=u.map(r);return s=s.replace(a[0],i(a[1],d)),c(e,s),console.log(`[patch] ✅ Added ${u.length} CSS files to SSR bundle`),!0}return console.warn(`[patch] Could not find CSS array in SSR bundle`),!1}function C(e,t){let o=d(t,`assets`);if(n(o)&&i(o).some(e=>e.startsWith(`entry-client`)&&e.endsWith(`.css`))){console.log(`[ssr-css] Skipped — client build already includes entry-client CSS`);return}let s=[d(e,`node_modules`,`.nitro`,`vite`,`services`,`ssr`,`assets`)];for(let o of s){if(!n(o))continue;let s=i(o).filter(e=>e.endsWith(`.css`));if(s.length===0)continue;let l=[d(t,`assets`),d(e,`.output`,`public`,`assets`),d(e,`.netlify`,`functions-internal`,`server`,`public`,`assets`)];for(let e of l){r(e,{recursive:!0});for(let t of s){let n=a(d(o,t),`utf-8`);n=x(n),c(d(e,`ssr-${t}`),n)}}let u=`ssr-${s[0]}`,f=d(l.find(e=>n(e))||l[0],u),p=n(f)?a(f).length:0;console.log(`[ssr-css] Copied SSR CSS → /assets/${u} (${p} bytes, minified)`);let m=[d(e,`.output`,`server`,`index.mjs`),d(e,`.netlify`,`functions-internal`,`server`,`main.mjs`)];for(let e of m){if(!n(e))continue;let t=a(e,`utf-8`),r=`/assets/${u}`;if(t.includes(r))continue;let i=/"\/assets\/[^"]+\.css":\{type:`text\/css[^}]+\}/.exec(t);if(i){let n=new Date().toISOString(),a=`,"${r}":{type:\`text/css; charset=utf-8\`,etag:\`${`"${p.toString(16)}-ssr"`}\`,mtime:\`${n}\`,size:${p},path:\`../public/assets/${u}\`}`;t=t.replace(i[0],i[0]+a),c(e,t),console.log(`[ssr-css] ✅ Patched asset manifest in ${e}`)}}return}console.log(`[ssr-css] No SSR CSS files found`)}async function w(e,t){let r=d(t,`islands`);if(!n(r)){console.log(`[islands] No islands directory found, skipping isolation`);return}let i=h(r,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(!i.some(e=>{let t=a(e,`utf-8`);return t.includes(`from"../`)||t.includes(`from'../`)})){console.log(`[islands] All islands are self-contained`);return}try{let{buildIsolatedIslands:r}=await import(`./isolated-island-builder.js`),o=new Map;for(let r of i){let i=f(t,r).replaceAll(`\\`,`/`).replace(/^islands\//,``).replace(/\.js$/,``),s=a(r,`utf-8`),c=`preact`;i.includes(`.solid`)?c=`solid`:i.includes(`.vue`)||i.endsWith(`.vue`)?c=`vue`:i.includes(`.svelte`)||i.endsWith(`.svelte`)?c=`svelte`:i.includes(`.lit`)?c=`lit`:i.includes(`.qwik`)?c=`qwik`:i.includes(`.react`)?c=`react`:i.includes(`.preact`)&&(c=`preact`);let l=/from["']((?:\/|\.\/)[^"']+\.(tsx|ts|jsx|js|vue|svelte))["']/i.exec(s),u;if(l&&!l[1].includes(`/assets/`)&&!l[1].startsWith(`../`))u=l[1];else{let t=i;u=[`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.js`,`${t}`].find(t=>n(d(e,t)))??`${t}.tsx`}u.startsWith(`src/islands/`)&&(u=u.slice(12)),u.startsWith(`/`)||(u=`/${u}`),o.set(i,{filePath:u,bundleKey:i,framework:c})}await r(e,t,o,[],{})}catch{console.warn(`[islands] Isolated rebuild failed, falling back to inline-islands`);try{let{inlineIslandChunks:e}=await import(`./inline-islands.js`);await e(t,{verbose:!0})}catch(e){console.error(`[islands] Inline fallback also failed:`,e)}}}function T(e,r){let i=d(r,`islands`);if(!n(i))return;let a=[d(e,`.output`,`public`),d(e,`.netlify`,`functions-internal`,`server`,`public`)];for(let e of a)n(e)&&t(i,d(e,`islands`),{recursive:!0,force:!0});console.log(`[islands] Synced isolated islands to output directories`)}function E(e){let t=d(e,`islands`);if(!n(t))return;let r=h(t,e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));if(r.length===0)return;let i=[];for(let t of r){let n=t.substring(e.length).replaceAll(`\\`,`/`);i.push(`${n} ${n} 200`)}let o=d(e,`_redirects`),s=n(o)?a(o,`utf-8`):``;c(o,s?`${s}\n${i.join(`
|
|
2
2
|
`)}`:i.join(`
|
|
3
|
-
`)),console.log(`[redirects] Generated ${i.length} island redirect(s)`)}function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const mod = await import('./main.mjs');
|
|
9
|
-
const handler = mod.handler || mod.default;
|
|
10
|
-
if (!handler) { console.error('No handler found in main.mjs'); process.exit(1); }
|
|
11
|
-
|
|
12
|
-
const server = createServer(async (req, res) => {
|
|
13
|
-
const url = new URL(req.url, 'http://localhost:${t}');
|
|
14
|
-
try {
|
|
15
|
-
const hdrs = new Headers();
|
|
16
|
-
for (const [k, v] of Object.entries(req.headers)) {
|
|
17
|
-
if (v) hdrs.set(k, Array.isArray(v) ? v.join(', ') : v);
|
|
18
|
-
}
|
|
19
|
-
const request = new Request(url.href, { method: req.method, headers: hdrs });
|
|
20
|
-
|
|
21
|
-
let response;
|
|
22
|
-
if (handler.fetch) {
|
|
23
|
-
response = await handler.fetch(request);
|
|
24
|
-
} else if (typeof handler === 'function') {
|
|
25
|
-
response = await handler(request);
|
|
26
|
-
} else {
|
|
27
|
-
res.writeHead(500);
|
|
28
|
-
res.end('Unknown handler format');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const body = await response.text();
|
|
33
|
-
const resHdrs = {};
|
|
34
|
-
if (response.headers && typeof response.headers.forEach === 'function') {
|
|
35
|
-
response.headers.forEach((v, k) => { resHdrs[k] = v; });
|
|
36
|
-
} else if (response.headers && typeof response.headers === 'object') {
|
|
37
|
-
Object.assign(resHdrs, response.headers);
|
|
38
|
-
}
|
|
39
|
-
res.writeHead(response.status || 200, resHdrs);
|
|
40
|
-
res.end(body);
|
|
41
|
-
} catch (err) {
|
|
42
|
-
console.error('Prerender request error:', err);
|
|
43
|
-
res.writeHead(500);
|
|
44
|
-
res.end('Internal Server Error');
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
server.listen(${t}, '127.0.0.1', () => console.log('Listening on http://127.0.0.1:${t}'));
|
|
48
|
-
`),r}function w(e){let t=/<a\s+[^>]*href=["']([^"']+)["'][^>]*>/gi,n=[],r;for(r=t.exec(e);r!==null;r=t.exec(e)){let e=r[1],t=e.indexOf(`#`);t!==-1&&(e=e.substring(0,t));let i=e.indexOf(`?`);i!==-1&&(e=e.substring(0,i)),e&&e.startsWith(`/`)&&!e.startsWith(`//`)&&!e.includes(`.`)&&n.push(e)}return n}async function T(t,i,d,f){let p=[l(t,`.netlify`,`functions-internal`,`server`,`server.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`server.mjs`),l(t,`.output`,`server`,`index.mjs`)].find(e=>n(e));if(!p){console.log(`[prerender] No server entry found, skipping prerender`);return}let m=[l(t,`.output`,`public`),i].find(e=>n(e));if(!m){console.log(`[prerender] No output directory found, skipping prerender`);return}let h={routes:d.routes??[`/`],crawlLinks:d.crawlLinks??!0,ignore:d.ignore??[],failOnError:d.failOnError??!1,concurrency:d.concurrency??4,autoSubfolderIndex:d.autoSubfolderIndex??!0,retry:d.retry??3,retryDelay:d.retryDelay??500},g=`http://localhost:${f}`,_=S(p),v=p;if(_){let e=l(c(p),`main.mjs`);if(!n(e)){console.error(`[prerender] Netlify handler detected but main.mjs not found`);return}v=C(e,f,t),console.log(`[prerender] Netlify handler detected — using wrapper`)}{let e=[p,l(c(p),`main.mjs`)].filter(e=>n(e));for(let n of e){let e=a(n,`utf-8`),r=/"\/[^"]*\.html":\{[^}]+\},?/g,i=e.length;e=e.replaceAll(r,``),i!==e.length&&(s(n,e),console.log(`[prerender] Patched ${u(t,n)}: removed HTML asset entries`))}}console.log(`[prerender] Starting prerender with server: ${u(t,v)}`);let{spawn:y}=await import(`node:child_process`),b;try{b=y(`node`,[v],{env:{...process.env,PORT:String(f),NITRO_PORT:String(f),HOST:`127.0.0.1`,NITRO_HOST:`127.0.0.1`,NODE_ENV:`production`},stdio:[`ignore`,`pipe`,`pipe`]}),b.stdout?.on(`data`,e=>{let t=e.toString().trim();t&&console.log(`[prerender:server] ${t}`)}),b.stderr?.on(`data`,e=>{let t=e.toString().trim();t&&console.error(`[prerender:server:err] ${t}`)});let e=Date.now(),t=!1;for(;Date.now()-e<15e3;){try{let e=await fetch(`${g}/`);if(e.ok||e.status<500){t=!0;break}}catch{}await new Promise(e=>setTimeout(e,200))}if(!t)throw Error(`Server did not become ready within 15s`);console.log(`[prerender] Server ready at ${g}`)}catch(e){b?.kill(`SIGKILL`),console.error(`[prerender] Failed to start server:`,e.message);return}let x=new Set,T=[...h.routes],E=[],D=[];function O(e){for(let t of h.ignore)if(typeof t==`string`&&e===t||typeof t==`string`&&t.endsWith(`/**`)&&e.startsWith(t.slice(0,-2)))return!0;return!1}try{for(;T.length>0;){let e=T.splice(0,h.concurrency);await Promise.all(e.map(async e=>{let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;if(x.has(t)||(x.add(t),O(t)))return;let n=null;for(let e=1;e<=h.retry;e++)try{let e=await fetch(`${g}${t}`);n={html:await e.text(),status:e.status};break}catch{e<h.retry&&await new Promise(e=>setTimeout(e,h.retryDelay))}if(!n){D.push({route:t,error:`Failed after ${h.retry} attempts`});return}if(n.status>=400){D.push({route:t,error:`Returned ${n.status}`});return}let i=h.autoSubfolderIndex?l(t,`index.html`):`${t}.html`,a=l(m,i);r(c(a),{recursive:!0});let o=n.html.replace(`<!DOCTYPE html>`,`<!DOCTYPE html>
|
|
49
|
-
<!-- SSG: prerendered at build time -->`).replaceAll(/<link rel="stylesheet" href="\/assets\/[^"]*\.css">\n?/g,e=>e.includes(`_isolated-island-entry`)?``:e);if(o=o.replaceAll(/<script type="module" src="\/assets\/entry-client[^"]*\.js"><\/script>\n?/g,``),o=o.replaceAll(/<link rel="stylesheet" href="\/assets\/entry-client[^"]*\.css">\n?/g,``),o=o.replaceAll(/<link rel="modulepreload" href="\/assets\/entry-client[^"]*\.js">\n?/g,``),s(a,o),E.push(t),console.log(`[prerender] ✅ ${t} → ${i}`),h.crawlLinks)for(let e of w(n.html)){let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;!x.has(t)&&!O(t)&&T.push(t)}}))}}finally{console.log(`[prerender] Shutting down server...`),b?.kill(`SIGKILL`)}if(console.log(`[prerender] Done: ${E.length} page(s) prerendered`+(D.length>0?`, ${D.length} error(s)`:``)),_){let e=l(c(p),`_prerender-server.mjs`);n(e)&&(o(e),console.log(`[prerender] Cleaned up wrapper script`))}if(E.length>0){let a=[i,l(t,`.netlify`,`functions-internal`,`server`,`public`),l(t,`.netlify`,`v1`,`functions`,`server`,`public`)].filter(e=>e!==m&&n(c(e)));for(let t of a)for(let i of E){let a=l(i,`index.html`),o=l(m,a),s=l(t,a);n(o)&&(r(c(s),{recursive:!0}),e(o,s))}a.length>0&&console.log(`[prerender] Copied prerendered HTML to ${a.length} additional output dir(s)`)}}function E(e,t){let r={},i=l(t,`island-deps.json`);if(n(i))try{r=JSON.parse(a(i,`utf-8`))}catch{}let o=l(e,`.output`,`public`);if(Object.keys(r).length===0&&n(o)){let e=d(l(o,`islands`),e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));for(let t of e){let e=`/${t.substring(o.length+1).replaceAll(`\\`,`/`)}`,n=a(t,`utf-8`),i=/\bfrom\s*["']([^"']+)["']|import\s*["']([^"']+)["']/g,s=[],u;for(u=i.exec(n);u!==null;u=i.exec(n)){let t=u[1]||u[2];if(t&&(t.includes(`/assets/`)||t.startsWith(`.`))){let n=(t.startsWith(`.`)?`/${l(c(e.slice(1)),t).replaceAll(`\\`,`/`).replace(/^\/+/,``)}`:t).split(`/`).filter(Boolean),r=[];for(let e of n)e===`..`?r.pop():e!==`.`&&r.push(e);s.push(`/${r.join(`/`)}`)}}s.length>0&&(r[e]=s)}}if(Object.keys(r).length===0)return;let u=[l(e,`.output`,`public`),t],f=0;for(let e of u){if(!n(e))continue;let t=d(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=new Set;for(let[e,i]of Object.entries(r))if(t.includes(e))for(let e of i)t.includes(`href="${e}"`)||n.add(e);if(n.size===0)continue;let i=Array.from(n).map(e=>`<link rel="modulepreload" href="${e}">`).join(`
|
|
50
|
-
`);t.includes(`</head>`)&&(t=t.replace(`</head>`,`${i}\n</head>`),s(e,t),f++)}}f>0&&console.log(`[modulepreload] ✅ Injected dependency preloads into ${f} HTML file(s)`)}const D=[/syntax-highlight/i,/hljs/i,/prism/i,/highlight\.js/i];function O(e,t){let r=[l(e,`.output`,`public`),t],i=A(r),o=0;for(let e of r){if(!n(e))continue;let t=d(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=t;t=j(t,i),t=k(t),t=M(t),t=N(t),t!==n&&(s(e,t),o++)}}o>0&&console.log(`[post-build] Optimized ${o} HTML file(s) — deferred non-critical CSS, added font preload hints`)}function k(e){if(!e.includes(`</head>`)||!e.includes(`<body`))return e;let t=e.indexOf(`</head>`),n=e.indexOf(`<body`);if(t===-1||n===-1||n<t)return e;let r=e.substring(n),i=new Set,a=[],o=``,s=0,c=0;for(;c<r.length;){if(r[c]===`<`){if(r.startsWith(`<template`,c)){s++;let e=r.indexOf(`>`,c);if(e===-1)break;o+=r.substring(c,e+1),c=e+1;continue}if(r.startsWith(`</template>`,c)){s=Math.max(0,s-1),o+=`</template>`,c+=11;continue}if(s===0&&r.startsWith(`<style`,c)){let e=r.indexOf(`</style>`,c);if(e===-1)break;let t=r.substring(c,e+8),n=/<style[^>]*>([\s\S]*?)<\/style>/.exec(t);if(n){let e=n[1].replaceAll(/\s+/g,` `).trim();e&&!i.has(e)&&(i.add(e),a.push(`<style>${e}</style>`))}c=e+8;continue}}o+=r[c],c++}return a.length===0?e:`${e.substring(0,t)}\n${a.join(`
|
|
51
|
-
`)}\n</head>${o}`}function A(e){let t=new Map;for(let r of e){let e=l(r,`assets`);if(n(e))for(let n of i(e))n.endsWith(`.css`)&&t.set(`/assets/${n}`,a(l(e,n),`utf-8`))}return t}function j(e,t){let n=/<link\s+rel="stylesheet"\s+href="(\/assets\/(?:ssr-)?index-[^"]+\.css)">/i.exec(e);if(!n)return e;let r=n[1],i=t.get(r);if(!i)return e;if(Buffer.byteLength(i,`utf-8`)<=14336){let t=`<style data-inlined-from="${r}">${i}</style>`;return e.replace(n[0],t)}let a=`<link rel="preload" href="${r}" as="style">`;return e.includes(`</title>`)&&!e.includes(`preload" href="${r}"`)&&(e=e.replace(`</title>`,`</title>\n${a}`)),e}function M(e){return e.replaceAll(/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,(e,t)=>{if(/\bmedia\s*=/i.test(t)||/data-critical/i.test(t))return e;let n=/href=["']([^"']+)["']/i.exec(t);if(!n)return e;let r=n[1],i=r.startsWith(`https://`)||r.startsWith(`http://`),a=!i&&D.some(e=>e.test(r));return!i&&!a?e:`<link ${t} media="print" onload="this.media='all'">\n<noscript><link ${t}></noscript>`})}function N(e){let t=/href=["'](https:\/\/fonts\.googleapis\.com\/css2[^"']+)["']/gi,n=new Set,r;for(r=t.exec(e);r!==null;r=t.exec(e))n.add(r[1]);if(n.size===0)return e;let i=[];for(let t of n){let n=t.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`<link\s+[^>]*rel=["']preload["'][^>]*href=["']${n}["'][^>]*>`,`i`).test(e)||i.push(`<link rel="preload" href="${t}" as="style">`)}if(i.length===0)return e;let a=i.join(`
|
|
52
|
-
`);return e.includes(`</title>`)?e.replace(`</title>`,`</title>\n${a}`):e}async function P(e){let{promisify:t}=await import(`node:util`),r=await import(`node:zlib`),i=t(r.brotliCompress),o=t(r.gzip),c=[l(e,`.output`,`public`),l(e,`.netlify`,`functions-internal`,`server`,`public`)],u=e=>e.endsWith(`.js`)||e.endsWith(`.css`)||e.endsWith(`.html`)||e.endsWith(`.svg`)||e.endsWith(`.txt`)||e.endsWith(`.xml`)||e.endsWith(`.json`),f=0;for(let e of c){if(!n(e))continue;let t=d(e,e=>u(e)&&!e.endsWith(`.map`));for(let e of t){let t=a(e);if(!(t.length<256))try{let[n,a]=await Promise.all([i(t,{params:{[r.constants.BROTLI_PARAM_QUALITY]:11}}),o(t,{level:9})]);s(`${e}.br`,n),s(`${e}.gz`,a),f++}catch{}}}f>0&&console.log(`[compress] ✅ Compressed ${f} public asset(s) (brotli + gzip)`)}function F(e){let t=[l(e,`.output`,`server`,`index.mjs`),l(e,`.netlify`,`functions-internal`,`server`,`index.mjs`)],r=l(e,`.output`,`public`);if(!n(r))return;let i=I(r);if(i.length===0)return;let a=0;for(let e of t)L(e,i,r)&&a++;a>0&&console.log(`[manifest] ✅ Registered ${i.length} prerendered HTML file(s) in Nitro asset manifest`)}function I(e){let t=d(e,e=>e===`index.html`||e===`index.html.br`||e===`index.html.gz`||e===`index.html.zst`),n=[],r=new Date().toISOString();for(let i of t){let t=`/${i.substring(e.length+1).replaceAll(`\\`,`/`)}`,o=a(i).length,s=`"${o.toString(16)}-prerender"`,c=``;t.endsWith(`.br`)?c=`br`:t.endsWith(`.gz`)?c=`gzip`:t.endsWith(`.zst`)&&(c=`zstd`);let l=`"${t}":{type:\`text/html; charset=utf-8\``;c&&(l+=`,encoding:\`${c}\``),l+=`,etag:\`${s}\`,mtime:\`${r}\`,size:${o},path:\`../public${t}\`}`,n.push(l)}return n}function L(e,t,r){if(!n(e))return!1;let i=a(e,`utf-8`),o=/("\/favicon\.ico":\{[^}]+\})/.exec(i);if(!o)return!1;let c=t.filter(e=>{let t=/^"([^"]+)"/.exec(e);return t&&!i.includes(`"${t[1]}":{`)});c.length>0&&(i=i.replace(o[0],`${o[0]},${c.join(`,`)}`));let l=R(r),u="u===`/favicon.ico`&&d.unshift({data:e})";for(let e of l){if(i.includes(`route:\`${e}\``))continue;let t=`[{name:\`headers\`,route:\`${e}\`,handler:x,options:{"Cache-Control":\`public, max-age=0, must-revalidate\`}}]`;i=i.replace(u,`${u};u===\`${e}\`&&d.unshift({data:${t}})`)}return s(e,i),c.length>0||l.length>0}function R(e){let t=[];if(!n(e))return t;for(let r of i(e,{withFileTypes:!0}))!r.isDirectory()&&r.name===`index.html`?t.push(`/`):r.isDirectory()&&n(l(e,r.name,`index.html`))&&t.push(`/${r.name}`);return t}export async function runPostBuild(e={}){let t=e.cwd??process.cwd(),r=l(t,`dist`),i=e.prerenderPort??13172;p(t),g(t,r);for(let e of[l(t,`.netlify`,`functions-internal`,`server`,`_ssr`,`ssr.mjs`),l(t,`.netlify`,`v1`,`functions`,`server`,`_ssr`,`ssr.mjs`),l(t,`.output`,`server`,`_ssr`,`ssr.mjs`)])n(e)&&(console.log(`[patch] Patching ${e}`),h(e,r,t));await _(t,r),v(t,r),y(r),b(t,r),x(t),e.prerender!==!1&&await T(t,r,e.prerender??{},i),E(t,r),O(t,r),await P(t),F(t),console.log(`[post-build] ✅ Complete`)}
|
|
3
|
+
`)),console.log(`[redirects] Generated ${i.length} island redirect(s)`)}function D(e,a){let o=d(e,`node_modules`,`@useavalon`);if(!n(o))return;let s=[d(a,`adapters`),d(e,`.output`,`public`,`adapters`),d(e,`.netlify`,`functions-internal`,`server`,`public`,`adapters`)];for(let e of s){if(!n(u(e)))continue;let a=i(o).map(e=>d(o,e,`client`)).filter(e=>n(e));for(let n of a){let i=d(e,n.split(`/`).at(-2)||``);r(i,{recursive:!0}),t(n,i,{recursive:!0,force:!0})}}console.log(`[adapters] Copied framework adapters to output directories`)}function O(e){let i=d(e,`.netlify`,`functions-internal`),a=d(e,`.netlify`,`v1`,`functions`);n(i)&&(r(a,{recursive:!0}),t(i,a,{recursive:!0,force:!0}),console.log(`[netlify] Copied server function to v1 API paths`))}export function isNetlifyHandler(e){return e.split(/[/\\]/).includes(`.netlify`)}function k(e,t){let n=d(u(e),`_prerender-server.mjs`);return c(n,m(`./${l(e)}`,t)),n}function A(e){let t=/<a\s+[^>]*href=["']([^"']+)["'][^>]*>/gi,n=[],r;for(r=t.exec(e);r!==null;r=t.exec(e)){let e=r[1],t=e.indexOf(`#`);t!==-1&&(e=e.substring(0,t));let i=e.indexOf(`?`);i!==-1&&(e=e.substring(0,i)),e&&e.startsWith(`/`)&&!e.startsWith(`//`)&&!e.includes(`.`)&&n.push(e)}return n}async function j(t,i,o,l,p=!1){let m=_(t),h=[...m?[m]:[],d(t,`.netlify`,`functions-internal`,`server`,`server.mjs`),d(t,`.netlify`,`v1`,`functions`,`server`,`server.mjs`),d(t,`.output`,`server`,`index.mjs`)].find(e=>n(e));if(!h){console.log(`[prerender] No server entry found, skipping prerender`);return}let v=[d(t,`.output`,`public`),i].find(e=>n(e));if(!v){console.log(`[prerender] No output directory found, skipping prerender`);return}let y={routes:o.routes??[`/`],crawlLinks:o.crawlLinks??!0,ignore:o.ignore??[],failOnError:o.failOnError??!1,concurrency:o.concurrency??4,autoSubfolderIndex:o.autoSubfolderIndex??!0,retry:o.retry??3,retryDelay:o.retryDelay??500},b=`http://localhost:${l}`,x=m!==null&&h===m,S=h;if(x)S=k(h,l),console.log(`[prerender] Cloudflare worker detected — using wrapper`);else if(isNetlifyHandler(h)){let e=d(u(h),`main.mjs`);if(!n(e)){console.error(`[prerender] Netlify handler detected but main.mjs not found`);return}S=k(e,l),console.log(`[prerender] Netlify handler detected — using wrapper`)}{let e=[h,d(u(h),`main.mjs`)].filter(e=>g(e));for(let n of e){let e=a(n,`utf-8`),r=/"\/[^"]*\.html":\{[^}]+\},?/g,i=e.length;e=e.replaceAll(r,``),i!==e.length&&(c(n,e),console.log(`[prerender] Patched ${f(t,n)}: removed HTML asset entries`))}}console.log(`[prerender] Starting prerender with server: ${f(t,S)}`);let{spawn:C}=await import(`node:child_process`),w;try{w=C(`node`,[S],{env:{...process.env,PORT:String(l),NITRO_PORT:String(l),HOST:`127.0.0.1`,NITRO_HOST:`127.0.0.1`,NODE_ENV:`production`},stdio:[`ignore`,`pipe`,`pipe`]}),w.stdout?.on(`data`,e=>{let t=e.toString().trim();t&&console.log(`[prerender:server] ${t}`)}),w.stderr?.on(`data`,e=>{let t=e.toString().trim();t&&console.error(`[prerender:server:err] ${t}`)});let e=Date.now(),t=!1;for(;Date.now()-e<15e3;){try{let e=await fetch(`${b}/`);if(e.ok||e.status<500){t=!0;break}}catch{}await new Promise(e=>setTimeout(e,200))}if(!t)throw Error(`Server did not become ready within 15s`);console.log(`[prerender] Server ready at ${b}`)}catch(e){w?.kill(`SIGKILL`),console.error(`[prerender] Failed to start server:`,e.message);return}let T=new Set,E=[...y.routes],D=[],O=[];function j(e){for(let t of y.ignore)if(typeof t==`string`&&e===t||typeof t==`string`&&t.endsWith(`/**`)&&e.startsWith(t.slice(0,-2)))return!0;return!1}try{for(;E.length>0;){let e=E.splice(0,y.concurrency);await Promise.all(e.map(async e=>{let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;if(T.has(t)||(T.add(t),j(t)))return;let n=null;for(let e=1;e<=y.retry;e++)try{let e=await fetch(`${b}${t}`);n={html:await e.text(),status:e.status};break}catch{e<y.retry&&await new Promise(e=>setTimeout(e,y.retryDelay))}if(!n){O.push({route:t,error:`Failed after ${y.retry} attempts`});return}if(n.status>=400){O.push({route:t,error:`Returned ${n.status}`});return}let i=y.autoSubfolderIndex?d(t,`index.html`):`${t}.html`,a=d(v,i);r(u(a),{recursive:!0});let o=n.html.replace(`<!DOCTYPE html>`,`<!DOCTYPE html>
|
|
4
|
+
<!-- SSG: prerendered at build time -->`).replaceAll(/<link rel="stylesheet" href="\/assets\/[^"]*\.css">\n?/g,e=>e.includes(`_isolated-island-entry`)?``:e);if(p||(o=o.replaceAll(/<script type="module" src="\/assets\/entry-client[^"]*\.js"><\/script>\n?/g,``)),(/\/assets\/(?:ssr-)?index-[^"]+\.css/.test(o)||/<style data-inlined-from="\/assets\/(?:ssr-)?index-/.test(o))&&(o=o.replaceAll(/<link rel="stylesheet" href="\/assets\/entry-client[^"]*\.css">\n?/g,``)),p||(o=o.replaceAll(/<link rel="modulepreload" href="\/assets\/entry-client[^"]*\.js">\n?/g,``)),c(a,o),D.push(t),console.log(`[prerender] ✅ ${t} → ${i}`),y.crawlLinks)for(let e of A(n.html)){let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;!T.has(t)&&!j(t)&&E.push(t)}}))}}finally{console.log(`[prerender] Shutting down server...`),w?.kill(`SIGKILL`)}if(console.log(`[prerender] Done: ${D.length} page(s) prerendered`+(O.length>0?`, ${O.length} error(s)`:``)),x||isNetlifyHandler(h)){let e=d(u(h),`_prerender-server.mjs`);n(e)&&(s(e),console.log(`[prerender] Cleaned up wrapper script`))}if(D.length>0){let a=[i,d(t,`.netlify`,`functions-internal`,`server`,`public`),d(t,`.netlify`,`v1`,`functions`,`server`,`public`)].filter(e=>e!==v&&n(u(e)));for(let t of a)for(let i of D){let a=d(i,`index.html`),o=d(v,a),s=d(t,a);n(o)&&(r(u(s),{recursive:!0}),e(o,s))}a.length>0&&console.log(`[prerender] Copied prerendered HTML to ${a.length} additional output dir(s)`)}}function M(e,t){let r={},i=d(t,`island-deps.json`);if(n(i))try{r=JSON.parse(a(i,`utf-8`))}catch{}let o=d(e,`.output`,`public`);if(Object.keys(r).length===0&&n(o)){let e=h(d(o,`islands`),e=>e.endsWith(`.js`)&&!e.endsWith(`.js.map`));for(let t of e){let e=`/${t.substring(o.length+1).replaceAll(`\\`,`/`)}`,n=a(t,`utf-8`),i=/\bfrom\s*["']([^"']+)["']|import\s*["']([^"']+)["']/g,s=[],c;for(c=i.exec(n);c!==null;c=i.exec(n)){let t=c[1]||c[2];if(t&&(t.includes(`/assets/`)||t.startsWith(`.`))){let n=(t.startsWith(`.`)?`/${d(u(e.slice(1)),t).replaceAll(`\\`,`/`).replace(/^\/+/,``)}`:t).split(`/`).filter(Boolean),r=[];for(let e of n)e===`..`?r.pop():e!==`.`&&r.push(e);s.push(`/${r.join(`/`)}`)}}s.length>0&&(r[e]=s)}}if(Object.keys(r).length===0)return;let s=[d(e,`.output`,`public`),t],l=0;for(let e of s){if(!n(e))continue;let t=h(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=new Set;for(let[e,i]of Object.entries(r))if(t.includes(e))for(let e of i)t.includes(`href="${e}"`)||n.add(e);if(n.size===0)continue;let i=Array.from(n).map(e=>`<link rel="modulepreload" href="${e}">`).join(`
|
|
5
|
+
`);t.includes(`</head>`)&&(t=t.replace(`</head>`,`${i}\n</head>`),c(e,t),l++)}}l>0&&console.log(`[modulepreload] ✅ Injected dependency preloads into ${l} HTML file(s)`)}const N=[];function P(e,t){let r=[d(e,`.output`,`public`),t],i=I(r),o=0;for(let e of r){if(!n(e))continue;let t=h(e,e=>e===`index.html`);for(let e of t){let t=a(e,`utf-8`),n=t;t=L(t,i),t=F(t),t=R(t),t=z(t),t!==n&&(c(e,t),o++)}}o>0&&console.log(`[post-build] Optimized ${o} HTML file(s) — deferred non-critical CSS, added font preload hints`)}function F(e){if(!e.includes(`</head>`)||!e.includes(`<body`))return e;let t=e.indexOf(`</head>`),n=e.indexOf(`<body`);if(t===-1||n===-1||n<t)return e;let r=e.substring(n),i=new Set,a=[],o=``,s=0,c=0;for(;c<r.length;){if(r[c]===`<`){if(r.startsWith(`<template`,c)){s++;let e=r.indexOf(`>`,c);if(e===-1)break;o+=r.substring(c,e+1),c=e+1;continue}if(r.startsWith(`</template>`,c)){s=Math.max(0,s-1),o+=`</template>`,c+=11;continue}if(s===0&&r.startsWith(`<style`,c)){let e=r.indexOf(`</style>`,c);if(e===-1)break;let t=r.substring(c,e+8),n=/<style[^>]*>([\s\S]*?)<\/style>/.exec(t);if(n){let e=n[1].replaceAll(/\s+/g,` `).trim();e&&!i.has(e)&&(i.add(e),a.push(`<style>${e}</style>`))}c=e+8;continue}}o+=r[c],c++}return a.length===0?e:`${e.substring(0,t)}\n${a.join(`
|
|
6
|
+
`)}\n</head>${o}`}function I(e){let t=new Map;for(let r of e){let e=d(r,`assets`);if(n(e))for(let n of i(e))n.endsWith(`.css`)&&t.set(`/assets/${n}`,a(d(e,n),`utf-8`))}return t}function L(e,t){let n=/<link\s+rel="stylesheet"\s+href="(\/assets\/(?:ssr-)?index-[^"]+\.css)">/i.exec(e);if(!n)return e;let r=n[1],i=t.get(r);if(!i)return e;if(Buffer.byteLength(i,`utf-8`)<=14336){let t=`<style data-inlined-from="${r}">${i}</style>`;return e.replace(n[0],t)}let a=`<link rel="preload" href="${r}" as="style">`;return e.includes(`</title>`)&&!e.includes(`preload" href="${r}"`)&&(e=e.replace(`</title>`,`</title>\n${a}`)),e}function R(e){return e.replaceAll(/<link\s+([^>]*rel=["']stylesheet["'][^>]*)>/gi,(t,n,r)=>{if(e.slice(Math.max(0,r-32),r).toLowerCase().includes(`<noscript`))return t;let i=n.replace(/\s*\/\s*$/,``).trim();if(/\bmedia\s*=/i.test(i)||/data-critical/i.test(i))return t;let a=/href=["']([^"']+)["']/i.exec(i);if(!a)return t;let o=a[1],s=o.startsWith(`https://`)||o.startsWith(`http://`),c=!s&&N.some(e=>e.test(o));return!s&&!c?t:`<link ${i} media="print" onload="this.media='all'">\n<noscript><link ${i}></noscript>`})}function z(e){let t=/href=["'](https:\/\/fonts\.googleapis\.com\/css2[^"']+)["']/gi,n=new Set,r;for(r=t.exec(e);r!==null;r=t.exec(e))n.add(r[1]);if(n.size===0)return e;let i=[];for(let t of n){let n=t.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);new RegExp(String.raw`<link\s+[^>]*rel=["']preload["'][^>]*href=["']${n}["'][^>]*>`,`i`).test(e)||i.push(`<link rel="preload" href="${t}" as="style">`)}if(i.length===0)return e;let a=i.join(`
|
|
7
|
+
`);return e.includes(`</title>`)?e.replace(`</title>`,`</title>\n${a}`):e}async function B(e){let{promisify:t}=await import(`node:util`),r=await import(`node:zlib`),i=t(r.brotliCompress),o=t(r.gzip),s=[d(e,`.output`,`public`),d(e,`.netlify`,`functions-internal`,`server`,`public`)],l=e=>e.endsWith(`.js`)||e.endsWith(`.css`)||e.endsWith(`.html`)||e.endsWith(`.svg`)||e.endsWith(`.txt`)||e.endsWith(`.xml`)||e.endsWith(`.json`),u=0;for(let e of s){if(!n(e))continue;let t=h(e,e=>l(e)&&!e.endsWith(`.map`));for(let e of t){let t=a(e);if(!(t.length<256))try{let[n,a]=await Promise.all([i(t,{params:{[r.constants.BROTLI_PARAM_QUALITY]:11}}),o(t,{level:9})]);c(`${e}.br`,n),c(`${e}.gz`,a),u++}catch{}}}u>0&&console.log(`[compress] ✅ Compressed ${u} public asset(s) (brotli + gzip)`)}function V(e){let t=[d(e,`.output`,`server`,`index.mjs`),d(e,`.netlify`,`functions-internal`,`server`,`index.mjs`)],r=d(e,`.output`,`public`);if(!n(r))return;let i=H(r);if(i.length===0)return;let a=0;for(let e of t)U(e,i,r)&&a++;a>0&&console.log(`[manifest] ✅ Registered ${i.length} prerendered HTML file(s) in Nitro asset manifest`)}function H(e){let t=h(e,e=>e===`index.html`||e===`index.html.br`||e===`index.html.gz`||e===`index.html.zst`),n=[],r=new Date().toISOString();for(let i of t){let t=`/${i.substring(e.length+1).replaceAll(`\\`,`/`)}`,o=a(i).length,s=`"${o.toString(16)}-prerender"`,c=``;t.endsWith(`.br`)?c=`br`:t.endsWith(`.gz`)?c=`gzip`:t.endsWith(`.zst`)&&(c=`zstd`);let l=`"${t}":{type:\`text/html; charset=utf-8\``;c&&(l+=`,encoding:\`${c}\``),l+=`,etag:\`${s}\`,mtime:\`${r}\`,size:${o},path:\`../public${t}\`}`,n.push(l)}return n}function U(e,t,r){if(!n(e))return!1;let i=a(e,`utf-8`),o=/("\/favicon\.ico":\{[^}]+\})/.exec(i);if(!o)return!1;let s=t.filter(e=>{let t=/^"([^"]+)"/.exec(e);return t&&!i.includes(`"${t[1]}":{`)});s.length>0&&(i=i.replace(o[0],`${o[0]},${s.join(`,`)}`));let l=W(r),u="u===`/favicon.ico`&&d.unshift({data:e})";for(let e of l){if(i.includes(`route:\`${e}\``))continue;let t=`[{name:\`headers\`,route:\`${e}\`,handler:x,options:{"Cache-Control":\`public, max-age=0, must-revalidate\`}}]`;i=i.replace(u,`${u};u===\`${e}\`&&d.unshift({data:${t}})`)}return c(e,i),s.length>0||l.length>0}function W(e){let t=[];if(!n(e))return t;for(let r of i(e,{withFileTypes:!0}))!r.isDirectory()&&r.name===`index.html`?t.push(`/`):r.isDirectory()&&n(d(e,r.name,`index.html`))&&t.push(`/${r.name}`);return t}export async function runPostBuild(e={}){let t=e.cwd??process.cwd(),n=d(t,`dist`),r=e.prerenderPort??13172;b(t),C(t,n);for(let e of[d(t,`.netlify`,`functions-internal`,`server`,`_ssr`,`ssr.mjs`),d(t,`.netlify`,`v1`,`functions`,`server`,`_ssr`,`ssr.mjs`),d(t,`.output`,`server`,`_ssr`,`ssr.mjs`),v(t)])e&&g(e)&&(console.log(`[patch] Patching ${e}`),patchSSRBundleCSS(e,n,t));await w(t,n),T(t,n),E(n),D(t,n),O(t),p(t),e.prerender!==!1&&await j(t,n,e.prerender??{},r,e.clientRouter),M(t,n),P(t,n),await B(t),V(t),console.log(`[post-build] ✅ Complete`)}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{existsSync as e,statSync as t}from"node:fs";import{resolve as n}from"node:path";export const DEFAULT_TREESHAKE_CONFIG={annotations:!0,moduleSideEffects:!0,propertyReadSideEffects:!1,unknownGlobalSideEffects:!1,manualPureFunctions:[]};export function mergeTreeshakeConfig(e,t){return{annotations:t.annotations??e.annotations,moduleSideEffects:t.moduleSideEffects??e.moduleSideEffects,propertyReadSideEffects:t.propertyReadSideEffects??e.propertyReadSideEffects,unknownGlobalSideEffects:t.unknownGlobalSideEffects??e.unknownGlobalSideEffects,manualPureFunctions:[...e.manualPureFunctions,...t.manualPureFunctions??[]]}}const a={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`};export function generateWrapperCode(e,t){let n=JSON.stringify(e);if(t===`qwik`)return[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
|
-
`);let r=a[t],i=[`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`];return r&&i.push(`export { hydrate as __hydrateIsland } from ${JSON.stringify(r)};`),i.join(`
|
|
3
|
-
`)}export function generateIntegrationLoaderForFramework(e){let t=a[e];return t?`export { hydrate } from ${JSON.stringify(t)};`:`export {};`}async function c(e,t){let n=[],{resolve:r}=await import(`node:path`),{existsSync:i}=await import(`node:fs`),{pathToFileURL:a}=await import(`node:url`),o={solid:{pkg:`vite-plugin-solid`,esmEntry:`dist/esm/index.mjs`},vue:{pkg:`@vitejs/plugin-vue`,esmEntry:`dist/index.mjs`},svelte:{pkg:`@sveltejs/vite-plugin-svelte`,esmEntry:`src/index.js`}}[e];if(!o)return n;try{let s=null,c=t;for(let e=0;e<8;e++){let e=r(c,`node_modules`,o.pkg,o.esmEntry);if(i(e)){s=e;break}for(let e of[`solid`,`vue`,`svelte`,`preact`,`react`,`lit`]){let t=r(c,`packages`,`integrations`,e,`node_modules`,o.pkg,o.esmEntry);if(i(t)){s=t;break}}if(s)break;c=r(c,`..`)}if(!s)throw Error(`Cannot find package '${o.pkg}'`);let l=await import(a(s).href);switch(e){case`solid`:n.push((l.default??l)({ssr:!1,hot:!1}));break;case`vue`:n.push((l.default??l)());break;case`svelte`:n.push((l.svelte??l.default)());break}}catch(t){console.warn(` ⚠ Could not load ${e} plugin: ${t instanceof Error?t.message:t}`)}return n}async function l(e){let{createRequire:t}=await import(`node:module`),{existsSync:n}=await import(`node:fs`),r=t(`${e}/package.json`),i=e=>{try{let t=r.resolve(e);return t.endsWith(`.js`)&&n(t.replace(/\.js$/,`.mjs`))?t.replace(/\.js$/,`.mjs`):t}catch{return null}},a={preact:i(`preact`),"preact/hooks":i(`preact/hooks`),"preact/compat":i(`preact/compat`),"preact/compat/client":i(`preact/compat/client`),"preact/compat/server":i(`preact/compat/server`),"preact/jsx-runtime":i(`preact/jsx-runtime`),react:i(`preact/compat`),"react/jsx-runtime":i(`preact/jsx-runtime`),"react/jsx-dev-runtime":i(`preact/jsx-runtime`),"react-dom":i(`preact/compat`),"react-dom/client":i(`preact/compat/client`),"react-dom/server":i(`preact/compat/server`)};return{name:`avalon:isolated-preact-compat`,enforce:`pre`,resolveId(e){return a[e]??null}}}export async function buildIsolatedIslands(a,u,d,f,p,m){if(d.size===0)return[];let{build:h}=await import(`vite`),g=new Set([`qwik`,`lit`,`preact`,`react`]),_=[...d.values()].filter(e=>!g.has(e.framework));console.log(`🏝️ Building ${_.length} island(s) in isolation (${d.size-_.length} skipped)...`);let v=performance.now(),y=m?.treeshake?mergeTreeshakeConfig(DEFAULT_TREESHAKE_CONFIG,m.treeshake):DEFAULT_TREESHAKE_CONFIG,b=[];for(let[,r]of d){let{filePath:i,bundleKey:d,framework:m}=r,g=`islands/${d}.js`;if(m===`qwik`||m===`lit`||m===`preact`||m===`react`){b.push({island:g,success:!0,skipped:!0});continue}let _=generateWrapperCode(i,m),v=`\0isolated-island-entry`,x=`\0virtual:avalon/integration-loader`;try{let r=await c(m,a),i=await l(a),o=performance.now(),S=await h({configFile:!1,root:a,logLevel:`silent`,plugins:[{name:`avalon:isolated-island-virtual`,resolveId(e){return e===v?e:e===`virtual:avalon/integration-loader`||e===x?x:null},load(e){return e===v?_:e===x?generateIntegrationLoaderForFramework(m):null}},i,...r],build:{write:!0,outDir:n(a,u),emptyOutDir:!1,minify:`oxc`,target:`es2022`,rollupOptions:{input:v,output:{format:`es`,entryFileNames:g},preserveEntrySignatures:`exports-only`,treeshake:y}},resolve:{alias:f},define:{...p,__DEV__:`false`,__PROD__:`true`,"process.env.NODE_ENV":`"production"`}}),C=performance.now()-o,w=n(a,u,g),T=e(w)?t(w).size:0,E=[],D=Array.isArray(S)?S:[S];for(let e of D)if(e&&`output`in e)for(let t of e.output)t.type===`chunk`&&t.fileName!==g&&E.push(`/${t.fileName}`);b.push({island:g,success:!0,size:T,elapsedMs:C,deps:E}),console.log(` ✅ ${d} (${m}): ${(T/1024).toFixed(1)} KiB`),C>2e3&&console.warn(` ⚠ ${d} (${m}): build took ${C.toFixed(0)}ms (>2s)`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${d} (${m}): ${t}`),b.push({island:g,success:!1,error:t})}}let x=((performance.now()-v)/1e3).toFixed(1),S=b.filter(e=>e.success&&!e.skipped).length,C=b.filter(e=>e.skipped).length,w=b.filter(e=>!e.success).length,T=`🏝️ Done in ${x}s: ${S} built`;C&&(T+=`, ${C} skipped`),w&&(T+=`, ${w} failed`),console.log(T);let E={};for(let e of b)e.success&&e.deps&&e.deps.length>0&&(E[`/${e.island}`]=e.deps);if(Object.keys(E).length>0){let e=n(a,u,`island-deps.json`),{writeFileSync:t}=await import(`node:fs`);t(e,JSON.stringify(E))}return b}
|
|
2
|
+
`);let r=a[t],i=[`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`];return r&&i.push(`export { hydrate as __hydrateIsland, mount as __mountIsland, unmount as __unmountIsland } from ${JSON.stringify(r)};`),i.join(`
|
|
3
|
+
`)}export function generateIntegrationLoaderForFramework(e){let t=a[e];return t?`export { hydrate, mount } from ${JSON.stringify(t)};`:`export {};`}async function c(e,t){let n=[],{resolve:r}=await import(`node:path`),{existsSync:i}=await import(`node:fs`),{pathToFileURL:a}=await import(`node:url`),o={solid:{pkg:`vite-plugin-solid`,esmEntry:`dist/esm/index.mjs`},vue:{pkg:`@vitejs/plugin-vue`,esmEntry:`dist/index.mjs`},svelte:{pkg:`@sveltejs/vite-plugin-svelte`,esmEntry:`src/index.js`}}[e];if(!o)return n;try{let s=null,c=t;for(let e=0;e<8;e++){let e=r(c,`node_modules`,o.pkg,o.esmEntry);if(i(e)){s=e;break}for(let e of[`solid`,`vue`,`svelte`,`preact`,`react`,`lit`]){let t=r(c,`packages`,`integrations`,e,`node_modules`,o.pkg,o.esmEntry);if(i(t)){s=t;break}}if(s)break;c=r(c,`..`)}if(!s)throw Error(`Cannot find package '${o.pkg}'`);let l=await import(a(s).href);switch(e){case`solid`:n.push((l.default??l)({ssr:!1,hot:!1}));break;case`vue`:n.push((l.default??l)());break;case`svelte`:n.push((l.svelte??l.default)());break}}catch(t){console.warn(` ⚠ Could not load ${e} plugin: ${t instanceof Error?t.message:t}`)}return n}async function l(e){let{createRequire:t}=await import(`node:module`),{existsSync:n}=await import(`node:fs`),r=t(`${e}/package.json`),i=e=>{try{let t=r.resolve(e);return t.endsWith(`.js`)&&n(t.replace(/\.js$/,`.mjs`))?t.replace(/\.js$/,`.mjs`):t}catch{return null}},a={preact:i(`preact`),"preact/hooks":i(`preact/hooks`),"preact/compat":i(`preact/compat`),"preact/compat/client":i(`preact/compat/client`),"preact/compat/server":i(`preact/compat/server`),"preact/jsx-runtime":i(`preact/jsx-runtime`),react:i(`preact/compat`),"react/jsx-runtime":i(`preact/jsx-runtime`),"react/jsx-dev-runtime":i(`preact/jsx-runtime`),"react-dom":i(`preact/compat`),"react-dom/client":i(`preact/compat/client`),"react-dom/server":i(`preact/compat/server`)};return{name:`avalon:isolated-preact-compat`,enforce:`pre`,resolveId(e){return a[e]??null}}}export async function buildIsolatedIslands(a,u,d,f,p,m){if(d.size===0)return[];let{build:h}=await import(`vite`),g=new Set([`qwik`,`lit`,`preact`,`react`]),_=[...d.values()].filter(e=>!g.has(e.framework));console.log(`🏝️ Building ${_.length} island(s) in isolation (${d.size-_.length} skipped)...`);let v=performance.now(),y=m?.treeshake?mergeTreeshakeConfig(DEFAULT_TREESHAKE_CONFIG,m.treeshake):DEFAULT_TREESHAKE_CONFIG,b=[];for(let[,r]of d){let{filePath:i,bundleKey:d,framework:m}=r,g=`islands/${d}.js`;if(m===`qwik`||m===`lit`||m===`preact`||m===`react`){b.push({island:g,success:!0,skipped:!0});continue}let _=generateWrapperCode(i,m),v=`\0isolated-island-entry`,x=`\0virtual:avalon/integration-loader`;try{let r=await c(m,a),i=await l(a),o=performance.now(),S=await h({configFile:!1,root:a,logLevel:`silent`,plugins:[{name:`avalon:isolated-island-virtual`,resolveId(e){return e===v?e:e===`virtual:avalon/integration-loader`||e===x?x:null},load(e){return e===v?_:e===x?generateIntegrationLoaderForFramework(m):null}},i,...r],build:{write:!0,outDir:n(a,u),emptyOutDir:!1,minify:`oxc`,target:`es2022`,rollupOptions:{input:v,output:{format:`es`,entryFileNames:g},preserveEntrySignatures:`exports-only`,treeshake:y}},resolve:{alias:f},define:{...p,__DEV__:`false`,__PROD__:`true`,"process.env.NODE_ENV":`"production"`}}),C=performance.now()-o,w=n(a,u,g),T=e(w)?t(w).size:0,E=[],D=Array.isArray(S)?S:[S];for(let e of D)if(e&&`output`in e)for(let t of e.output)t.type===`chunk`&&t.fileName!==g&&E.push(`/${t.fileName}`);b.push({island:g,success:!0,size:T,elapsedMs:C,deps:E}),console.log(` ✅ ${d} (${m}): ${(T/1024).toFixed(1)} KiB`),C>2e3&&console.warn(` ⚠ ${d} (${m}): build took ${C.toFixed(0)}ms (>2s)`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${d} (${m}): ${t}`),b.push({island:g,success:!1,error:t})}}let x=((performance.now()-v)/1e3).toFixed(1),S=b.filter(e=>e.success&&!e.skipped).length,C=b.filter(e=>e.skipped).length,w=b.filter(e=>!e.success).length,T=`🏝️ Done in ${x}s: ${S} built`;C&&(T+=`, ${C} skipped`),w&&(T+=`, ${w} failed`),console.log(T);let E={};for(let e of b)e.success&&e.deps&&e.deps.length>0&&(E[`/${e.island}`]=e.deps);if(Object.keys(E).length>0){let e=n(a,u,`island-deps.json`),{writeFileSync:t}=await import(`node:fs`);t(e,JSON.stringify(E))}return b}
|
|
@@ -18,6 +18,13 @@ import type { ViteDevServer } from "vite";
|
|
|
18
18
|
* @returns Array of CSS strings ready to be injected as <style> tags
|
|
19
19
|
*/
|
|
20
20
|
export declare function collectCssFromModuleGraph(server: ViteDevServer, moduleUrl: string): Promise<string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Extract raw CSS from Vite's transformed CSS module JS wrapper.
|
|
23
|
+
*
|
|
24
|
+
* Vite 8 writes `const __vite__css = ${JSON.stringify(css)}`. `?direct`
|
|
25
|
+
* skips that wrapper and returns hashed CSS as `result.code`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractCssFromTransformedModule(code: string): string | null;
|
|
21
28
|
/**
|
|
22
29
|
* Inject collected CSS into an HTML string before </head>.
|
|
23
30
|
*
|
|
@@ -26,3 +33,25 @@ export declare function collectCssFromModuleGraph(server: ViteDevServer, moduleU
|
|
|
26
33
|
* If no </head> is found, styles are prepended to the HTML.
|
|
27
34
|
*/
|
|
28
35
|
export declare function injectSsrCss(html: string, cssContents: string[]): string;
|
|
36
|
+
/**
|
|
37
|
+
* Sanitize CSS content for safe injection inside a <style> tag.
|
|
38
|
+
*
|
|
39
|
+
* The only way to break out of a <style> tag is with a closing </style> tag
|
|
40
|
+
* (case-insensitive, with optional whitespace). We neutralize this by escaping
|
|
41
|
+
* any occurrence of `</style` in the CSS content.
|
|
42
|
+
*
|
|
43
|
+
* This prevents XSS via:
|
|
44
|
+
* .foo {} </style><script>alert('xss')</script><style>
|
|
45
|
+
*
|
|
46
|
+
* The CSS source comes from Vite's module graph (local project files and
|
|
47
|
+
* npm dependencies), so this is defense-in-depth against compromised deps.
|
|
48
|
+
*/
|
|
49
|
+
export declare function sanitizeCssForStyleTag(css: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Vite strips `t=<13 digits>` from transform URLs (HMR timestamp). `v=`
|
|
52
|
+
* stays, so each read is a new module id and cannot reuse a stale
|
|
53
|
+
* `transformResult` for `?direct`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function directCssRequestUrl(href: string, bust?: number): string;
|
|
56
|
+
/** Transform a `/`-rooted project CSS href to hashed CSS text for SSR/HMR. */
|
|
57
|
+
export declare function readDirectCss(server: ViteDevServer, href: string): Promise<string>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export async function collectCssFromModuleGraph(e,r){let i=[],a=new Set,o=await e.moduleGraph.getModuleByUrl(r);if(!o)return i;let s=[o];for(;s.length>0;){let r=s.shift(),o=r.id??r.url;if(!a.has(o)){if(a.add(o),t(o)){let t=await n(e,r);t&&i.push(t)}for(let e of r.importedModules){let t=e.id??e.url;a.has(t)||s.push(e)}}}return i}function t(e){let t=e.split(`?`)[0];return t.endsWith(`.css`)||t.endsWith(`.scss`)||t.endsWith(`.sass`)||t.endsWith(`.less`)||t.endsWith(`.styl`)||t.endsWith(`.stylus`)}async function n(e,t){try{let n=t.url,i=await e.transformRequest(n+`?direct`);return i&&typeof i.code==`string`?
|
|
2
|
-
`).replaceAll(String.raw`\t`,` `).replaceAll(String.raw`\r`,`\r`).replaceAll(String.raw`\"`,`"`).replaceAll(`\\\\`,`\\`)}export function injectSsrCss(e,t){if(t.length===0)return e;let n=t.map(e=>`<style data-avalon-ssr-css>${
|
|
3
|
-
`);return e.includes(`</head>`)?e.replace(`</head>`,`${n}\n</head>`):e.includes(`<head>`)?e.replace(`<head>`,`<head>\n${n}`):n+e}function
|
|
1
|
+
export async function collectCssFromModuleGraph(e,r){let i=[],a=new Set,o=await e.moduleGraph.getModuleByUrl(r);if(!o)return i;let s=[o];for(;s.length>0;){let r=s.shift(),o=r.id??r.url;if(!a.has(o)){if(a.add(o),t(o)){let t=await n(e,r);t&&i.push(t)}for(let e of r.importedModules){let t=e.id??e.url;a.has(t)||s.push(e)}}}return i}function t(e){let t=e.split(`?`)[0];return t.endsWith(`.css`)||t.endsWith(`.scss`)||t.endsWith(`.sass`)||t.endsWith(`.less`)||t.endsWith(`.styl`)||t.endsWith(`.stylus`)}async function n(e,t){try{let n=t.url,i=await e.transformRequest(n+`?direct`);return i&&typeof i.code==`string`?extractCssFromTransformedModule(i.code):null}catch{return null}}export function extractCssFromTransformedModule(e){if(!e)return null;let t=/const\s+__vite__css\s*=\s*("(?:[^"\\]|\\.)*")/.exec(e);if(t?.[1])return a(t[1]);let n=/__vite_ssr_exports__\.default\s*=\s*("(?:[^"\\]|\\.)*")/.exec(e);if(n?.[1])return a(n[1]);let r=/export\s+default\s+("(?:[^"\\]|\\.)*")/.exec(e);return r?.[1]?a(r[1]):i(e)?e:null}function i(e){return!(e.includes(`__vite__updateStyle`)||e.includes(`import.meta.hot`)||e.includes(`export `)||e.includes(`import `))}function a(e){try{return JSON.parse(e)}catch{return o(e.slice(1,-1))}}function o(e){return e.replaceAll(String.raw`\n`,`
|
|
2
|
+
`).replaceAll(String.raw`\t`,` `).replaceAll(String.raw`\r`,`\r`).replaceAll(String.raw`\"`,`"`).replaceAll(`\\\\`,`\\`)}export function injectSsrCss(e,t){if(t.length===0)return e;let n=t.map(e=>`<style data-avalon-ssr-css>${sanitizeCssForStyleTag(e)}</style>`).join(`
|
|
3
|
+
`);return e.includes(`</head>`)?e.replace(`</head>`,`${n}\n</head>`):e.includes(`<head>`)?e.replace(`<head>`,`<head>\n${n}`):n+e}export function sanitizeCssForStyleTag(e){return e.replaceAll(/<\/style/gi,String.raw`<\/style`)}export function directCssRequestUrl(e,t=Date.now()){return`${e}?direct&v=${t}`}export async function readDirectCss(e,t){try{let n=await e.transformRequest(directCssRequestUrl(t));return!n||typeof n.code!=`string`?``:extractCssFromTransformedModule(n.code)??``}catch{return``}}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route → CSS href matching for Nitro SSR. Pure: the layouts virtual module
|
|
3
|
+
* imports this; a filesystem walker must not leak into the Nitro bundle.
|
|
4
|
+
*/
|
|
5
|
+
export interface DevCssRouteEntry {
|
|
6
|
+
pattern: string;
|
|
7
|
+
/** Page + matching layouts + global CSS. */
|
|
8
|
+
hrefs: string[];
|
|
9
|
+
/** Page + global CSS (used when the page skips layouts). */
|
|
10
|
+
pageHrefs: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface DevCssRouteTable {
|
|
13
|
+
routes: DevCssRouteEntry[];
|
|
14
|
+
/** Global CSS + root layout graph — used when no page matches. */
|
|
15
|
+
fallbackHrefs: string[];
|
|
16
|
+
globalHrefs: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function stripTrailingSlash(path: string): string;
|
|
19
|
+
/** Avalon page-pattern match (`:param`, `**`) used for CSS table lookup. */
|
|
20
|
+
export declare function matchPathPattern(pathname: string, pattern: string): boolean;
|
|
21
|
+
/** Layout prefix match: `/` is root-only; `/blog` does not match `/blogger`. */
|
|
22
|
+
export declare function layoutPrefixMatches(pathname: string, prefix: string): boolean;
|
|
23
|
+
export declare function selectDevCssHrefs(table: DevCssRouteTable, pathname: string, skipLayouts: boolean): string[];
|
|
24
|
+
/** Vite URL for `\0virtual:avalon/dev-css-hmr` — loaded as its own module graph entry. */
|
|
25
|
+
export declare const DEV_CSS_HMR_CLIENT_SRC = "/@id/__x00__virtual:avalon/dev-css-hmr";
|
|
26
|
+
/**
|
|
27
|
+
* Insert per-route `<link rel="stylesheet" href="…?direct">`. The browser
|
|
28
|
+
* fetches hashed CSS from Vite on every document load, so a reload cannot
|
|
29
|
+
* serve a baked snapshot. Also injects `/@vite/client` and the CSS HMR
|
|
30
|
+
* listener — Nitro HTML never runs `transformIndexHtml`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function appendDevCssLinks(html: string, hrefs: string[]): string;
|
|
33
|
+
/**
|
|
34
|
+
* Insert hashed CSS as `<style data-avalon-css>` so HMR can update
|
|
35
|
+
* `textContent` in place. `</style` is neutralized so the tag cannot close early.
|
|
36
|
+
*/
|
|
37
|
+
export declare function appendDevStyleTags(html: string, sheets: Array<{
|
|
38
|
+
href: string;
|
|
39
|
+
css: string;
|
|
40
|
+
}>): string;
|