@useavalon/avalon 0.1.63 → 0.1.65
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/src/build/bundle-audit.d.ts +139 -0
- package/dist/src/build/bundle-audit.js +2 -0
- package/dist/src/build/island-client-bundler.d.ts +4 -3
- package/dist/src/build/island-client-bundler.js +4 -3
- package/dist/src/build/island-code-splitting.d.ts +131 -0
- package/dist/src/build/island-code-splitting.js +2 -0
- package/dist/src/islands/critical-css.d.ts +45 -0
- package/dist/src/islands/critical-css.js +3 -0
- package/dist/src/islands/island.d.ts +2 -0
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/modulepreload-collector.d.ts +57 -0
- package/dist/src/islands/modulepreload-collector.js +2 -0
- package/dist/src/islands/per-island-script.d.ts +41 -0
- package/dist/src/islands/per-island-script.js +1 -0
- package/dist/src/nitro/renderer.js +20 -20
- package/dist/src/post-build/inline-islands.d.ts +31 -0
- package/dist/src/post-build/inline-islands.js +2 -0
- package/dist/src/post-build/isolated-island-builder.d.ts +37 -0
- package/dist/src/post-build/isolated-island-builder.js +4 -0
- package/dist/src/vite-plugin/nitro-integration.js +2 -2
- package/dist/src/vite-plugin/plugin.d.ts +7 -5
- package/dist/src/vite-plugin/plugin.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle Audit Utility
|
|
3
|
+
*
|
|
4
|
+
* Lightweight utility for analyzing production build output:
|
|
5
|
+
* - Lists all output chunks with their sizes
|
|
6
|
+
* - Identifies which modules are included in each chunk
|
|
7
|
+
* - Flags unexpected large modules or duplicated code
|
|
8
|
+
* - Reports total JS size and per-island chunk sizes
|
|
9
|
+
* - Provides a Vite plugin hook for post-build reporting
|
|
10
|
+
*
|
|
11
|
+
* @module build/bundle-audit
|
|
12
|
+
*/
|
|
13
|
+
import type { Plugin } from "vite";
|
|
14
|
+
/** Represents a single chunk in the build output. */
|
|
15
|
+
export interface ChunkInfo {
|
|
16
|
+
fileName: string;
|
|
17
|
+
/** Raw code size in bytes. */
|
|
18
|
+
size: number;
|
|
19
|
+
/** Module IDs included in this chunk. */
|
|
20
|
+
moduleIds: string[];
|
|
21
|
+
/** Whether this is an island chunk. */
|
|
22
|
+
isIsland: boolean;
|
|
23
|
+
/** Whether this is an entry chunk. */
|
|
24
|
+
isEntry: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Summary of the full bundle audit. */
|
|
27
|
+
export interface BundleAuditReport {
|
|
28
|
+
/** All chunks in the build output. */
|
|
29
|
+
chunks: ChunkInfo[];
|
|
30
|
+
/** Total JS size in bytes (sum of all chunk code sizes). */
|
|
31
|
+
totalJsBytes: number;
|
|
32
|
+
/** Total island JS size in bytes. */
|
|
33
|
+
islandJsBytes: number;
|
|
34
|
+
/** Chunks that exceed the configured threshold. */
|
|
35
|
+
oversizedChunks: ChunkInfo[];
|
|
36
|
+
/** Module IDs that appear in more than one chunk. */
|
|
37
|
+
duplicatedModules: DuplicatedModule[];
|
|
38
|
+
}
|
|
39
|
+
/** A module that appears in multiple chunks. */
|
|
40
|
+
export interface DuplicatedModule {
|
|
41
|
+
moduleId: string;
|
|
42
|
+
/** File names of chunks containing this module. */
|
|
43
|
+
chunks: string[];
|
|
44
|
+
}
|
|
45
|
+
/** Options for the bundle audit Vite plugin. */
|
|
46
|
+
export interface BundleAuditOptions {
|
|
47
|
+
/** Maximum allowed size (in bytes) for any single island chunk. Default: 20480 (20 KiB). */
|
|
48
|
+
chunkSizeThreshold?: number;
|
|
49
|
+
/** Maximum allowed total JS size (in bytes). Default: 20480 (20 KiB). */
|
|
50
|
+
totalSizeThreshold?: number;
|
|
51
|
+
/** Whether to log the report to console. Default: true. */
|
|
52
|
+
logReport?: boolean;
|
|
53
|
+
/** Whether to emit warnings for oversized chunks. Default: true. */
|
|
54
|
+
warnOnOversized?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Default per-chunk size threshold: 20 KiB */
|
|
57
|
+
export declare const DEFAULT_CHUNK_THRESHOLD: number;
|
|
58
|
+
/** Default total JS size threshold: 20 KiB (from requirements) */
|
|
59
|
+
export declare const DEFAULT_TOTAL_THRESHOLD: number;
|
|
60
|
+
/** Shape of a bundle asset — loose enough to handle both Rollup and Rolldown. */
|
|
61
|
+
export type BundleAsset = Record<string, unknown> & {
|
|
62
|
+
type: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Extract chunk information from a Rollup/Vite output bundle.
|
|
66
|
+
* Compatible with both Rollup and Rolldown bundle formats.
|
|
67
|
+
*/
|
|
68
|
+
export declare function extractChunks(bundle: Record<string, BundleAsset>): ChunkInfo[];
|
|
69
|
+
/**
|
|
70
|
+
* Find modules that are duplicated across multiple chunks.
|
|
71
|
+
* Intentional duplication (e.g., framework runtime inlined per-island)
|
|
72
|
+
* is expected — this helps identify *unintentional* duplication.
|
|
73
|
+
*/
|
|
74
|
+
export declare function findDuplicatedModules(chunks: ChunkInfo[]): DuplicatedModule[];
|
|
75
|
+
/**
|
|
76
|
+
* Check if any chunks contain dev-only modules that shouldn't be in production.
|
|
77
|
+
* Returns the list of chunk/module pairs that are suspicious.
|
|
78
|
+
*/
|
|
79
|
+
export declare function findDevOnlyModules(chunks: ChunkInfo[]): Array<{
|
|
80
|
+
chunkFileName: string;
|
|
81
|
+
moduleId: string;
|
|
82
|
+
pattern: string;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Generate a full bundle audit report from extracted chunks.
|
|
86
|
+
*/
|
|
87
|
+
export declare function generateAuditReport(chunks: ChunkInfo[], options?: {
|
|
88
|
+
chunkSizeThreshold?: number;
|
|
89
|
+
totalSizeThreshold?: number;
|
|
90
|
+
}): BundleAuditReport;
|
|
91
|
+
/**
|
|
92
|
+
* Format a byte count as a human-readable KiB string.
|
|
93
|
+
*/
|
|
94
|
+
export declare function formatKiB(bytes: number): string;
|
|
95
|
+
/**
|
|
96
|
+
* Format the audit report as a human-readable string for console output.
|
|
97
|
+
*/
|
|
98
|
+
export declare function formatAuditReport(report: BundleAuditReport): string;
|
|
99
|
+
/** Result of auditing the Vite build configuration. */
|
|
100
|
+
export interface BuildConfigAudit {
|
|
101
|
+
/** Whether build.minify is set to a production minifier. */
|
|
102
|
+
hasMinify: boolean;
|
|
103
|
+
/** The minify setting value. */
|
|
104
|
+
minifyValue: string | boolean;
|
|
105
|
+
/** Whether build.target is set for modern browsers. */
|
|
106
|
+
hasModernTarget: boolean;
|
|
107
|
+
/** The build target value. */
|
|
108
|
+
targetValue: string | string[];
|
|
109
|
+
/** Whether process.env.NODE_ENV is defined for dead code elimination. */
|
|
110
|
+
hasNodeEnvDefine: boolean;
|
|
111
|
+
/** Whether treeshake config is present in rollupOptions. */
|
|
112
|
+
hasTreeshakeConfig: boolean;
|
|
113
|
+
/** List of issues found. */
|
|
114
|
+
issues: string[];
|
|
115
|
+
/** List of optimizations already in place. */
|
|
116
|
+
optimizations: string[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Audit a Vite resolved config for optimal production tree-shaking settings.
|
|
120
|
+
*/
|
|
121
|
+
export declare function auditBuildConfig(config: {
|
|
122
|
+
build?: {
|
|
123
|
+
minify?: string | boolean;
|
|
124
|
+
target?: string | string[];
|
|
125
|
+
rollupOptions?: {
|
|
126
|
+
treeshake?: unknown;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
define?: Record<string, unknown>;
|
|
130
|
+
}): BuildConfigAudit;
|
|
131
|
+
/**
|
|
132
|
+
* Creates a Vite plugin that reports bundle size after production builds.
|
|
133
|
+
*
|
|
134
|
+
* - Logs a summary of all chunks and their sizes
|
|
135
|
+
* - Warns if any island chunk exceeds the configured threshold
|
|
136
|
+
* - Reports total JS payload size
|
|
137
|
+
* - Audits the resolved Vite config for optimal tree-shaking settings
|
|
138
|
+
*/
|
|
139
|
+
export declare function bundleAuditPlugin(options?: BundleAuditOptions): Plugin;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const DEFAULT_CHUNK_THRESHOLD=20*1024;export const DEFAULT_TOTAL_THRESHOLD=20*1024;const n=[/\/dev\.js$/,/\/dev\.mjs$/,/__DEV__/,/hot-reload/,/hmr-coordinator/,/hmr-error-overlay/,/solid-refresh/];export function extractChunks(e){let t=[];for(let[n,r]of Object.entries(e)){if(r.type!==`chunk`)continue;let e=r.fileName??n,i=r.code??``,a=r.moduleIds??Object.keys(r.modules??{});t.push({fileName:e,size:Buffer.byteLength(i,`utf-8`),moduleIds:a,isIsland:e.startsWith(`islands/`)||e.includes(`/islands/`),isEntry:r.isEntry??!1})}return t.sort((e,t)=>t.size-e.size)}export function findDuplicatedModules(e){let t=new Map;for(let n of e)for(let e of n.moduleIds){if(e.startsWith(`\0`))continue;let r=t.get(e)??[];r.push(n.fileName),t.set(e,r)}let n=[];for(let[e,r]of t)r.length>1&&n.push({moduleId:e,chunks:r});return n.sort((e,t)=>t.chunks.length-e.chunks.length)}export function findDevOnlyModules(e){let t=[];for(let r of e)for(let e of r.moduleIds)for(let i of n)i.test(e)&&t.push({chunkFileName:r.fileName,moduleId:e,pattern:i.source});return t}export function generateAuditReport(t,n={}){let r=n.chunkSizeThreshold??DEFAULT_CHUNK_THRESHOLD;return{chunks:t,totalJsBytes:t.reduce((e,t)=>e+t.size,0),islandJsBytes:t.filter(e=>e.isIsland).reduce((e,t)=>e+t.size,0),oversizedChunks:t.filter(e=>e.isIsland&&e.size>r),duplicatedModules:findDuplicatedModules(t)}}export function formatKiB(e){return`${(e/1024).toFixed(2)} KiB`}function c(e){return e.isIsland?` [island]`:e.isEntry?` [entry]`:` `}function l(e,t){return e.length<=t?e:`...${e.slice(-(t-3))}`}export function formatAuditReport(e){let t=[`┌─────────────────────────────────────────────────────┐`,`│ Bundle Audit Report │`,`├─────────────────────────────────────────────────────┤`,`│ Total JS: ${formatKiB(e.totalJsBytes).padStart(12)} │`,`│ Island JS: ${formatKiB(e.islandJsBytes).padStart(12)} │`,`│ Chunks: ${String(e.chunks.length).padStart(12)} │`,`├─────────────────────────────────────────────────────┤`,`│ Chunks by size: │`];for(let n of e.chunks){let e=c(n),r=l(n.fileName,32);t.push(`│ ${e} ${r.padEnd(32)} ${formatKiB(n.size).padStart(10)} │`)}if(e.oversizedChunks.length>0){t.push(`├─────────────────────────────────────────────────────┤`),t.push(`│ Oversized island chunks: │`);for(let n of e.oversizedChunks)t.push(`│ ${n.fileName.padEnd(38)} ${formatKiB(n.size).padStart(10)} │`)}if(e.duplicatedModules.length>0){t.push(`├─────────────────────────────────────────────────────┤`),t.push(`│ Duplicated modules: ${e.duplicatedModules.length} │`);for(let n of e.duplicatedModules.slice(0,5)){let e=l(n.moduleId,40);t.push(`│ ${e.padEnd(40)} x${n.chunks.length} │`)}e.duplicatedModules.length>5&&t.push(`│ ... and ${e.duplicatedModules.length-5} more │`)}return t.push(`└─────────────────────────────────────────────────────┘`),t.join(`
|
|
2
|
+
`)}export function auditBuildConfig(e){let t=[],n=[],r=e.build?.minify??`esbuild`,i=r!==!1;i?n.push(`Minification enabled: ${r}`):t.push(`build.minify is disabled — production builds should be minified`);let a=e.build?.target??`modules`,o=a!==`es5`&&a!==`es3`;o?n.push(`Modern build target: ${JSON.stringify(a)}`):t.push(`build.target is "${a}" — consider "es2020" or higher for smaller output`);let s=e.define??{},c=`process.env.NODE_ENV`in s||`__DEV__`in s||`__PROD__`in s;c?n.push(`process.env.NODE_ENV / __DEV__ / __PROD__ defined for dead code elimination`):t.push(`No process.env.NODE_ENV define — dev-only code may leak into production`);let l=e.build?.rollupOptions?.treeshake!=null;return l?n.push(`Custom treeshake.moduleSideEffects configured`):t.push(`No custom treeshake config — solid-js may not be fully tree-shaken`),{hasMinify:i,minifyValue:r,hasModernTarget:o,targetValue:a,hasNodeEnvDefine:c,hasTreeshakeConfig:l,issues:t,optimizations:n}}export function bundleAuditPlugin(t={}){let{chunkSizeThreshold:n=DEFAULT_CHUNK_THRESHOLD,totalSizeThreshold:i=20480,logReport:c=!0,warnOnOversized:l=!0}=t,d=!1;return{name:`avalon:bundle-audit`,enforce:`post`,config(e,{command:t}){d=t===`build`},configResolved(e){if(d&&c){let t=e.build,n=t?.rollupOptions,r=auditBuildConfig({build:{minify:t?.minify,target:t?.target,rollupOptions:{treeshake:n?.treeshake}},define:e.define});if(r.issues.length>0)for(let e of r.issues)console.warn(`⚠️ [bundle-audit] ${e}`)}},generateBundle(e,t){if(!d)return;let u=extractChunks(t),f=generateAuditReport(u,{chunkSizeThreshold:n,totalSizeThreshold:i});if(c&&console.log(formatAuditReport(f)),l&&f.oversizedChunks.length>0)for(let e of f.oversizedChunks)this.warn(`Island chunk "${e.fileName}" is ${formatKiB(e.size)} (threshold: ${formatKiB(n)})`);l&&f.totalJsBytes>i&&this.warn(`Total JS size ${formatKiB(f.totalJsBytes)} exceeds threshold of ${formatKiB(i)}`);let p=findDevOnlyModules(u);if(p.length>0)for(let e of p)this.warn(`Dev-only module "${e.moduleId}" found in chunk "${e.chunkFileName}"`)}}}
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* post-build script (post-build.mjs), which patches the SSR bundle to
|
|
10
10
|
* include all CSS files from the client build output.
|
|
11
11
|
*/
|
|
12
|
-
import type { Plugin } from
|
|
13
|
-
import type { ResolvedAvalonConfig } from
|
|
12
|
+
import type { Plugin } from "vite";
|
|
13
|
+
import type { ResolvedAvalonConfig } from "../vite-plugin/types.ts";
|
|
14
|
+
import type { AvalonNitroConfig } from "../nitro/config.ts";
|
|
14
15
|
/**
|
|
15
16
|
* Creates a Vite plugin that emits island components as separate client chunks.
|
|
16
17
|
*/
|
|
17
|
-
export declare function islandClientBundlerPlugin(config: ResolvedAvalonConfig): Plugin;
|
|
18
|
+
export declare function islandClientBundlerPlugin(config: ResolvedAvalonConfig, nitroConfig?: AvalonNitroConfig): Plugin;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import{readFileSync as e,existsSync as t,readdirSync as n,statSync as r}from"node:fs";import{resolve as i,dirname as a,relative as o}from"node:path";export function islandClientBundlerPlugin(e){let
|
|
2
|
-
`):[`
|
|
3
|
-
`)
|
|
1
|
+
import{readFileSync as e,existsSync as t,readdirSync as n,statSync as r}from"node:fs";import{resolve as i,dirname as a,relative as o}from"node:path";export function islandClientBundlerPlugin(e,t){let n=process.cwd(),r=new Map,i=!1,a=c(e,n);for(let e of a)d(e,n,r);let o=`\0avalon-island-entry:`,s=!1,l=[],u={},f=`dist`;return{name:`avalon:island-client-bundler`,enforce:`pre`,configResolved(e){s=e.command===`serve`,i=e.command===`build`,l=e.resolve?.alias??[],u=e.define??{},f=e.build?.outDir??`dist`},resolveId(e){return e.startsWith(o)?e:null},load(e){if(!e.startsWith(o))return null;let t=e.slice(21),n=JSON.stringify(t);if(t.includes(`.qwik.`))return i?[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
|
+
`):[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`,`import __C from ${n};`,`export default __C;`,`if(typeof globalThis<"u")globalThis.__avalonIsland=__C;`].join(`
|
|
3
|
+
`);let r=t.includes(`.lit.`),a=t.includes(`.solid.`),s=t.includes(`.preact.`),c=t.includes(`.react.`),l=t.includes(`.vue.`),u=t.includes(`.svelte.`),d=[];if(r&&i&&d.push(`import "@useavalon/lit/client";`),d.push(`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`),i){let e={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,lit:`@useavalon/lit/client`},t=a?`solid`:s?`preact`:c?`react`:l?`vue`:u?`svelte`:r?`lit`:null;t&&e[t]?d.push(`export { hydrate as __hydrateIsland } from ${JSON.stringify(e[t])};`):d.push(`export { loadIntegrationModule } from "virtual:avalon/integration-loader";`)}return d.join(`
|
|
4
|
+
`)},async buildStart(){if(s)return;let t=this.environment;if(!(t&&t.name!==`client`)&&r.size>0){for(let[,e]of r)this.emitFile({type:`chunk`,id:o+e.filePath,fileName:`islands/${e.bundleKey}.js`,preserveSignature:`exports-only`});e.verbose&&console.log(`🏝️ Emitting ${r.size} island client bundles`)}},async closeBundle(){if(s||!i||r.size===0||globalThis.__avalonIslandsRebuilt)return;globalThis.__avalonIslandsRebuilt=!0;let{buildIsolatedIslands:e}=await import(`../post-build/isolated-island-builder.js`),t=new Map;for(let[e,n]of r){let r=n.filePath.includes(`.solid.`)?`solid`:n.filePath.includes(`.preact.`)?`preact`:n.filePath.includes(`.react.`)?`react`:n.filePath.includes(`.vue.`)||n.filePath.endsWith(`.vue`)?`vue`:n.filePath.includes(`.svelte.`)||n.filePath.endsWith(`.svelte`)?`svelte`:n.filePath.includes(`.lit.`)?`lit`:n.filePath.includes(`.qwik.`)?`qwik`:`preact`;t.set(e,{...n,framework:r})}await e(n,f,t,l,u)}}}function c(e,r){let a=[];if(e.pagesDir){let n=i(r,e.pagesDir);t(n)&&a.push(n)}if(e.layoutsDir){let n=i(r,e.layoutsDir);t(n)&&a.push(n)}if(e.modules){let o=i(r,e.modules.dir);if(t(o))try{for(let e of n(o,{withFileTypes:!0}))if(e.isDirectory())for(let n of[`pages`,`layouts`,`components`]){let r=i(o,e.name,n);t(r)&&a.push(r)}}catch{}}return a}const l=[`.qwik.`];function u(e){return l.some(t=>e.includes(t))}function d(t,r,a){let o;try{o=n(t,{withFileTypes:!0})}catch{return}for(let n of o){let o=i(t,n.name);if(n.isDirectory()){d(o,r,a);continue}if(/\.(tsx?|jsx?|mdx?)$/.test(n.name))try{let t=e(o,`utf-8`),n=t.includes(`island=`)||t.includes(`island `),i=/import\s+\w+\s+from\s+['"][^'"]*\.qwik\.[^'"]*['"]/m.test(t);if(!n&&!i)continue;f(t,o,r,a)}catch{}}}function f(e,t,n,r){let i=/<([A-Z]\w*)\s+[^>]*\bisland\b/g,a=new Set,s;for(;(s=i.exec(e))!==null;)a.add(s[1]);let c=/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,l=new Set,d=[];for(;(s=c.exec(e))!==null;)d.push([s[1],s[2]]),u(s[2])&&RegExp(`<`+s[1]+`[\\s/>]`).test(e)&&l.add(s[1]);if(!(a.size===0&&l.size===0))for(let[e,i]of d){if(!a.has(e)&&!l.has(e))continue;let s=p(i,t,n);if(!s)continue;let c=o(n,s).replaceAll(`\\`,`/`).replace(/\.(tsx?|jsx?)$/,``);r.has(s)||r.set(s,{filePath:s,bundleKey:c})}}function p(e,n,o){let s;if(e.startsWith(`@shared/`))s=i(o,`app/shared`,e.slice(8));else if(e.startsWith(`@modules/`))s=i(o,`app/modules`,e.slice(9));else if(e.startsWith(`@/`))s=i(o,`app`,e.slice(2));else if(e.startsWith(`.`))s=i(a(n),e);else return null;if(t(s)&&r(s).isFile())return s;for(let e of[`.tsx`,`.ts`,`.jsx`,`.js`,`.vue`,`.svelte`])if(t(s+e))return s+e;return null}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Island Code Splitting Plugin
|
|
3
|
+
*
|
|
4
|
+
* Configures Vite/Rollup to bundle the framework adapter + runtime INTO each
|
|
5
|
+
* island component chunk rather than splitting them into separate shared chunks.
|
|
6
|
+
*
|
|
7
|
+
* When per-island hydration mode is active, each island gets its own
|
|
8
|
+
* `<script type="module">` that imports the component + adapter. Without this
|
|
9
|
+
* plugin, Rollup would extract the `virtual:avalon/integration-loader` module
|
|
10
|
+
* and framework runtimes (e.g., solid-js/web) into separate shared chunks,
|
|
11
|
+
* adding extra network requests.
|
|
12
|
+
*
|
|
13
|
+
* This plugin works by:
|
|
14
|
+
* 1. Using `manualChunks` with `getModuleInfo` to walk the import graph and
|
|
15
|
+
* assign framework runtime modules to the same chunk as their importing
|
|
16
|
+
* island entry point
|
|
17
|
+
* 2. When a framework module is imported by multiple islands, each island
|
|
18
|
+
* gets its own copy (the runtime is duplicated intentionally)
|
|
19
|
+
* 3. The integration loader is similarly inlined into each island chunk
|
|
20
|
+
*
|
|
21
|
+
*
|
|
22
|
+
* @module build/island-code-splitting
|
|
23
|
+
*/
|
|
24
|
+
import type { Plugin } from "vite";
|
|
25
|
+
import type { ResolvedAvalonConfig } from "../vite-plugin/types.ts";
|
|
26
|
+
import type { AvalonNitroConfig } from "../nitro/config.ts";
|
|
27
|
+
/** Default size threshold (in bytes) below which island chunks are consolidated. */
|
|
28
|
+
export declare const DEFAULT_CHUNK_SIZE_THRESHOLD = 4096;
|
|
29
|
+
/** Minimal chunk shape used by the consolidation logic (compatible with both Rollup and Rolldown). */
|
|
30
|
+
export interface BundleChunk {
|
|
31
|
+
type: string;
|
|
32
|
+
fileName: string;
|
|
33
|
+
code: string;
|
|
34
|
+
moduleIds: string[];
|
|
35
|
+
}
|
|
36
|
+
/** A mutable bundle record — works with both Rollup's and Rolldown's OutputBundle. */
|
|
37
|
+
type MutableBundle = Record<string, unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if a module ID matches the integration loader virtual module.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isIntegrationLoaderModule(id: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Check if a module ID is a framework runtime that should be inlined
|
|
44
|
+
* into island chunks.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isFrameworkRuntimeModule(id: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a module ID belongs to an island chunk.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isIslandModule(id: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Check if a module should be inlined into island chunks rather than
|
|
53
|
+
* extracted into a separate shared/vendor chunk.
|
|
54
|
+
*/
|
|
55
|
+
export declare function shouldInlineIntoIsland(id: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Walk the importer chain of a module to find the island entry point
|
|
58
|
+
* that imports it. Returns the island chunk name (e.g., "islands/Counter")
|
|
59
|
+
* or undefined if the module is not imported by any island.
|
|
60
|
+
*
|
|
61
|
+
* Uses getModuleInfo from Rollup's manualChunks context to traverse
|
|
62
|
+
* the import graph upward from a framework module to its island entry.
|
|
63
|
+
*/
|
|
64
|
+
export declare function findIslandImporter(id: string, getModuleInfo: (id: string) => {
|
|
65
|
+
importers: readonly string[];
|
|
66
|
+
} | null, visited?: Set<string>): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Check if a module ID belongs to a package that is known to be
|
|
69
|
+
* side-effect-free and safe for aggressive tree-shaking.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isSideEffectFreeModule(id: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Detect the framework used by an island chunk by inspecting its module IDs.
|
|
74
|
+
* Returns the framework package name (e.g., "solid") or "unknown".
|
|
75
|
+
*/
|
|
76
|
+
export declare function detectChunkFramework(moduleIds: string[]): string;
|
|
77
|
+
/**
|
|
78
|
+
* Identify island chunks in the bundle that are below the size threshold.
|
|
79
|
+
* Returns an array of small island chunks.
|
|
80
|
+
*/
|
|
81
|
+
export declare function findSmallIslandChunks(bundle: Record<string, BundleChunk | {
|
|
82
|
+
type: string;
|
|
83
|
+
}>, threshold: number): BundleChunk[];
|
|
84
|
+
/**
|
|
85
|
+
* Group small island chunks by their detected framework.
|
|
86
|
+
* Returns a map of framework name → array of small chunks.
|
|
87
|
+
*/
|
|
88
|
+
export declare function groupChunksByFramework(chunks: BundleChunk[]): Map<string, BundleChunk[]>;
|
|
89
|
+
/**
|
|
90
|
+
* Build a consolidated chunk by concatenating the code of multiple small
|
|
91
|
+
* island chunks. Each original chunk's exports are preserved as named
|
|
92
|
+
* exports in the consolidated module.
|
|
93
|
+
*
|
|
94
|
+
* Returns the consolidated code string and the combined module IDs.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildConsolidatedChunk(chunks: BundleChunk[]): {
|
|
97
|
+
code: string;
|
|
98
|
+
moduleIds: string[];
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Consolidate small island chunks in the output bundle.
|
|
102
|
+
* Merges island chunks below the size threshold into shared bundles
|
|
103
|
+
* grouped by framework (e.g., `islands/shared-solid.js`).
|
|
104
|
+
*
|
|
105
|
+
* Only groups with 2+ small chunks are consolidated — a single small
|
|
106
|
+
* chunk is left as-is since merging wouldn't reduce requests.
|
|
107
|
+
*
|
|
108
|
+
* Returns a map of original fileName → consolidated fileName for
|
|
109
|
+
* updating references (e.g., script tags, modulepreload hints).
|
|
110
|
+
*/
|
|
111
|
+
export declare function consolidateIslandChunks(bundle: MutableBundle, threshold?: number): Map<string, string>;
|
|
112
|
+
/**
|
|
113
|
+
* Creates a Vite plugin that configures Rollup to bundle the framework
|
|
114
|
+
* adapter + runtime into each island component chunk.
|
|
115
|
+
*
|
|
116
|
+
* Only active when:
|
|
117
|
+
* - The hydration mode is "per-island"
|
|
118
|
+
* - The build command is "build" (not dev server)
|
|
119
|
+
*
|
|
120
|
+
* This eliminates the separate runtime chunk, matching Astro's approach
|
|
121
|
+
* where each island is fully self-contained.
|
|
122
|
+
*
|
|
123
|
+
* Tree-shaking: Configures `treeshake.moduleSideEffects` so that solid-js
|
|
124
|
+
* packages are treated as side-effect-free. This allows Rollup to drop
|
|
125
|
+
* unused exports (template, delegateEvents, spread, etc.) from island
|
|
126
|
+
* chunks that only use hydrate() + createComponent().
|
|
127
|
+
*/
|
|
128
|
+
export declare function islandCodeSplittingPlugin(_avalonConfig: ResolvedAvalonConfig, _nitroConfig?: AvalonNitroConfig, options?: {
|
|
129
|
+
chunkSizeThreshold?: number;
|
|
130
|
+
}): Plugin;
|
|
131
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const DEFAULT_CHUNK_SIZE_THRESHOLD=4096;const t=[`\0virtual:avalon/integration-loader`,`virtual:avalon/integration-loader`],n=[`solid-js`,`solid-js/web`,`solid-js/store`,`preact`,`preact/hooks`,`preact/jsx-runtime`,`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`,`vue`,`@vue/runtime-dom`,`@vue/runtime-core`,`@vue/reactivity`,`@vue/shared`,`svelte`,`svelte/internal`,`svelte/store`,`lit`,`lit-html`,`@lit/reactive-element`,`@builder.io/qwik`];export function isIntegrationLoaderModule(e){return t.some(t=>e.includes(t))}export function isFrameworkRuntimeModule(e){return n.some(t=>e.includes(`/node_modules/${t}/`)||e===t)}export function isIslandModule(e){return e.includes(`/islands/`)||e.includes(`avalon-island-entry:`)}export function shouldInlineIntoIsland(e){return isIntegrationLoaderModule(e)||isFrameworkRuntimeModule(e)}const s=/islands\/([^.]+)/;function c(e){let t=s.exec(e);return t?`islands/${t[1]}`:void 0}function l(e){return e.includes(`avalon-island-entry:`)?!0:e.includes(`/islands/`)&&!shouldInlineIntoIsland(e)}export function findIslandImporter(e,t,n){let r=n??new Set;if(r.has(e))return;r.add(e);let i=t(e);if(i)for(let e of i.importers){if(l(e))return c(e);let n=findIslandImporter(e,t,r);if(n)return n}}const u=[`solid-js`,`solid-js/web`,`solid-js/store`,`preact`,`preact/hooks`,`svelte`,`svelte/internal`,`lit`,`lit-html`,`@lit/reactive-element`];export function isSideEffectFreeModule(e){return u.some(t=>e.includes(`/node_modules/${t}/`)||e.includes(`/${t}/dist/`))}export function detectChunkFramework(e){for(let t of e){if(t.includes(`solid-js`)||t.includes(`solid-js/web`))return`solid`;if(t.includes(`preact`))return`preact`;if(t.includes(`react`))return`react`;if(t.includes(`vue`))return`vue`;if(t.includes(`svelte`))return`svelte`;if(t.includes(`@builder.io/qwik`))return`qwik`;if(t.includes(`lit`))return`lit`}return`unknown`}export function findSmallIslandChunks(e,t){let n=[];for(let r of Object.values(e)){if(r.type!==`chunk`)continue;let e=r;e.fileName.startsWith(`islands/`)&&(e.fileName.includes(`shared-`)||e.code.length<t&&n.push(e))}return n}export function groupChunksByFramework(e){let t=new Map;for(let n of e){let e=detectChunkFramework(n.moduleIds),r=t.get(e)??[];r.push(n),t.set(e,r)}return t}export function buildConsolidatedChunk(e){let t=[],n=[];for(let r of e){t.push(`// --- ${r.fileName} ---`,r.code);for(let e of r.moduleIds)n.push(e)}return{code:t.join(`
|
|
2
|
+
`),moduleIds:n}}export function consolidateIslandChunks(t,n=DEFAULT_CHUNK_SIZE_THRESHOLD){let r=new Map,i=findSmallIslandChunks(t,n);if(i.length<2)return r;let a=groupChunksByFramework(i);for(let[e,n]of a){if(n.length<2)continue;let i=`islands/shared-${e}.js`,{code:a,moduleIds:o}=buildConsolidatedChunk(n);t[i]={type:`chunk`,fileName:i,code:a,moduleIds:o,isEntry:!1,isDynamicEntry:!1,facadeModuleId:null,modules:{},exports:[],imports:[],dynamicImports:[],implicitlyLoadedBefore:[],importedBindings:{},referencedFiles:[],map:null,name:`shared-${e}`,preliminaryFileName:i,sourcemapFileName:null};for(let e of n)r.set(e.fileName,i),delete t[e.fileName]}return r}export function islandCodeSplittingPlugin(t,n,r){let i=r?.chunkSizeThreshold??DEFAULT_CHUNK_SIZE_THRESHOLD,a=!1;return{name:`avalon:island-code-splitting`,enforce:`post`,config(e,{command:t}){if(t===`build`)return a=!0,{build:{rollupOptions:{treeshake:{moduleSideEffects(e){return!isSideEffectFreeModule(e)}}}}}},configEnvironment(e){},generateBundle(e,n){if(!a)return;let r=this.environment?.name;if(r&&r!==`client`)return;let o=findSmallIslandChunks(n,i);if(o.length>=2&&t.verbose){let e=groupChunksByFramework(o);for(let[t,n]of e)n.length>=2&&console.log(`🏝️ ${n.length} small ${t} island chunks could be consolidated into islands/shared-${t}.js`)}}}}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Critical CSS Extraction and Inlining
|
|
3
|
+
*
|
|
4
|
+
* Collects CSS generated during SSR (via the universal CSS collector),
|
|
5
|
+
* inlines it as a <style> tag in <head>, and defers external stylesheets
|
|
6
|
+
* using the media="print" swap pattern for async loading.
|
|
7
|
+
*
|
|
8
|
+
* This eliminates render-blocking CSS requests, improving FCP and LCP.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Deduplicate CSS rules by normalizing each top-level rule block.
|
|
12
|
+
* Removes exact-duplicate rules while preserving order of first occurrence.
|
|
13
|
+
*/
|
|
14
|
+
export declare function deduplicateCSSRules(css: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Collect SSR-generated CSS from the universal collector, deduplicate it,
|
|
17
|
+
* and return an inline <style> tag suitable for injection into <head>.
|
|
18
|
+
*
|
|
19
|
+
* @param clear - Whether to clear the collector after extraction (default: true)
|
|
20
|
+
* @returns An inline <style> tag string, or empty string if no CSS was collected
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractCriticalCSS(clear?: boolean): string;
|
|
23
|
+
/**
|
|
24
|
+
* Convert external third-party <link rel="stylesheet"> tags to use the
|
|
25
|
+
* media="print" swap pattern for async/non-blocking loading.
|
|
26
|
+
*
|
|
27
|
+
* Only defers stylesheets from external origins (https://) — local asset
|
|
28
|
+
* stylesheets (e.g., /assets/entry-client-*.css) are never deferred since
|
|
29
|
+
* they contain essential layout CSS that would cause FOUC if delayed.
|
|
30
|
+
*
|
|
31
|
+
* Stylesheets that already have a media attribute or are marked as critical
|
|
32
|
+
* (data-critical) are left untouched.
|
|
33
|
+
*/
|
|
34
|
+
export declare function deferNonCriticalStylesheets(html: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Process the full HTML response to inline critical CSS and defer external stylesheets.
|
|
37
|
+
*
|
|
38
|
+
* 1. Extracts SSR-collected CSS from the universal collector
|
|
39
|
+
* 2. Inlines it as a <style> tag in <head>
|
|
40
|
+
* 3. Converts external stylesheet <link> tags to async loading
|
|
41
|
+
*
|
|
42
|
+
* @param html - The rendered HTML string
|
|
43
|
+
* @returns The HTML with critical CSS inlined and external stylesheets deferred
|
|
44
|
+
*/
|
|
45
|
+
export declare function inlineCriticalCSS(html: string): string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{getUniversalCSS as e}from"./universal-css-collector.js";import{minifyCSS as t}from"./css-utils.js";function n(e){let t=0;for(let n of e)n===`{`?t++:n===`}`&&t--;return t}function r(e,t,n){let r=e.replaceAll(/\s+/g,` `).trim();if(r.startsWith(`/*`)&&r.endsWith(`*/`)){n.push(e.trimEnd());return}t.has(r)||(t.add(r),n.push(e.trimEnd()))}export function deduplicateCSSRules(e){if(!e.trim())return``;let t=new Set,i=e.split(`
|
|
2
|
+
`),a=[],o=``,s=0;for(let e of i)s+=n(e),o+=`${e}\n`,s<=0&&o.trim()&&(r(o,t,a),o=``,s=0);return o.trim()&&a.push(o.trimEnd()),a.join(`
|
|
3
|
+
`)}export function extractCriticalCSS(n=!0){let r=e(n);if(!r.trim())return``;let a=t(deduplicateCSSRules(r));return a.trim()?`<style data-critical-css="true">${a}</style>`:``}export function deferNonCriticalStylesheets(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];return!r.startsWith(`https://`)&&!r.startsWith(`http://`)?e:`${`<link ${t} media="print" onload="this.media='all'">`}\n${`<noscript><link rel="stylesheet" href="${r}"></noscript>`}`})}export function inlineCriticalCSS(e){let t=extractCriticalCSS(!0),n=e;return t&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${t}\n</head>`)),n=deferNonCriticalStylesheets(n),n}
|
|
@@ -4,6 +4,8 @@ import type { AnalyzerOptions } from "../core/components/component-analyzer.ts";
|
|
|
4
4
|
import type { Framework } from "./types.ts";
|
|
5
5
|
declare global {
|
|
6
6
|
var __viteDevServer: ViteDevServer | undefined;
|
|
7
|
+
/** Hydration mode — automatically set: "entry-client" in dev (HMR), "per-island" in production */
|
|
8
|
+
var __avalonHydrationMode: "entry-client" | "per-island" | undefined;
|
|
7
9
|
}
|
|
8
10
|
/** Supported hydration conditions for island components */
|
|
9
11
|
export type HydrationCondition = "on:visible" | "on:interaction" | "on:idle" | "on:client" | `media:${string}` | `on:${string}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{h as
|
|
1
|
+
import{Fragment as e,h as t}from"preact";import{getIslandBundlePath as n}from"../build/island-manifest.js";import{devError as r,devLog as i,devWarn as a,isDev as o,logRenderTiming as s}from"../utils/dev-logger.js";import{analyzeComponentFile as c,renderComponentSSROnly as l}from"./component-analysis.js";import{detectFramework as u}from"./framework-detection.js";import{isCustomDirective as d,serializeDirectiveScript as f}from"./hydration-directives.js";import{detectFrameworkFromPath as p,loadIntegration as m}from"./integration-loader.js";import{generatePerIslandScript as h}from"./per-island-script.js";import{addModulepreload as g}from"./modulepreload-collector.js";import{addUniversalCSS as _}from"./universal-css-collector.js";import{addUniversalHead as v}from"./universal-head-collector.js";function y(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function b(){return globalThis.__avalonHydrationMode===void 0?!o():globalThis.__avalonHydrationMode===`per-island`}function x(r,i){if(!b()||i.shouldSkipHydration)return r;let a=n(i.src);i.condition===`on:client`&&g(a);let o=d(i.condition),s=o?f(i.condition):void 0;return t(e,null,r,t(`div`,{dangerouslySetInnerHTML:{__html:h({islandId:i.islandId,componentSrc:a,framework:i.framework,condition:i.condition,conditionArg:i.conditionArg,propsJson:JSON.stringify(i.props),isCustomDirective:o,directiveScript:s??void 0})},style:`display:contents`,"data-island-script":``}))}function S(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function C(e,t,r,i,a){let o={"data-condition":t,"data-src":n(e),"data-props":JSON.stringify(r),"data-render-strategy":`hydrate`,...S(i)};if(d(t)){o[`data-custom-directive`]=t;let e=f(t);e&&(o[`data-directive-script`]=e)}return a&&(o[`data-condition-arg`]=a),o}function w(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function T(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function E(e,t,n,r){if(e.css&&_(e.css,t,n,e.scopeId),e.head){let a=e.head.trim(),o=w(a);if(o===`style`){let o=T(a);o&&(i(`${r} Extracting CSS from head <style> tag`),_(o,t,n,e.scopeId));return}v(e.head,t,n,o)}}function D(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:a,src:o,condition:s,conditionArg:c,props:l,hydrationData:u,children:d}=e,f={id:n,"data-framework":r},p=a?{"data-render-strategy":`ssr-only`}:C(o,s,l,u,c);r===`lit`&&i(`🔍 [Island Component] ${o} - Lit hydration data:`,{hydrationDataKeys:Object.keys(u),metadata:u.metadata});let m={...f,...p},h;return h=typeof d==`string`?t(`avalon-island`,{...m,dangerouslySetInnerHTML:{__html:d}}):t(`avalon-island`,m,d),x(h,{islandId:n,src:o,condition:s,conditionArg:c,props:l,framework:r,shouldSkipHydration:a})}function O(e){let{islandId:r,detectedFramework:i,shouldSkipHydration:a,src:o,condition:s,props:c,hydrationData:l,conditionArg:u}=e;if(a)return t(`avalon-island`,{id:r,"data-render-strategy":`ssr-only`,"data-framework":i});let p={id:r,"data-condition":s,"data-src":n(o),"data-props":JSON.stringify(c),"data-render-strategy":`hydrate`,"data-framework":i,...S(l)};if(d(s)){p[`data-custom-directive`]=s;let e=f(s);e&&(p[`data-directive-script`]=e)}return u&&(p[`data-condition-arg`]=u),x(t(`avalon-island`,p),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function k({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:o,ssr:s=t!==`on:client`,framework:c,ssrOnly:l=!1,renderOptions:u={},hydrationData:d={}}){let f=y(e),m=l||!!u.forceSSROnly,h=c||p(e),g=o!=null&&o!==``;return i(`🔍 [Island Component] ${e}`,{ssr:s,ssrOnly:l,hasChildren:g,framework:c,condition:t}),s&&g?D({islandId:f,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,conditionArg:n,props:r,hydrationData:d,children:o}):(s&&!g&&m&&a(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),O({islandId:f,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,props:r,hydrationData:d,conditionArg:n}))}function A(e,i){let a=i instanceof Error?i.message:String(i);return r(`🚨 Island SSR failed for ${e}:`,i),i instanceof Error&&i.stack&&r(`Stack trace:`,i.stack),t(`avalon-island`,{id:y(e),"data-src":n(e),"data-ssr-error":a,"data-render-strategy":`client-only`})}async function j({src:e,condition:t,conditionArg:n,props:i,children:a,ssr:s,framework:c,ssrOnly:l,renderOptions:u,component:d}){let f=`🏝️ [${e}]`;if(!s||a)return k({src:e,condition:t,conditionArg:n,props:i,children:a,ssr:s,framework:c,ssrOnly:l,renderOptions:u});let p;try{p=await m(c)}catch(a){return r(`${f} Failed to load ${c} integration:`,a),k({src:e,condition:t,conditionArg:n,props:i,ssr:!1,framework:c,ssrOnly:l,renderOptions:u})}try{let r=await p.render({component:d??null,props:i,src:e,condition:t,ssrOnly:l,viteServer:globalThis.__viteDevServer,isDev:o()});return E(r,e,c,f),k({src:e,condition:t,conditionArg:n,props:i,children:r.html,ssr:!0,framework:c,ssrOnly:l,renderOptions:u,hydrationData:l?void 0:r.hydrationData})}catch(a){return r(`${f} Fast path SSR failed:`,a),k({src:e,condition:t,conditionArg:n,props:i,ssr:!1,framework:c,ssrOnly:l,renderOptions:u})}}async function M(e,t,n,r){if(t||n.detectScripts===!1)return t;try{let t=await c(e,n);if(t.decision.warnings?.length)for(let e of t.decision.warnings)a(`${r} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return a(`${r} Component analysis failed:`,e),t}}async function N(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?u(e):`unknown`}async function P(e,t,n,r,i,a,s){let c=await N(e),l=c,u=await(await F(c,a)).render({component:s??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:o()});return E(u,e,c,a),k({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:l,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function F(e,t){try{i(`${t} Loading integration for framework: ${e}`);let n=await m(e);return i(`${t} ✅ Integration loaded successfully`),n}catch(n){throw r(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:c,ssrOnly:l=!1,renderOptions:u={},component:d}){let f=o()?performance.now():0,p=`🏝️ [${e}]`;try{return l&&!a&&(a=!0),c?await j({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:c,ssrOnly:l,renderOptions:u,component:d}):await I({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,ssrOnly:l,renderOptions:u,logPrefix:p,component:d})}catch(t){return A(e,t)}finally{o()&&s(e,performance.now()-f)}}async function I(e){let{src:t,condition:n,conditionArg:a,props:o,children:s,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f}=e;if(i(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!s,condition:n}),await M(t,l,u,d))return L(t,n,o,s,c,u,d);if(!c||s)return k({src:t,condition:n,conditionArg:a,props:o,children:s,ssr:c,renderOptions:u});try{return await P(t,n,o,l,u,d,f)}catch(e){let i=await N(t);return r(`${d} Framework rendering failed:`,e),k({src:t,condition:n,conditionArg:a,props:o,ssr:!1,framework:i,renderOptions:u})}}function L(e,t,n,i,a,o,s){return a&&!i?l({src:e,condition:t,props:n,renderOptions:o}).catch(i=>(r(`${s} SSR failed for SSR-only component:`,i),k({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:o}))):k({src:e,condition:t,props:n,children:i,ssr:a,ssrOnly:!0,renderOptions:o})}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modulepreload Collector for SSR
|
|
3
|
+
*
|
|
4
|
+
* Collects island chunk bundle paths during SSR rendering for islands
|
|
5
|
+
* that use `on:client` (immediate hydration). These paths are injected
|
|
6
|
+
* as `<link rel="modulepreload">` tags in `<head>` so the browser starts
|
|
7
|
+
* fetching island JS chunks as soon as it parses the head, eliminating
|
|
8
|
+
* the waterfall delay between HTML parse and island JS fetch.
|
|
9
|
+
*
|
|
10
|
+
* Only `on:client` islands are collected — deferred islands (on:visible,
|
|
11
|
+
* on:idle, on:interaction, media:*) are intentionally excluded since
|
|
12
|
+
* preloading them would defeat the purpose of lazy loading.
|
|
13
|
+
*/
|
|
14
|
+
declare global {
|
|
15
|
+
var __modulepreloadPaths: Set<string> | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register an island's bundle path for modulepreload.
|
|
19
|
+
*
|
|
20
|
+
* Call this during SSR when an island with `on:client` condition is rendered.
|
|
21
|
+
* The path is deduplicated automatically (Set-based).
|
|
22
|
+
*
|
|
23
|
+
* @param bundlePath - The island chunk's bundle path (e.g., "/islands/Counter.abc123.js")
|
|
24
|
+
*/
|
|
25
|
+
export declare function addModulepreload(bundlePath: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get all collected modulepreload paths.
|
|
28
|
+
*
|
|
29
|
+
* @param clear - Whether to clear the collector after retrieval (default: true)
|
|
30
|
+
* @returns Array of unique bundle paths
|
|
31
|
+
*/
|
|
32
|
+
export declare function getModulepreloadPaths(clear?: boolean): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Generate `<link rel="modulepreload">` tags for all collected island chunks.
|
|
35
|
+
*
|
|
36
|
+
* @param clear - Whether to clear the collector after extraction (default: true)
|
|
37
|
+
* @returns A string of modulepreload link tags, or empty string if none collected
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateModulepreloadTags(clear?: boolean): string;
|
|
40
|
+
/**
|
|
41
|
+
* Inject modulepreload link tags into the HTML `<head>`.
|
|
42
|
+
*
|
|
43
|
+
* Extracts collected modulepreload paths, generates `<link rel="modulepreload">`
|
|
44
|
+
* tags, and injects them before `</head>`.
|
|
45
|
+
*
|
|
46
|
+
* @param html - The rendered HTML string
|
|
47
|
+
* @returns HTML with modulepreload links injected into `<head>`
|
|
48
|
+
*/
|
|
49
|
+
export declare function injectModulepreloadLinks(html: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Clear all collected modulepreload paths.
|
|
52
|
+
*/
|
|
53
|
+
export declare function clearModulepreloads(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Get the current number of collected modulepreload paths.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getModulepreloadCount(): number;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(){return globalThis.__modulepreloadPaths??=new Set,globalThis.__modulepreloadPaths}export function addModulepreload(t){t&&e().add(t)}export function getModulepreloadPaths(t=!0){let n=e(),r=Array.from(n);return t&&n.clear(),r}export function generateModulepreloadTags(e=!0){let t=getModulepreloadPaths(e);return t.length===0?``:t.map(e=>`<link rel="modulepreload" href="${e}">`).join(`
|
|
2
|
+
`)}export function injectModulepreloadLinks(e){let t=generateModulepreloadTags(!0);return t&&e.includes(`</head>`)?e.replace(`</head>`,`${t}\n</head>`):e}export function clearModulepreloads(){e().clear()}export function getModulepreloadCount(){return e().size}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-Island Script Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates self-contained `<script type="module">` tags for each island,
|
|
5
|
+
* eliminating the need for a shared hydration runtime. This is how Astro
|
|
6
|
+
* handles island hydration — each island is fully self-contained.
|
|
7
|
+
*
|
|
8
|
+
* Each generated script:
|
|
9
|
+
* 1. Imports the specific component module
|
|
10
|
+
* 2. Imports the framework adapter (integration)
|
|
11
|
+
* 3. Handles the hydration strategy (on:client, on:visible, etc.)
|
|
12
|
+
* 4. Hydrates just that one island
|
|
13
|
+
*
|
|
14
|
+
* @module islands/per-island-script
|
|
15
|
+
*/
|
|
16
|
+
import type { HydrationCondition } from "./island.tsx";
|
|
17
|
+
export interface PerIslandScriptOptions {
|
|
18
|
+
/** The island element's DOM id */
|
|
19
|
+
islandId: string;
|
|
20
|
+
/** Bundle path to the component module */
|
|
21
|
+
componentSrc: string;
|
|
22
|
+
/** Framework identifier (solid, preact, vue, etc.) */
|
|
23
|
+
framework: string;
|
|
24
|
+
/** Hydration condition */
|
|
25
|
+
condition: HydrationCondition;
|
|
26
|
+
/** Optional condition argument (e.g., delay ms for custom directives) */
|
|
27
|
+
conditionArg?: string;
|
|
28
|
+
/** Serialized props JSON */
|
|
29
|
+
propsJson: string;
|
|
30
|
+
/** Whether this is a custom directive */
|
|
31
|
+
isCustomDirective?: boolean;
|
|
32
|
+
/** Serialized custom directive script (if applicable) */
|
|
33
|
+
directiveScript?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate an inline hydration script for a single island.
|
|
37
|
+
*
|
|
38
|
+
* The script is a self-contained ES module that imports the component
|
|
39
|
+
* and framework adapter, then hydrates the island based on its condition.
|
|
40
|
+
*/
|
|
41
|
+
export declare function generatePerIslandScript(opts: PerIslandScriptOptions): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function generatePerIslandScript(e){let{islandId:r,componentSrc:i,framework:a,condition:o,conditionArg:s,propsJson:c,isCustomDirective:l,directiveScript:u}=e;return`<script type="module">${n(r,o,t(r,i,a,c),s,l,u)}<\/script>`}function t(e,t,n,r){return[`async function h(){`,`var e=document.getElementById(${JSON.stringify(e)});`,`if(!e||e.dataset.hydrated)return;`,`try{`,`var p=${r};`,`var m=await import(${JSON.stringify(t)});`,`var C=m.default||Object.values(m).find(function(v){return typeof v==="function"&&v.prototype})||m;`,`if(m.__hydrateIsland){await m.__hydrateIsland(e,C,p)}`,`else if(m.loadIntegrationModule){var i=await m.loadIntegrationModule(${JSON.stringify(n)});if(i.hydrate)await i.hydrate(e,C,p)}`,`e.dataset.hydrated="true";`,`}catch(err){console.error("Hydration error:",err)}`,`}`].join(``)}function n(e,t,n,r,i,a){if(t===`on:client`)return`${n}(window.requestIdleCallback||requestAnimationFrame)(function(){h()});`;if(t===`on:visible`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){try{var o=new IntersectionObserver(function(n){`,`if(n[0].isIntersecting){h();o.disconnect()}`,`},{rootMargin:"50px",threshold:0});o.observe(e)}catch(_){h()}}`].join(``);if(t===`on:idle`)return[n,`if("requestIdleCallback" in globalThis){`,`globalThis.requestIdleCallback(function(){h()},{timeout:5000})`,`}else if(document.readyState==="complete"){`,`setTimeout(function(){h()},200)`,`}else{globalThis.addEventListener("load",function(){setTimeout(function(){h()},200)},{once:true})}`].join(``);if(t===`on:interaction`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var d=false;var ev=["click","touchstart","mouseenter","focusin"];`,`var fn=function(){if(d)return;d=true;ev.forEach(function(n){e.removeEventListener(n,fn)});h()};`,`ev.forEach(function(n){e.addEventListener(n,fn,{once:true,passive:true})})}`].join(``);if(t.startsWith(`media:`)){let e=t.slice(6);return[n,`try{var mq=globalThis.matchMedia(${JSON.stringify(e)});`,`if(mq.matches){h()}else{mq.addEventListener("change",function x(ev){`,`if(ev.matches){h();mq.removeEventListener("change",x)}},{once:true})}}catch(_){h()}`].join(``)}return i&&a?[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var dir=(${a});`,`dir(e,function(){h()}${r?`,${JSON.stringify(r)}`:``})}`].join(``):`${n}h();`}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{getRequestURL as e}from"h3";import{h as t}from"preact";import n from"preact-render-to-string";import{discoverScopedMiddleware as r,executeScopedMiddleware as i}from"../middleware/index.js";import{discoverErrorPages as a,handleRenderError as o}from"./error-handler.js";import{createNotFoundError as s,isHttpError as c}from"./types.js";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}export function createErrorResponse(e,t){let n=c(e)?e.statusCode:500;if(t){let t=
|
|
1
|
+
import{getRequestURL as e}from"h3";import{h as t}from"preact";import n from"preact-render-to-string";import{discoverScopedMiddleware as r,executeScopedMiddleware as i}from"../middleware/index.js";import{discoverErrorPages as a,handleRenderError as o}from"./error-handler.js";import{createNotFoundError as s,isHttpError as c}from"./types.js";import{inlineCriticalCSS as l}from"../islands/critical-css.js";import{injectModulepreloadLinks as u}from"../islands/modulepreload-collector.js";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}export function createErrorResponse(e,t){let n=c(e)?e.statusCode:500;if(t){let t=_(e,n);return new Response(t,{status:n,headers:{"Content-Type":`text/html; charset=utf-8`}})}let r=v(n);return new Response(r,{status:n,headers:{"Content-Type":`text/html; charset=utf-8`}})}function _(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{h as t}from"preact";import n from"prea
|
|
|
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>${y(t)}</h1>
|
|
60
|
+
<p class="message">${b(e.message)}</p>
|
|
61
61
|
${e.stack?`
|
|
62
62
|
<div class="stack-title">Stack Trace</div>
|
|
63
|
-
<pre>${
|
|
63
|
+
<pre>${b(e.stack)}</pre>
|
|
64
64
|
`:``}
|
|
65
65
|
</div>
|
|
66
66
|
</body>
|
|
67
|
-
</html>`}function
|
|
67
|
+
</html>`}function v(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{h as t}from"preact";import n from"prea
|
|
|
113
113
|
<body>
|
|
114
114
|
<div class="error-container">
|
|
115
115
|
<div class="status-code">${e}</div>
|
|
116
|
-
<h1>${
|
|
116
|
+
<h1>${y(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>`,`${a}\n</body>`):e+a}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,n={},r){try{let i={};return e.getServerSideProps&&(i=await e.getServerSideProps(t)),{html:await
|
|
120
|
+
</html>`}function y(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 b(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}export function extractIslandMarkers(e){let t=[],n=/<[^>]*data-framework="([^"]+)"[^>]*>/g,r=n.exec(e);for(;r!==null;){let i=r[0],a=r[1],o=/data-src="([^"]+)"/.exec(i),s=o?o[1]:``,c=/data-props="([^"]*)"/.exec(i),l=c?c[1]:void 0,u=/data-hydrate="([^"]+)"/.exec(i),d=u?u[1]:void 0;t.push({framework:a,src:s,props:l,hydrate:d}),r=n.exec(e)}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 x(e){return e.replaceAll(/<div data-island-script="" style="display:contents">(<script[\s\S]*?<\/script>)<\/div>/g,`$1`)}export function injectHydrationScript(e,t,n={}){if(globalThis.__avalonHydrationMode===`per-island`||!t)return x(e);if(!(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 r=n.scriptPath||(t?`/src/client/main.js`:`/dist/client.js`),i=[];i.push(`<script type="module" src="${r}"><\/script>`),n.additionalScripts&&i.push(...n.additionalScripts);let a=i.join(`
|
|
121
|
+
`);return e.includes(`</body>`)?e.replace(`</body>`,`${a}\n</body>`):e+a}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,n={},r){try{let i={};return e.getServerSideProps&&(i=await e.getServerSideProps(t)),{html:await S(e,i,t,n,r),statusCode:200,headers:{"Content-Type":`text/html; charset=utf-8`}}}catch(e){throw console.error(`[SSR Error]`,e),n.onError&&e instanceof Error&&n.onError(e),e}}async function S(e,r,i,a,o){let s=e.default,c=e.metadata||{},l=process.env.NODE_ENV!==`production`,u;try{let e=s(r);u=e instanceof Promise?await e:e}catch(e){console.error(`[renderer] Error calling page component:`,e),u=t(`div`,null,`Error rendering page`)}let d;try{d=n(u)}catch(e){console.error(`[renderer] Error in preactRenderToString:`,e),d=`<div>Error rendering page</div>`}if(o)return await o(d,e,i);let f=l?`
|
|
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
|
-
${c.description?`<meta name="description" content="${
|
|
127
|
+
<title>${b(String(c.title||`Avalon App`))}</title>
|
|
128
|
+
${c.description?`<meta name="description" content="${b(String(c.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,r={}){let i=new TextEncoder,a=null,o={shellSent:!1,contentSent:!1,closed:!1,error:null},s=r.shellReadyTimeout,c=r.allReadyTimeout,l=null,u=null,d=()=>{l&&=(clearTimeout(l),null),u&&=(clearTimeout(u),null)};function f(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=
|
|
135
|
+
</html>`}export async function renderPageStream(e,t,r={}){let i=new TextEncoder,a=null,o={shellSent:!1,contentSent:!1,closed:!1,error:null},s=r.shellReadyTimeout,c=r.allReadyTimeout,l=null,u=null,d=()=>{l&&=(clearTimeout(l),null),u&&=(clearTimeout(u),null)};function f(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=E(e);r.enqueue(i.encode(t));let n=T();r.enqueue(i.encode(n))}else{let t=_(e,500);r.enqueue(i.encode(t))}n.closed=!0,r.close()}}async function p(s){a=s;let p={};e.getServerSideProps&&(p=await e.getServerSideProps(t));let m=C(e.metadata||{},t);if(o.closed||(s.enqueue(i.encode(m)),o.shellSent=!0,l&&=(clearTimeout(l),null),r.onShellReady&&r.onShellReady()),c&&c>0&&(u=setTimeout(()=>{!o.contentSent&&!o.closed&&f(Error(`All ready timeout after ${c}ms`),!1,o,a,i,d,r)},c)),!o.closed){let t=e.default;if(t&&typeof t==`function`){let r=t(p);if(r&&typeof r.then==`function`)try{let e=n(await r);s.enqueue(i.encode(` <div id="app">${e}</div>\n`))}catch(t){console.error(`[streaming] Async component error:`,t);let n=w(e,p);s.enqueue(i.encode(n))}else{let t=w(e,p);s.enqueue(i.encode(t))}}else{let t=w(e,p);s.enqueue(i.encode(t))}o.contentSent=!0}if(!o.closed){let e=T();s.enqueue(i.encode(e))}d(),r.onAllReady&&!o.closed&&r.onAllReady(),o.closed||(o.closed=!0,s.close())}return new ReadableStream({async start(e){a=e,s&&s>0&&(l=setTimeout(()=>{!o.shellSent&&!o.closed&&f(Error(`Shell ready timeout after ${s}ms`),!0,o,a,i,d,r)},s));try{await p(e)}catch(e){f(e instanceof Error?e:Error(String(e)),!o.shellSent,o,a,i,d,r)}},cancel(){if(d(),!o.closed&&a){o.closed=!0;try{a.close()}catch{}}}})}function C(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>${b(String(e.title||`Avalon App`))}</title>
|
|
141
|
+
${e.description?`<meta name="description" content="${b(String(e.description))}">`:``}
|
|
142
142
|
</head>
|
|
143
143
|
<body>
|
|
144
|
-
`}function
|
|
144
|
+
`}function w(e,t){let r=e.default;if(r&&typeof r==`function`)try{let e=r(t);if(e&&typeof e.then==`function`){let e=r.name||`Page`;return` <div id="app" data-page="${b(String(e))}" data-props='${b(JSON.stringify(t))}'>
|
|
145
145
|
<!-- Async component — awaiting hydration -->
|
|
146
|
-
</div>\n`}return` <div id="app">${n(e)}</div>\n`}catch(e){console.error(`[streaming] Error rendering page component:`,e)}let i=e.default?.name||`Page`;return` <div id="app" data-page="${
|
|
146
|
+
</div>\n`}return` <div id="app">${n(e)}</div>\n`}catch(e){console.error(`[streaming] Error rendering page component:`,e)}let i=e.default?.name||`Page`;return` <div id="app" data-page="${b(String(i))}" data-props='${b(JSON.stringify(t))}'>
|
|
147
147
|
<!-- Component render fallback -->
|
|
148
|
-
</div>\n`}function
|
|
149
|
-
</html>`}function
|
|
148
|
+
</div>\n`}function T(){return` </body>
|
|
149
|
+
</html>`}function E(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
|
+
">${b(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{h as t}from"preact";import n from"prea
|
|
|
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> ${b(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 D(e,t,n){return async()=>(e.value??=await r({baseDir:t,devMode:n}),e.value)}function O(e,t,n){return async(r,i)=>e?o(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:r=!0}=e,o={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};r&&a(o).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let c=D({value:null},t.srcDir||`src`,n),m=O(r,o,n);async function h(r){let a=getRequestURL(r).pathname;try{let o=await i(r,await c(),{devMode:n});if(o)return n&&console.log(`[renderer] Middleware terminated request for ${a}`),o;let f=r.context.route,p=null;if(p=f||(e.resolvePageRoute?await e.resolvePageRoute(a,t.pagesDir):await k(a,t.pagesDir)),!p)return m(s(`Page not found: ${a}`),r);let h=e.loadPageModule?await e.loadPageModule(p.filePath):await A(p.filePath),g=createRenderContext(r,r.context.params||p.params);if(e.resolveLayouts&&(g.layoutContext={layouts:await e.resolveLayouts(a,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(h,g,{onShellReady:()=>{setResponseHeader(r,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(h,g,{},e.wrapWithLayouts),r=injectHydrationScript(t.html,n);return r=l(r),r=u(r),new Response(r,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),r)}}return Object.assign(h,{async fetch(r){let i=new URL(r.url,`http://localhost`).pathname;try{let a=null;if(a=e.resolvePageRoute?await e.resolvePageRoute(i,t.pagesDir):await k(i,t.pagesDir),!a)return createErrorResponse(s(`Page not found: ${i}`),n);let o=await renderPage(e.loadPageModule?await e.loadPageModule(a.filePath):await A(a.filePath),createRenderContextFromRequest(r,a.params),{},e.wrapWithLayouts),c=injectHydrationScript(o.html,n);return c=l(c),c=u(c),new Response(c,{status:o.statusCode,headers:o.headers})}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function k(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function A(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:r,resolveLayouts:o,enableCustomErrorPages:c=!0}=e,m={isDev:n,avalonConfig:t,loadPageModule:r,pagesDir:t.pagesDir};c&&a(m).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let h=D({value:null},t.srcDir||`src`,n),_=O(c,m,n);async function v(a){let c=getRequestURL(a).pathname;try{let f=await i(a,await h(),{devMode:n});if(f)return n&&console.log(`[renderer] Middleware terminated request for ${c}`),f;let p=a.context.params||{},m=p.slug||c.replace(/^\//,``)||`index`,g=`${t.pagesDir}/${m}.tsx`,v;try{v=await r(g)}catch(e){try{v=await r(`${t.pagesDir}/${m}/index.tsx`)}catch(r){return n&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${t.pagesDir}/${m}/index.tsx`,r)),_(s(`Page not found: ${c}`),a)}}let y=createRenderContext(a,p);if(o&&(y.layoutContext={layouts:await o(c,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(v,y,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(v,y,{},e.wrapWithLayouts),r=injectHydrationScript(t.html,n);return r=l(r),r=u(r),new Response(r,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),_(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(v,{async fetch(i){let a=new URL(i.url,`http://localhost`).pathname;try{let o=a.replace(/^\//,``)||`index`,c=`${t.pagesDir}/${o}.tsx`,d;try{d=await r(c)}catch{try{d=await r(`${t.pagesDir}/${o}/index.tsx`)}catch{return createErrorResponse(s(`Page not found: ${a}`),n)}}let p=createRenderContextFromRequest(i),m=await renderPage(d,p,{},e.wrapWithLayouts),h=injectHydrationScript(m.html,n);return h=l(h),h=u(h),new Response(h,{status:m.statusCode,headers:m.headers})}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";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-build Island Inlining
|
|
3
|
+
*
|
|
4
|
+
* Re-bundles island chunks with esbuild to inline shared dependencies
|
|
5
|
+
* (framework runtimes) into a single self-contained file per island.
|
|
6
|
+
*
|
|
7
|
+
* Safe for all frameworks because each island wrapper exports __hydrateIsland
|
|
8
|
+
* directly from the framework adapter — the component and hydrate function
|
|
9
|
+
* share the same module graph, so no duplicate framework singletons are created.
|
|
10
|
+
*
|
|
11
|
+
* Runs AFTER the main Vite build, operating on compiled JS output.
|
|
12
|
+
*/
|
|
13
|
+
interface InlineResult {
|
|
14
|
+
island: string;
|
|
15
|
+
beforeSize: number;
|
|
16
|
+
afterSize: number;
|
|
17
|
+
success: boolean;
|
|
18
|
+
skipped?: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Inline shared chunk dependencies into each island file.
|
|
23
|
+
*
|
|
24
|
+
* @param distDir - The build output directory (e.g., "dist" or ".output/public")
|
|
25
|
+
* @param options - Configuration options
|
|
26
|
+
*/
|
|
27
|
+
export declare function inlineIslandChunks(distDir: string, options?: {
|
|
28
|
+
verbose?: boolean;
|
|
29
|
+
skipQwik?: boolean;
|
|
30
|
+
}): Promise<InlineResult[]>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{readdir as e,readFile as t}from"node:fs/promises";import{resolve as n,join as r}from"node:path";export async function inlineIslandChunks(e,r={}){let{verbose:i=!1,skipQwik:o=!0}=r,s=await a(n(e,`islands`));if(s.length===0)return i&&console.log(`🏝️ No island files found, skipping inlining`),[];let c=null,l;if(globalThis.Bun!==void 0)c=`bun`;else try{l=await import(`esbuild`),c=`esbuild`}catch{}if(!c)return console.warn(`🏝️ No bundler available (Bun or esbuild), skipping island inlining`),[];console.log(`🏝️ Inlining shared chunks into ${s.length} islands...`);let u=[];for(let r of s){let a=r.replace(`${e}/`,``);if(a.includes(`.qwik.`)){u.push({island:a,beforeSize:0,afterSize:0,success:!0,skipped:!0});continue}try{let e=await t(r,`utf-8`),o=Buffer.byteLength(e,`utf-8`);if(!e.includes(`from"../`)&&!e.includes(`from'../`)){u.push({island:a,beforeSize:o,afterSize:o,success:!0});continue}if(c===`bun`){let e={entrypoints:[r],outdir:n(r,`..`),naming:`[name].[ext]`,minify:!0,target:`browser`,format:`esm`,treeshaking:!0,external:[`*integration-loader*`]},t=await Bun.build(e);if(!t.success)throw Error(t.logs.map(e=>e.message).join(`
|
|
2
|
+
`))}else await l.build({entryPoints:[r],outfile:r,bundle:!0,format:`esm`,minify:!0,treeShaking:!0,target:`es2020`,allowOverwrite:!0,plugins:[{name:`externalize-integration-loader`,setup(e){e.onResolve({filter:/integration-loader|rolldown-runtime/},e=>({path:e.path,external:!0}))}}]});let s=await t(r,`utf-8`),d=Buffer.byteLength(s,`utf-8`);if(u.push({island:a,beforeSize:o,afterSize:d,success:!0}),i){let e=(o/1024).toFixed(1),t=(d/1024).toFixed(1);console.log(` ✅ ${a}: ${e} KiB → ${t} KiB`)}}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${a}: ${t}`),u.push({island:a,beforeSize:0,afterSize:0,success:!1,error:t})}}let d=u.filter(e=>e.success).length,f=u.filter(e=>!e.success).length;return console.log(`🏝️ Done: ${d} inlined${f?`, ${f} failed`:``}`),u}async function a(t,n=[]){try{let i=await e(t,{withFileTypes:!0});for(let e of i){let i=r(t,e.name);e.isDirectory()?await a(i,n):e.name.endsWith(`.js`)&&!e.name.endsWith(`.map`)&&n.push(i)}}catch{}return n}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated Island Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds each island as a separate Vite build with fresh framework plugins.
|
|
5
|
+
* This produces self-contained island files with the framework runtime inlined
|
|
6
|
+
* and tree-shaken — matching Astro's approach.
|
|
7
|
+
*
|
|
8
|
+
* Each build:
|
|
9
|
+
* 1. Loads framework plugins fresh (vite-plugin-solid, @vitejs/plugin-vue, etc.)
|
|
10
|
+
* 2. Compiles from source (.tsx/.vue/.svelte)
|
|
11
|
+
* 3. Outputs a single file with codeSplitting: false
|
|
12
|
+
* 4. Tree-shakes aggressively — only used runtime functions remain
|
|
13
|
+
*
|
|
14
|
+
* Runs as a post-build step after the main Vite build.
|
|
15
|
+
*/
|
|
16
|
+
interface IslandBuildResult {
|
|
17
|
+
island: string;
|
|
18
|
+
success: boolean;
|
|
19
|
+
size?: number;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
interface IslandSource {
|
|
23
|
+
filePath: string;
|
|
24
|
+
bundleKey: string;
|
|
25
|
+
framework: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build all islands in isolation.
|
|
29
|
+
*
|
|
30
|
+
* @param cwd - Project root directory
|
|
31
|
+
* @param distDir - Build output directory (e.g., "dist")
|
|
32
|
+
* @param islands - Map of discovered islands (filePath → bundleKey)
|
|
33
|
+
* @param resolveAliases - Resolve aliases from the main Vite config
|
|
34
|
+
* @param defineValues - Define replacements from the main Vite config
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildIsolatedIslands(cwd: string, distDir: string, islands: Map<string, IslandSource>, resolveAliases: any[], defineValues: Record<string, unknown>): Promise<IslandBuildResult[]>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{existsSync as e,statSync as t}from"node:fs";import{resolve as n}from"node:path";function r(e,t){let n=JSON.stringify(e);if(t===`qwik`)return[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
|
+
`);let r=[];t===`lit`&&r.push(`import "@useavalon/lit/client";`),r.push(`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`);let i={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,lit:`@useavalon/lit/client`};return i[t]&&r.push(`export { hydrate as __hydrateIsland } from ${JSON.stringify(i[t])};`),r.join(`
|
|
3
|
+
`)}async function i(e,t){let n=[],{resolve:r}=await import(`node:path`),i={solid:{pkg:`vite-plugin-solid`,integration:`packages/integrations/solid`,esmEntry:`dist/esm/index.mjs`},vue:{pkg:`@vitejs/plugin-vue`,integration:`packages/integrations/vue`,esmEntry:`dist/index.mjs`},svelte:{pkg:`@sveltejs/vite-plugin-svelte`,integration:`packages/integrations/svelte`,esmEntry:`src/index.js`}}[e];if(!i)return n;try{let a=r(r(t,`..`,i.integration),`node_modules`,i.pkg),o=i.esmEntry?r(a,i.esmEntry):a,{pathToFileURL:s}=await import(`node:url`),c=await import(s(o).href);switch(e){case`solid`:{let e=c.default??c;n.push(e({ssr:!1,hot:!1}));break}case`vue`:{let e=c.default??c;n.push(e());break}case`svelte`:{let e=c.svelte??c.default;n.push(e({compilerOptions:{hydratable:!0}}));break}}}catch(t){console.warn(` ⚠ Could not load ${e} plugin: ${t instanceof Error?t.message:t}`)}return n}function a(e){let t={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,lit:`@useavalon/lit/client`}[e];return t?[`import { hydrate } from ${JSON.stringify(t)};`,`export async function loadIntegrationModule() { return { hydrate }; }`].join(`
|
|
4
|
+
`):`export async function loadIntegrationModule() { return {}; }`}async function o(e){let{createRequire:t}=await import(`node:module`),n=t(`${e}/package.json`),r=e=>{try{return n.resolve(e).replace(/\.js$/,`.mjs`)}catch{return null}},i={preact:r(`preact`),"preact/hooks":r(`preact/hooks`),"preact/compat":r(`preact/compat`),"preact/compat/client":r(`preact/compat/client`),"preact/compat/server":r(`preact/compat/server`),"preact/jsx-runtime":r(`preact/jsx-runtime`),react:r(`preact/compat`),"react/jsx-runtime":r(`preact/jsx-runtime`),"react/jsx-dev-runtime":r(`preact/jsx-runtime`),"react-dom":r(`preact/compat`),"react-dom/client":r(`preact/compat/client`),"react-dom/server":r(`preact/compat/server`)};return{name:`avalon:isolated-preact-compat`,enforce:`pre`,resolveId(e){return i[e]||null}}}export async function buildIsolatedIslands(s,c,l,u,d){if(l.size===0)return[];let{build:f}=await import(`vite`);console.log(`🏝️ Building ${l.size} islands in isolation...`);let p=performance.now(),m=[];for(let[,p]of l){let{filePath:l,bundleKey:h,framework:g}=p,_=`islands/${h}.js`;if(g===`qwik`||g===`lit`){m.push({island:_,success:!0});continue}let v=r(l,g),y=`\0isolated-island-entry`,b=`\0virtual:avalon/integration-loader`;try{let r=await i(g,s);await f({configFile:!1,root:s,logLevel:`silent`,plugins:[{name:`avalon:isolated-island-virtual`,resolveId(e){return e===y?e:e===`virtual:avalon/integration-loader`||e===b?b:null},load(e){return e===y?v:e===b?a(g):null}},await o(s),...r],build:{write:!0,outDir:n(s,c),emptyOutDir:!1,minify:`esbuild`,target:`es2020`,rollupOptions:{input:y,output:{format:`es`,entryFileNames:_},preserveEntrySignatures:`exports-only`}},resolve:{alias:u},define:{...d,__DEV__:`false`,__PROD__:`true`,"process.env.NODE_ENV":`"production"`}});let l=n(s,c,_),p=e(l)?t(l).size:0;m.push({island:_,success:!0,size:p}),console.log(` ✅ ${h} (${g}): ${(p/1024).toFixed(1)} KiB`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${h} (${g}): ${t}`),m.push({island:_,success:!1,error:t})}}let h=((performance.now()-p)/1e3).toFixed(1),g=m.filter(e=>e.success).length,_=m.filter(e=>!e.success).length;return console.log(`🏝️ Done in ${h}s: ${g} built${_?`, ${_} failed`:``}`),m}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as e,readdirSync as t}from"node:fs";import{stat as n}from"node:fs/promises";import{createRequire as r}from"node:module";import{dirname as i,join as a,relative as o,resolve as s}from"node:path";import{nitro as c}from"nitro/vite";import{isRunnableDevEnvironment as l}from"vite";import{getUniversalCSSForHead as u}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as d,injectSolidHydrationScriptIfNeeded as f}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as p,discoverScopedMiddleware as m,executeScopedMiddleware as h}from"../middleware/index.js";import{createNitroConfig as g}from"../nitro/config.js";import{createIslandManifestPlugin as _,createNitroBuildPlugin as v,createSourceMapConfig as y,createSourceMapPlugin as b}from"../nitro/index.js";import{collectCssFromModuleGraph as x,injectSsrCss as S}from"../render/collect-css.js";import{generateErrorPage as C,generateFallback404 as w}from"../render/error-pages.js";function T(t){let n=a(i(r(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(n)){let t=n.replace(/\.ts$/,`.js`);if(e(t))return t}return n}function E(t,n){let o=a(i(r(a(process.cwd(),`package.json`)).resolve(`@useavalon/${t}`)),n);if(n.endsWith(`.ts`)&&!e(o)){let t=o.replace(/\.ts$/,`.js`);if(e(t))return t}return o}export const VIRTUAL_MODULE_IDS={PAGE_ROUTES:`virtual:avalon/page-routes`,PAGE_LOADER:`virtual:avalon/page-loader`,ISLAND_MANIFEST:`virtual:avalon/island-manifest`,RUNTIME_CONFIG:`virtual:avalon/runtime-config`,CONFIG:`virtual:avalon/config`,LAYOUTS:`virtual:avalon/layouts`,ASSETS:`virtual:avalon/assets`,RENDERER:`virtual:avalon/renderer`,CLIENT_ENTRY:`virtual:avalon/client-entry`,INTEGRATION_LOADER:`virtual:avalon/integration-loader`};export const RESOLVED_VIRTUAL_IDS={PAGE_ROUTES:`\0`+VIRTUAL_MODULE_IDS.PAGE_ROUTES,PAGE_LOADER:`\0`+VIRTUAL_MODULE_IDS.PAGE_LOADER,ISLAND_MANIFEST:`\0`+VIRTUAL_MODULE_IDS.ISLAND_MANIFEST,RUNTIME_CONFIG:`\0`+VIRTUAL_MODULE_IDS.RUNTIME_CONFIG,CONFIG:`\0`+VIRTUAL_MODULE_IDS.CONFIG,LAYOUTS:`\0`+VIRTUAL_MODULE_IDS.LAYOUTS,ASSETS:`\0`+VIRTUAL_MODULE_IDS.ASSETS,RENDERER:`\0`+VIRTUAL_MODULE_IDS.RENDERER,CLIENT_ENTRY:`\0`+VIRTUAL_MODULE_IDS.CLIENT_ENTRY,INTEGRATION_LOADER:`\0`+VIRTUAL_MODULE_IDS.INTEGRATION_LOADER};export function createNitroIntegration(e,t={}){let n=g(t,e),r={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/]};t.renderer===!1?r.renderer=!1:n.renderer&&(r.renderer=n.renderer),n.publicRuntimeConfig&&(r.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(r.publicAssets=n.publicAssets),n.compressPublicAssets&&(r.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(r.serverEntry=n.serverEntry);let i=n.traceDeps??[];r.traceDeps=[...new Set([`undici`,...i])],n.prerender&&(r.prerender={routes:[],crawlLinks:!1});let a=c(r),o=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),s=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),l=v(e,t),u=_(e,{verbose:e.verbose,generatePreloadHints:!0}),d=b(y(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(a)?a:[a],o,s,l,u,d]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,nitroConfig:n,verbose:r}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,configResolved(e){globalThis.__avalonConfig=t},configureServer(e){globalThis.__viteDevServer=e;let n=null;async function i(){return n||=await m({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),n}function a(){n=null}F(e,t,r,a),i().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let o=e.environments?.ssr,s=!!o&&l(o);s&&N(e,t.integrations,r).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(n,a,o)=>{if(!s)return o();let c=n.url||`/`;if(c.endsWith(`.html`)&&(c=c.slice(0,-5)||`/`),c===`/index`&&(c=`/`),c.startsWith(`/@`)||c.startsWith(`/__`)||c.startsWith(`/node_modules/`)||c.startsWith(`/src/client/`)||c.startsWith(`/packages/`)||c.includes(`.`)&&!c.endsWith(`/`)||c.startsWith(`/api/`))return o();try{if(await j(e,c,n,a,i,r)||await q(e,c,t,a))return;let o=await J(e,c,t);if(o){a.statusCode=200,a.setHeader(`Content-Type`,`text/html`),a.end(o);return}await M(e,c,a,t)}catch(e){console.error(`[SSR Error]`,e),a.statusCode=500,a.setHeader(`Content-Type`,`text/html`),a.end(C(e))}})},buildStart(){}}}async function j(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await h({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),d=performance.now()-o;return d>100&&console.warn(`⚠️ Slow middleware: ${d.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function M(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(w(t))}}async function N(e,t,n){let r=performance.now(),i=[{path:T(`src/render/ssr.ts`),assignTo:`ssr`},{path:T(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:T(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:E(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(G=r),n===`layout`&&(K=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:n,nitroConfig:r,verbose:i}=e,c=null,l=a,u=o,d=s,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=l(e,t.name);if(t.isDirectory()&&t.name!==`node_modules`&&!t.name.startsWith(`.`))m(n);else if(t.isFile()&&t.name.endsWith(`.css`)){let e=u(f,n).replaceAll(`\\`,`/`);p.push(e.startsWith(`/`)?e:`/`+e)}}}catch{}}function h(){p.length=0;for(let e of r.globalCSS??[])p.push(e.startsWith(`/`)?e:`/`+e);n.modules&&m(d(f,n.modules.dir)),m(d(f,n.layoutsDir))}return n.isDev&&h(),{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:e===VIRTUAL_MODULE_IDS.LAYOUTS?RESOLVED_VIRTUAL_IDS.LAYOUTS:e===VIRTUAL_MODULE_IDS.ASSETS?RESOLVED_VIRTUAL_IDS.ASSETS:e===VIRTUAL_MODULE_IDS.RENDERER?RESOLVED_VIRTUAL_IDS.RENDERER:e===VIRTUAL_MODULE_IDS.CLIENT_ENTRY?RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY:e===VIRTUAL_MODULE_IDS.INTEGRATION_LOADER?RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await I(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await L(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?R():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?z(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(c||=await B(n,r,p),c):e===RESOLVED_VIRTUAL_IDS.ASSETS?V(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?H(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await U(n,r):e===RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER?generateIntegrationLoaderModule(n):null},handleHotUpdate({file:e,server:t}){if(e.includes(n.pagesDir)){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_ROUTES);e&&t.moduleGraph.invalidateModule(e)}if(e.includes(`/layouts/`)||e.includes(`_layout`)||e.endsWith(`.css`)){c=null,h();let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.LAYOUTS);e&&t.moduleGraph.invalidateModule(e);let n=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY);n&&t.moduleGraph.invalidateModule(n)}if(e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CONFIG);e&&t.moduleGraph.invalidateModule(e)}}}}function F(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(p(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(G=null,K=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(p(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(p(),r?.())})}async function I(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
1
|
+
import{existsSync as e,readdirSync as t}from"node:fs";import{stat as n}from"node:fs/promises";import{createRequire as r}from"node:module";import{dirname as i,join as a,relative as o,resolve as s}from"node:path";import{nitro as c}from"nitro/vite";import{isRunnableDevEnvironment as l}from"vite";import{getUniversalCSSForHead as u}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as d,injectSolidHydrationScriptIfNeeded as f}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as p,discoverScopedMiddleware as m,executeScopedMiddleware as h}from"../middleware/index.js";import{createNitroConfig as g}from"../nitro/config.js";import{createIslandManifestPlugin as _,createNitroBuildPlugin as v,createSourceMapConfig as y,createSourceMapPlugin as b}from"../nitro/index.js";import{collectCssFromModuleGraph as x,injectSsrCss as S}from"../render/collect-css.js";import{generateErrorPage as C,generateFallback404 as w}from"../render/error-pages.js";function T(t){let n=a(i(r(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(n)){let t=n.replace(/\.ts$/,`.js`);if(e(t))return t}return n}function E(t,n){let o=a(i(r(a(process.cwd(),`package.json`)).resolve(`@useavalon/${t}`)),n);if(n.endsWith(`.ts`)&&!e(o)){let t=o.replace(/\.ts$/,`.js`);if(e(t))return t}return o}export const VIRTUAL_MODULE_IDS={PAGE_ROUTES:`virtual:avalon/page-routes`,PAGE_LOADER:`virtual:avalon/page-loader`,ISLAND_MANIFEST:`virtual:avalon/island-manifest`,RUNTIME_CONFIG:`virtual:avalon/runtime-config`,CONFIG:`virtual:avalon/config`,LAYOUTS:`virtual:avalon/layouts`,ASSETS:`virtual:avalon/assets`,RENDERER:`virtual:avalon/renderer`,CLIENT_ENTRY:`virtual:avalon/client-entry`,INTEGRATION_LOADER:`virtual:avalon/integration-loader`};export const RESOLVED_VIRTUAL_IDS={PAGE_ROUTES:`\0`+VIRTUAL_MODULE_IDS.PAGE_ROUTES,PAGE_LOADER:`\0`+VIRTUAL_MODULE_IDS.PAGE_LOADER,ISLAND_MANIFEST:`\0`+VIRTUAL_MODULE_IDS.ISLAND_MANIFEST,RUNTIME_CONFIG:`\0`+VIRTUAL_MODULE_IDS.RUNTIME_CONFIG,CONFIG:`\0`+VIRTUAL_MODULE_IDS.CONFIG,LAYOUTS:`\0`+VIRTUAL_MODULE_IDS.LAYOUTS,ASSETS:`\0`+VIRTUAL_MODULE_IDS.ASSETS,RENDERER:`\0`+VIRTUAL_MODULE_IDS.RENDERER,CLIENT_ENTRY:`\0`+VIRTUAL_MODULE_IDS.CLIENT_ENTRY,INTEGRATION_LOADER:`\0`+VIRTUAL_MODULE_IDS.INTEGRATION_LOADER};export function createNitroIntegration(e,t={}){let n=g(t,e),r={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/]};t.renderer===!1?r.renderer=!1:n.renderer&&(r.renderer=n.renderer),n.publicRuntimeConfig&&(r.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(r.publicAssets=n.publicAssets),n.compressPublicAssets&&(r.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(r.serverEntry=n.serverEntry);let i=n.traceDeps??[];r.traceDeps=[...new Set([`undici`,...i])],n.prerender&&(r.prerender={routes:[],crawlLinks:!1});let a=c(r),o=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),s=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),l=v(e,t),u=_(e,{verbose:e.verbose,generatePreloadHints:!0}),d=b(y(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(a)?a:[a],o,s,l,u,d]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,nitroConfig:n,verbose:r}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,configResolved(e){globalThis.__avalonConfig=t,globalThis.__avalonHydrationMode=e.command===`serve`?`entry-client`:`per-island`},configureServer(e){globalThis.__viteDevServer=e;let n=null;async function i(){return n||=await m({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),n}function a(){n=null}F(e,t,r,a),i().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let o=e.environments?.ssr,s=!!o&&l(o);s&&N(e,t.integrations,r).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(n,a,o)=>{if(!s)return o();let c=n.url||`/`;if(c.endsWith(`.html`)&&(c=c.slice(0,-5)||`/`),c===`/index`&&(c=`/`),c.startsWith(`/@`)||c.startsWith(`/__`)||c.startsWith(`/node_modules/`)||c.startsWith(`/src/client/`)||c.startsWith(`/packages/`)||c.includes(`.`)&&!c.endsWith(`/`)||c.startsWith(`/api/`))return o();try{if(await j(e,c,n,a,i,r)||await q(e,c,t,a))return;let o=await J(e,c,t);if(o){a.statusCode=200,a.setHeader(`Content-Type`,`text/html`),a.end(o);return}await M(e,c,a,t)}catch(e){console.error(`[SSR Error]`,e),a.statusCode=500,a.setHeader(`Content-Type`,`text/html`),a.end(C(e))}})},buildStart(){}}}async function j(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await h({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),d=performance.now()-o;return d>100&&console.warn(`⚠️ Slow middleware: ${d.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function M(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(w(t))}}async function N(e,t,n){let r=performance.now(),i=[{path:T(`src/render/ssr.ts`),assignTo:`ssr`},{path:T(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:T(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:E(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(G=r),n===`layout`&&(K=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:n,nitroConfig:r,verbose:i}=e,c=null,l=a,u=o,d=s,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=l(e,t.name);if(t.isDirectory()&&t.name!==`node_modules`&&!t.name.startsWith(`.`))m(n);else if(t.isFile()&&t.name.endsWith(`.css`)){let e=u(f,n).replaceAll(`\\`,`/`);p.push(e.startsWith(`/`)?e:`/`+e)}}}catch{}}function h(){p.length=0;for(let e of r.globalCSS??[])p.push(e.startsWith(`/`)?e:`/`+e);n.modules&&m(d(f,n.modules.dir)),m(d(f,n.layoutsDir))}return n.isDev&&h(),{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:e===VIRTUAL_MODULE_IDS.LAYOUTS?RESOLVED_VIRTUAL_IDS.LAYOUTS:e===VIRTUAL_MODULE_IDS.ASSETS?RESOLVED_VIRTUAL_IDS.ASSETS:e===VIRTUAL_MODULE_IDS.RENDERER?RESOLVED_VIRTUAL_IDS.RENDERER:e===VIRTUAL_MODULE_IDS.CLIENT_ENTRY?RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY:e===VIRTUAL_MODULE_IDS.INTEGRATION_LOADER?RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await I(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await L(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?R():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?z(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(c||=await B(n,r,p),c):e===RESOLVED_VIRTUAL_IDS.ASSETS?V(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?H(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await U(n,r):e===RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER?generateIntegrationLoaderModule(n):null},handleHotUpdate({file:e,server:t}){if(e.includes(n.pagesDir)){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_ROUTES);e&&t.moduleGraph.invalidateModule(e)}if(e.includes(`/layouts/`)||e.includes(`_layout`)||e.endsWith(`.css`)){c=null,h();let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.LAYOUTS);e&&t.moduleGraph.invalidateModule(e);let n=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY);n&&t.moduleGraph.invalidateModule(n)}if(e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CONFIG);e&&t.moduleGraph.invalidateModule(e)}}}}function F(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(p(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(G=null,K=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(p(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(p(),r?.())})}async function I(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
2
2
|
export default pageRoutes;
|
|
3
3
|
`}}async function L(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),{relative:r}=await import(`node:path`),i=process.cwd(),a=await n(await t(e.pagesDir,e.modules,i),{developmentMode:e.isDev}),o=[],s=[];for(let e=0;e<a.length;e++){let t=a[e],n=`page_${e}`,c=r(i,t.filePath).replaceAll(`\\`,`/`),l=c.startsWith(`/`)?c:`/`+c;o.push(`import * as ${n} from '${l}';`),s.push(` { pattern: ${JSON.stringify(t.pattern)}, params: ${JSON.stringify(t.params)}, module: ${n} }`)}return[...o,``,`const routes = [`,s.join(`,
|
|
4
4
|
`),`];`,``,`/**`,` * Match a pathname against discovered routes and return the page module.`,` * Uses the same pattern matching as Avalon's route discovery.`,` */`,`export function loadPage(pathname) {`,` const cleanPath = pathname.split('?')[0];`,` for (const route of routes) {`,` if (matchRoute(cleanPath, route.pattern, route.params)) {`,` return route.module;`,` }`,` }`,` return null;`,`}`,``,`function matchRoute(pathname, pattern, paramNames) {`,` // Exact match`,` if (pattern === pathname) return true;`,` // Normalize trailing slashes`,` const normPath = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');`,` const normPattern = pattern === '/' ? '/' : pattern.replace(/\\/$/, '');`,` if (normPath === normPattern) return true;`,` // Dynamic segments: /users/:id matches /users/123`,` if (paramNames.length > 0) {`,` const patternParts = normPattern.split('/');`,` const pathParts = normPath.split('/');`,` if (patternParts.length !== pathParts.length) return false;`,` return patternParts.every((part, i) => part.startsWith(':') || part === pathParts[i]);`,` }`,` return false;`,`}`,``,`export default { loadPage, routes };`,``].join(`
|
|
@@ -10,7 +10,7 @@ export default islandManifest;
|
|
|
10
10
|
`),`];`,``,`function getLayoutsForPath(pathname) {`,` for (const entry of moduleLayouts) {`,` if (entry.prefix === '/' ? pathname === '/' : pathname.startsWith(entry.prefix)) {`,` return entry;`,` }`,` }`,` return null;`,`}`,``,`function injectUniversalAssets(html) {`,` // In dev, inject <link> tags for all discovered CSS files.`,` // The ?direct suffix makes Vite return raw CSS (text/css) instead`,` // of a JS module wrapper, so <link rel="stylesheet"> works.`,` if (process.env.NODE_ENV !== 'production' && _cssLinks.length > 0) {`,` var links = _cssLinks.map(function(href) {`,` return '<link rel="stylesheet" href="' + href + '?direct">';`,` }).join('\\n');`,` if (html.includes('</head>')) {`,` html = html.replace('</head>', links + '\\n</head>');`,` }`,` }`,` const universalCSS = getUniversalCSSForHead(true);`,` if (universalCSS && html.includes('</head>')) {`,` html = html.replace('</head>', universalCSS + '\\n</head>');`,` }`,` const universalHead = getUniversalHeadForInjection(true);`,` if (universalHead && html.includes('</head>')) {`,` html = html.replace('</head>', universalHead + '\\n</head>');`,` }`,` html = injectSolidHydrationScriptIfNeeded(html);`,` return html;`,`}`,``,`export async function wrapWithLayouts(pageHtml, pageModule, context, injectAssets) {`,` const pathname = context.url.pathname;`,` const frontmatter = {`,` ...(pageModule.frontmatter || {}),`,` ...(pageModule.metadata || {}),`,` currentPath: pathname,`,` };`,` const pageLayoutConfig = pageModule.layoutConfig;`,` const skipAll = pageLayoutConfig?.skipLayouts?.includes('_layout');`,``,` const layoutEntry = getLayoutsForPath(pathname);`,` const routeInfo = { path: pathname, params: context.params, query: context.url.searchParams };`,` let html;`,``,` if (!layoutEntry || skipAll) {`,` if (RootLayoutComponent && !skipAll) {`,` const rootProps = {`,` children: h('div', { id: 'app', dangerouslySetInnerHTML: { __html: pageHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const rootResult = RootLayoutComponent(rootProps);`,` const resolvedRoot = rootResult instanceof Promise ? await rootResult : rootResult;`,` html = '<!DOCTYPE html>\\n' + preactRenderToString(resolvedRoot);`,` } else {`,` const title = String(frontmatter.title || 'Avalon');`,` html = [`,` '<!DOCTYPE html>',`,` '<html lang="en">',`,` '<head>',`,` '<meta charset="utf-8">',`,` '<meta name="viewport" content="width=device-width, initial-scale=1">',`,` '<title>' + title + '</title>',`,` '</head>',`,` '<body>',`,` '<div id="app">' + pageHtml + '</div>',`,` '</body>',`,` '</html>',`,` ].join('\\n');`,` }`,` } else {`,` const layoutProps = {`,` children: h('div', { dangerouslySetInnerHTML: { __html: pageHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const layoutResult = layoutEntry.Layout(layoutProps);`,` const resolvedLayout = layoutResult instanceof Promise ? await layoutResult : layoutResult;`,` let wrappedHtml = preactRenderToString(resolvedLayout);`,``,` if (!layoutEntry.skipRoot && RootLayoutComponent) {`,` const rootProps = {`,` children: h('div', { dangerouslySetInnerHTML: { __html: wrappedHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const rootResult = RootLayoutComponent(rootProps);`,` const resolvedRoot = rootResult instanceof Promise ? await rootResult : rootResult;`,` wrappedHtml = preactRenderToString(resolvedRoot);`,` }`,``,` html = '<!DOCTYPE html>\\n' + wrappedHtml;`,` }`,``,` if (injectAssets) {`,` html = injectAssets(html);`,` }`,` return injectUniversalAssets(html);`,`}`,``,`export default { wrapWithLayouts };`,``].join(`
|
|
11
11
|
`)}function V(e){let t=e.clientEntry??`app/entry-client`;return[`// Auto-generated by Avalon — do not edit`,`// @ts-ignore — virtual import resolved by Nitro's Vite assets plugin at build time`,`import clientAssets from '${t.startsWith(`/`)?t:`/`+t}?assets=client';`,``,`function buildAssetTags() {`,` const cssLinks = (clientAssets?.css ?? [])`,` .map(attr => '<link rel="stylesheet" href="' + attr.href + '">')`,` .join('\\n');`,` const jsPreloads = (clientAssets?.js ?? [])`,` .map(attr => '<link rel="modulepreload" href="' + attr.href + '">')`,` .join('\\n');`,` const entryScript = clientAssets?.entry`,` ? '<script type="module" src="' + clientAssets.entry + '"><\/script>'`,` : '';`,` return { cssLinks, jsPreloads, entryScript };`,`}`,``,`export function injectAssets(html) {`,` const { cssLinks, jsPreloads, entryScript } = buildAssetTags();`,` if (html.includes('</head>')) {`,` html = html.replace('</head>', cssLinks + '\\n' + jsPreloads + '\\n</head>');`,` }`,` if (html.includes('</body>')) {`,` html = html.replace('</body>', entryScript + '\\n</body>');`,` }`,` return html;`,`}`,``,`export { clientAssets };`,`export default { injectAssets, clientAssets };`,``].join(`
|
|
12
12
|
`)}function H(e){let t=Array.isArray(e.integrations)?e.integrations:[],n=[],r=[];for(let e of t){let t=`${e}Integration`;n.push(`import { ${t} } from '@useavalon/${e}';`),r.push(`registry.register(${t});`)}return[`// Auto-generated by Avalon — do not edit`,`import { createNitroRenderer } from '@useavalon/avalon/nitro/renderer';`,`import { registerBuiltinDirectives } from '@useavalon/avalon';`,`import { registry } from '@useavalon/avalon/islands/integration-registry';`,`import avalonConfig from 'virtual:avalon/config';`,`import { loadPage } from 'virtual:avalon/page-loader';`,`import { wrapWithLayouts } from 'virtual:avalon/layouts';`,`import { injectAssets } from 'virtual:avalon/assets';`,...n,``,`// Pre-register framework integrations for SSR`,...r,``,`// Register built-in custom hydration directives (on:delay, on:scroll, etc.)`,`registerBuiltinDirectives();`,``,`export default createNitroRenderer({`,` avalonConfig,`,` isDev: avalonConfig.isDev,`,` resolvePageRoute: async (pathname) => {`,` const mod = loadPage(pathname);`,` if (!mod || !('default' in mod)) return null;`,` return { filePath: '[virtual:' + pathname + ']', pattern: pathname, params: {} };`,` },`,` loadPageModule: async (filePath) => {`,` const match = filePath.match(/^\\[virtual:(.+)\\]$/);`,` const pathname = match ? match[1] : filePath;`,` const mod = loadPage(pathname);`,` if (mod) return mod;`,` return { default: () => null, metadata: { title: 'Avalon' } };`,` },`,` wrapWithLayouts: (pageHtml, pageModule, context) =>`,` wrapWithLayouts(pageHtml, pageModule, context, injectAssets),`,`});`,``].join(`
|
|
13
|
-
`)}async function U(e,t){let{getAllLayoutDirs:n}=await import(`./module-discovery.js`),{readdir:r}=await import(`node:fs/promises`),{relative:i,join:a}=await import(`node:path`),o=process.cwd(),s=await n(e.layoutsDir,e.modules,o),c=[];for(let{dir:e}of s)try{let t=await r(e,{withFileTypes:!0});for(let n of t){if(!n.isFile()||!n.name.endsWith(`.css`))continue;let t=i(o,a(e,n.name)).replaceAll(`\\`,`/`),r=t.startsWith(`/`)?t:`/`+t;c.push(r)}}catch{}let l=[`// Auto-generated by Avalon — do not edit`,`// Island hydration runtime
|
|
13
|
+
`)}async function U(e,t){let{getAllLayoutDirs:n}=await import(`./module-discovery.js`),{readdir:r}=await import(`node:fs/promises`),{relative:i,join:a}=await import(`node:path`),o=process.cwd(),s=await n(e.layoutsDir,e.modules,o),c=[];for(let{dir:e}of s)try{let t=await r(e,{withFileTypes:!0});for(let n of t){if(!n.isFile()||!n.name.endsWith(`.css`))continue;let t=i(o,a(e,n.name)).replaceAll(`\\`,`/`),r=t.startsWith(`/`)?t:`/`+t;c.push(r)}}catch{}let l=globalThis.__avalonConfig?.isDev??e.isDev?`@useavalon/avalon/client/main`:`@useavalon/avalon/client/main-slim`,u=[`// Auto-generated by Avalon — do not edit`,`// Island hydration runtime`];(globalThis.__avalonHydrationMode??`entry-client`)===`entry-client`?u.push(`import '${l}';`):u.push(`// Per-island hydration mode — no shared runtime needed`),u.push(``);let d=t.globalCSS??[];for(let e of d){let t=e.startsWith(`/`)?e:`/`+e;u.push(`// Global CSS`),u.push(`import '${t}';`)}if(d.length>0&&u.push(``),c.length>0){u.push(`// Layout CSS (auto-discovered)`);let e=0;for(let t of c)t.includes(`.module.`)?(u.push(`import _lcss${e} from '${t}';`),e++):u.push(`import '${t}';`)}return u.push(``),u.join(`
|
|
14
14
|
`)}export function generateIntegrationLoaderModule(e){let t=(e.integrations??[]).map(e=>typeof e==`string`?e:e.name),n={preact:`@useavalon/preact/client`,react:`@useavalon/preact/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,solid:`@useavalon/solid/client`,lit:`@useavalon/lit/client`,qwik:`@useavalon/qwik/client`},r={preact:`@useavalon/preact/client/hmr`,react:`@useavalon/react/client/hmr`,vue:`@useavalon/vue/client/hmr`,svelte:`@useavalon/svelte/client/hmr`,solid:`@useavalon/solid/client/hmr`,lit:`@useavalon/lit/client/hmr`,qwik:`@useavalon/qwik/client/hmr`},i=t.includes(`lit`),a=t.includes(`solid`),o=(globalThis.__avalonConfig?.isDev??e.isDev)===!0,s=[`// Auto-generated by Avalon — only includes configured integrations`,``];a&&!o&&(s.push(`// --- Inlined Solid adapter (production) ---`),s.push(`// Eliminates a separate chunk + network request for the Solid client adapter.`),s.push(`// Only imports hydrate/createComponent — no render() fallback (saves ~1-2 KiB).`),s.push(`function _ensureHydrationContext() {`),s.push(` if (!globalThis._$HY) {`),s.push(` globalThis._$HY = { events: [], completed: new WeakSet(), r: {}, fe() {} };`),s.push(` }`),s.push(`}`),s.push(``),s.push(`async function _solidHydrate(container, Component, props) {`),s.push(` if (!container) throw new Error("Container element is required for hydration");`),s.push(` if (!Component || typeof Component !== "function") {`),s.push(` throw new Error("Invalid Solid component: expected function, got " + typeof Component);`),s.push(` }`),s.push(` var el = container;`),s.push(` var renderId = el.dataset.solidRenderId || el.dataset.renderId;`),s.push(` var { hydrate: solidHydrate, createComponent } = await import("solid-js/web");`),s.push(` _ensureHydrationContext();`),s.push(` solidHydrate(function() { return createComponent(Component, props || {}); }, el, { renderId: renderId || "" });`),s.push(`}`),s.push(``),s.push(`var _solidModule = { hydrate: _solidHydrate };`),s.push(``)),s.push(`// --- loadIntegrationModule ---`),s.push(`export async function loadIntegrationModule(framework) {`),s.push(` switch (framework) {`);for(let e of t){let t=n[e];t&&(e===`solid`&&!o?(s.push(` case "solid":`),s.push(` return _solidModule;`)):e===`react`?s.push(` case "react":`):e===`preact`?(s.push(` case "preact":`),s.push(` return import("${t}");`)):(s.push(` case "${e}":`),s.push(` return import("${t}");`)))}t.includes(`react`)&&!t.includes(`preact`)&&s.push(` return import("${n.react}");`),s.push(` default:`),s.push(" throw new Error(`Unknown or unconfigured framework: ${framework}`);"),s.push(` }`),s.push(`}`),s.push(``),s.push(`// --- Lit hydration pre-load (only if Lit is configured) ---`),i?(s.push(`export async function preLitHydration() {`),s.push(` await import("@useavalon/lit/client");`),s.push(`}`)):s.push(`export async function preLitHydration() {}`),s.push(``),s.push(`// --- HMR adapter loader ---`),s.push(`export async function loadHMRAdapter(framework) {`),s.push(` switch (framework) {`);for(let e of t){let t=r[e];t&&(s.push(` case "${e}":`),s.push(` return import("${t}").then(m => m.${e}Adapter);`))}return s.push(` default: return null;`),s.push(` }`),s.push(`}`),s.join(`
|
|
15
15
|
`)}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const W=`<!--AVALON_STREAM_BOUNDARY-->`;let G=null,K=null;async function q(e,t,n,r){if(!n.modules)return!1;let i=t.split(`?`)[0],a=await Q(i,n,e);if(!a)return!1;try{let t=await e.ssrLoadModule(a),n=t.default;if(!n)return!1;let o=t.layoutConfig,s=await x(e,a),c=await Z(i,e),l=[];for(let t of c){let n=await e.ssrLoadModule(t);l.push({file:t,module:n})}for(let t of c){let n=await x(e,t);s.push(...n)}if(l.length===0)return!1;let{render:p}=await e.ssrLoadModule(`preact-render-to-string`),{h:m}=await e.ssrLoadModule(`preact`),h=o?.skipLayouts||[],g=l.filter(({file:e})=>{let t=e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``;return!h.includes(t)}),_=t.frontmatter,v=t.metadata,y={children:null,frontmatter:{..._,...v,currentPath:i},params:{},url:i},b=[],S=[];for(let e of g){let t=e.module.default;if(!(!t||typeof t!=`function`))try{let n=t({...y,children:m(`div`,null,`test`)}),r=p(n instanceof Promise?await n:n);r.trim().startsWith(`<html`)||r.includes(`<!DOCTYPE`)?b.push(e):S.push(e)}catch{S.push(e)}}if(b.length===0)return!1;let{module:C}=b[b.length-1],w=C.default;if(!w||typeof w!=`function`)return!1;let T;try{let e=w({...y,children:m(`div`,{dangerouslySetInnerHTML:{__html:W}})});T=p(e instanceof Promise?await e:e)}catch{return!1}let E=T.indexOf(W);if(E===-1)return!1;let D=T.slice(0,E),O=T.slice(E+29),k=D;if(s.length>0){let e=`<style data-avalon-ssr-css>${s.join(`
|
|
16
16
|
`)}</style>`;k=D.includes(`</head>`)?D.replace(`</head>`,`${e}\n</head>`):D+e}k.trim().toLowerCase().startsWith(`<!doctype`)||(k=`<!DOCTYPE html>
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
* Islands are detected by usage - any component used with an `island` prop in pages
|
|
10
10
|
* or layouts is automatically treated as an island. No fixed islands directory required.
|
|
11
11
|
*/
|
|
12
|
-
import type { Plugin, PluginOption, ViteDevServer } from
|
|
13
|
-
import type { AvalonPluginConfig, IntegrationName, ResolvedAvalonConfig } from
|
|
14
|
-
import type { NitroConfigOutput } from
|
|
12
|
+
import type { Plugin, PluginOption, ViteDevServer } from "vite";
|
|
13
|
+
import type { AvalonPluginConfig, IntegrationName, ResolvedAvalonConfig } from "./types.ts";
|
|
14
|
+
import type { NitroConfigOutput } from "../nitro/config.ts";
|
|
15
15
|
declare global {
|
|
16
16
|
var __avalonConfig: ResolvedAvalonConfig | undefined;
|
|
17
17
|
var __viteDevServer: ViteDevServer | undefined;
|
|
18
18
|
var __nitroConfig: NitroConfigOutput | undefined;
|
|
19
|
+
/** Hydration mode — automatically set: "entry-client" in dev (HMR), "per-island" in production */
|
|
20
|
+
var __avalonHydrationMode: "entry-client" | "per-island" | undefined;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Collects Vite plugins from all activated integrations.
|
|
@@ -47,5 +49,5 @@ export declare function getPagesDir(): string;
|
|
|
47
49
|
export declare function getLayoutsDir(): string;
|
|
48
50
|
export declare function getNitroConfig(): NitroConfigOutput | undefined;
|
|
49
51
|
export declare function isNitroEnabled(): boolean;
|
|
50
|
-
export type { AvalonPluginConfig, IntegrationName, ResolvedAvalonConfig, ImageConfig, ResolvedImageConfig, } from
|
|
51
|
-
export type { AvalonNitroConfig, NitroConfigOutput } from
|
|
52
|
+
export type { AvalonPluginConfig, IntegrationName, ResolvedAvalonConfig, ImageConfig, ResolvedImageConfig, } from "./types.ts";
|
|
53
|
+
export type { AvalonNitroConfig, NitroConfigOutput } from "../nitro/config.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import{dirname as t,join as n}from"node:path";import{resolveConfig as r,checkDirectoriesExist as i}from"./config.js";import{activateIntegrations as a,activateSingleIntegration as o}from"./integration-activator.js";import{discoverIntegrationsFromIslandUsage as s}from"./auto-discover.js";import{validateActiveIntegrations as c,formatValidationResults as l}from"./validation.js";import{createMDXPlugin as u}from"../build/mdx-plugin.js";import{mdxIslandTransform as d}from"../build/mdx-island-transform.js";import{pageIslandTransform as f}from"../build/page-island-transform.js";import{registry as p}from"../core/integrations/registry.js";import{createNitroIntegration as m}from"./nitro-integration.js";import{islandSidecarPlugin as h}from"./island-sidecar-plugin.js";import{createImagePlugin as g}from"./image-optimization.js";import{islandClientBundlerPlugin as _}from"../build/island-client-bundler.js";export async function collectIntegrationPlugins(e,t=!1){let n=[],r=[];for(let i of e){let e=await
|
|
1
|
+
import{createRequire as e}from"node:module";import{dirname as t,join as n}from"node:path";import{resolveConfig as r,checkDirectoriesExist as i}from"./config.js";import{activateIntegrations as a,activateSingleIntegration as o}from"./integration-activator.js";import{discoverIntegrationsFromIslandUsage as s}from"./auto-discover.js";import{validateActiveIntegrations as c,formatValidationResults as l}from"./validation.js";import{createMDXPlugin as u}from"../build/mdx-plugin.js";import{mdxIslandTransform as d}from"../build/mdx-island-transform.js";import{pageIslandTransform as f}from"../build/page-island-transform.js";import{registry as p}from"../core/integrations/registry.js";import{createNitroIntegration as m}from"./nitro-integration.js";import{islandSidecarPlugin as h}from"./island-sidecar-plugin.js";import{createImagePlugin as g}from"./image-optimization.js";import{islandClientBundlerPlugin as _}from"../build/island-client-bundler.js";import{islandCodeSplittingPlugin as v}from"../build/island-code-splitting.js";export async function collectIntegrationPlugins(e,t=!1){let n=[],r=[];for(let i of e){let e=await y(i,t);i===`lit`?r.push(...e):n.push(...e)}return[...r,...n]}async function y(e,t){let n=p.get(e);if(!n||typeof n.vitePlugin!=`function`)return[];try{let e=await n.vitePlugin();return(Array.isArray(e)?e:[e]).filter(e=>e!=null)}catch(t){return console.warn(`[avalon] Failed to load vite plugin for ${e}:`,t instanceof Error?t.message:t),[]}}async function b(e,t){let n=new Set;try{let r=await s(e.pagesDir,e.layoutsDir,t,e.modules?.dir);for(let t of r)e.integrations.includes(t)&&n.add(t)}catch{for(let t of e.integrations)n.add(t)}return n}async function x(e){if(!e.lazyIntegrations||e.integrations.length===0)return[...e.integrations];let t=await b(e);return t.size===0?[...e.integrations]:Array.from(t)}async function S(e){try{let t=await u({jsxImportSource:e.mdx.jsxImportSource,syntaxHighlighting:e.mdx.syntaxHighlighting,remarkPlugins:e.mdx.remarkPlugins,rehypePlugins:e.mdx.rehypePlugins,development:!0});return t.push(d({verbose:e.verbose})),t}catch(t){return e.showWarnings&&console.warn(`⚠️ Could not configure MDX plugin:`,t),[]}}function C(e,t,n){let{plugins:r,nitroOptions:i}=m(e,t);return globalThis.__nitroConfig=i,{plugins:r,options:i}}async function w(e,t,n){if(e.autoDiscoverIntegrations)try{let r=await s(e.pagesDir,e.layoutsDir,t,e.modules?.dir);for(let t of r)if(!n.has(t))try{await o(t,n,e.verbose)}catch(n){e.showWarnings&&console.warn(` ⚠️ Could not auto-load integration: ${t}`,n)}}catch(t){e.showWarnings&&console.warn(` ⚠️ Auto-discovery failed:`,t)}}function T(e,t){if(!e.validateIntegrations||t.size===0)return;let n=c(t,e.showWarnings);n.allValid||(console.error(l(n)),e.showWarnings&&console.warn(` ⚠️ Some integrations have validation issues.`))}export async function avalon(o){let s,c,l=new Set,u=r(o,!0),d=await x(u);d.length>0&&await a({...u,integrations:d},l);let p=await S(u),m=await g(u.image,u.verbose),y=[];l.size>0&&(y=await collectIntegrationPlugins(l,u.verbose));let b=[];if(o?.nitro){let{plugins:e}=C(u,o.nitro,u.verbose);b=e}let E=h({verbose:u.verbose}),D=e(import.meta.url),O=null;try{O=n(t(D.resolve(`@useavalon/avalon/client`)),`main.js`)}catch{}let k=new Set([`preact`,`react`,`vue`,`svelte`,`solid`,`lit`,`qwik`].flatMap(e=>[`/@useavalon/${e}/client`,`/@useavalon/${e}/client/hmr`,`@useavalon/${e}/client`,`@useavalon/${e}/client/hmr`])),A={name:`avalon`,enforce:`pre`,config(){let e={preact:[`preact`,`preact/hooks`],react:[`react`,`react-dom`,`react-dom/client`],vue:[`vue`],svelte:[`svelte`,`svelte/internal`],solid:[`solid-js`,`solid-js/web`],lit:[`lit`,`@lit-labs/ssr-client`],qwik:[`@builder.io/qwik`]},t=d.flatMap(t=>e[t]??[]);return{oxc:{exclude:[/node_modules\/@useavalon\/.*\.tsx?$/]},ssr:{noExternal:[/^@useavalon\//]},optimizeDeps:{exclude:[`@useavalon/avalon`,...d.map(e=>`@useavalon/${e}`)],include:t}}},configResolved(e){c=e,s=r(o,e.command===`serve`),globalThis.__avalonConfig=s,i(s,e.root)},async resolveId(e){if(e===`/src/client/main.js`&&O)return O;if(k.has(e)){let t=e.startsWith(`/`)?e.slice(1):e;return(await this.resolve(t))?.id??null}return null},async transform(e,t){if(t.includes(`@useavalon/`)&&/\.tsx?$/.test(t)){let{transform:n}=await import(`oxc-transform`),r=await n(t,e,{sourcemap:!0,typescript:{onlyRemoveTypeImports:!1}});return{code:r.code,map:r.map,moduleType:`js`}}},async buildStart(){await w(s,c?.root,l),T(s,l)},configureServer(e){globalThis.__viteDevServer=e}},j=y.filter(e=>e.name?.includes(`lit`)),M=y.filter(e=>!e.name?.includes(`lit`));return[f({pagesDir:u.pagesDir,layoutsDir:u.layoutsDir,modules:u.modules,verbose:u.verbose}),_(u,o?.nitro),v(u,o?.nitro),...m,...j,...p,A,E,...b,...M]}export function getResolvedConfig(){return globalThis.__avalonConfig}export function getPagesDir(){return globalThis.__avalonConfig?.pagesDir??`src/pages`}export function getLayoutsDir(){return globalThis.__avalonConfig?.layoutsDir??`src/layouts`}export function getNitroConfig(){return globalThis.__nitroConfig}export function isNitroEnabled(){return globalThis.__nitroConfig!==void 0}
|