@useavalon/avalon 0.1.65 → 0.1.67
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 +68 -0
- package/dist/src/build/bundle-audit.js +2 -2
- package/dist/src/build/island-client-bundler.d.ts +3 -2
- package/dist/src/build/island-client-bundler.js +3 -3
- package/dist/src/client/adapters/lit-adapter.js +1 -1
- package/dist/src/client/hmr-coordinator.js +1 -1
- package/dist/src/client/main-slim.js +1 -1
- package/dist/src/client/strategies.js +1 -1
- package/dist/src/post-build/inline-islands.js +2 -2
- package/dist/src/post-build/isolated-island-builder.d.ts +44 -5
- package/dist/src/post-build/isolated-island-builder.js +3 -4
- package/package.json +15 -2
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @module build/bundle-audit
|
|
12
12
|
*/
|
|
13
13
|
import type { Plugin } from "vite";
|
|
14
|
+
import { z } from "zod";
|
|
14
15
|
/** Represents a single chunk in the build output. */
|
|
15
16
|
export interface ChunkInfo {
|
|
16
17
|
fileName: string;
|
|
@@ -42,6 +43,14 @@ export interface DuplicatedModule {
|
|
|
42
43
|
/** File names of chunks containing this module. */
|
|
43
44
|
chunks: string[];
|
|
44
45
|
}
|
|
46
|
+
/** Per-framework size thresholds for island chunks (in bytes). */
|
|
47
|
+
export interface PerFrameworkThresholds {
|
|
48
|
+
solid: number;
|
|
49
|
+
preact: number;
|
|
50
|
+
react: number;
|
|
51
|
+
vue: number;
|
|
52
|
+
svelte: number;
|
|
53
|
+
}
|
|
45
54
|
/** Options for the bundle audit Vite plugin. */
|
|
46
55
|
export interface BundleAuditOptions {
|
|
47
56
|
/** Maximum allowed size (in bytes) for any single island chunk. Default: 20480 (20 KiB). */
|
|
@@ -52,11 +61,69 @@ export interface BundleAuditOptions {
|
|
|
52
61
|
logReport?: boolean;
|
|
53
62
|
/** Whether to emit warnings for oversized chunks. Default: true. */
|
|
54
63
|
warnOnOversized?: boolean;
|
|
64
|
+
/** Per-framework size thresholds for island chunks. When provided, islands are also checked against their framework-specific limit. */
|
|
65
|
+
perFrameworkThresholds?: Partial<PerFrameworkThresholds>;
|
|
66
|
+
/** Path to the benchmark baseline JSON file. When set, enables benchmark comparison mode. */
|
|
67
|
+
benchmarkBaseline?: string;
|
|
68
|
+
/** Maximum allowed regression in bytes before flagging. Default: 500. */
|
|
69
|
+
regressionThreshold?: number;
|
|
70
|
+
}
|
|
71
|
+
/** Zod schema for the benchmark baseline file used to track island bundle sizes over time. */
|
|
72
|
+
export declare const BenchmarkBaselineSchema: z.ZodObject<{
|
|
73
|
+
version: z.ZodNumber;
|
|
74
|
+
timestamp: z.ZodString;
|
|
75
|
+
islands: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
76
|
+
size: z.ZodNumber;
|
|
77
|
+
framework: z.ZodString;
|
|
78
|
+
}, z.core.$strip>>;
|
|
79
|
+
references: z.ZodObject<{
|
|
80
|
+
astroSolidCounter: z.ZodNumber;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
/** Benchmark baseline data parsed from the baseline JSON file. */
|
|
84
|
+
export type BenchmarkBaseline = z.infer<typeof BenchmarkBaselineSchema>;
|
|
85
|
+
/** Result of comparing a single island's current size against the benchmark baseline. */
|
|
86
|
+
export interface BenchmarkResult {
|
|
87
|
+
island: string;
|
|
88
|
+
currentSize: number;
|
|
89
|
+
baselineSize: number | null;
|
|
90
|
+
deltaBytes: number;
|
|
91
|
+
deltaPercent: number;
|
|
92
|
+
framework: string;
|
|
93
|
+
astroReferenceSize?: number;
|
|
55
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Compare current island sizes against a stored benchmark baseline.
|
|
97
|
+
*
|
|
98
|
+
* This is a pure function — the caller is responsible for loading and parsing
|
|
99
|
+
* the baseline JSON file. For each island in `currentIslands`, the function
|
|
100
|
+
* looks up the baseline entry by island name and computes the delta.
|
|
101
|
+
*
|
|
102
|
+
* @param currentIslands - Array of current island build results with name, size, and framework
|
|
103
|
+
* @param baseline - Parsed benchmark baseline data
|
|
104
|
+
* @param regressionThreshold - Maximum allowed regression in bytes before flagging (default: 500)
|
|
105
|
+
* @returns Results per island and whether any regression was detected
|
|
106
|
+
*/
|
|
107
|
+
export declare function compareBenchmarkBaseline(currentIslands: Array<{
|
|
108
|
+
island: string;
|
|
109
|
+
size: number;
|
|
110
|
+
framework: string;
|
|
111
|
+
}>, baseline: BenchmarkBaseline, regressionThreshold?: number): {
|
|
112
|
+
results: BenchmarkResult[];
|
|
113
|
+
hasRegression: boolean;
|
|
114
|
+
};
|
|
56
115
|
/** Default per-chunk size threshold: 20 KiB */
|
|
57
116
|
export declare const DEFAULT_CHUNK_THRESHOLD: number;
|
|
58
117
|
/** Default total JS size threshold: 20 KiB (from requirements) */
|
|
59
118
|
export declare const DEFAULT_TOTAL_THRESHOLD: number;
|
|
119
|
+
/** Default per-framework island chunk thresholds. Solid: 6 KiB, others: 10 KiB. */
|
|
120
|
+
export declare const DEFAULT_PER_FRAMEWORK_THRESHOLDS: PerFrameworkThresholds;
|
|
121
|
+
/**
|
|
122
|
+
* Detect the framework from an island chunk file name.
|
|
123
|
+
* Looks for `.solid.`, `.preact.`, `.react.`, `.vue.`, `.svelte.` in the file name.
|
|
124
|
+
* Returns the framework name or `null` if none is detected.
|
|
125
|
+
*/
|
|
126
|
+
export declare function detectFrameworkFromFileName(fileName: string): string | null;
|
|
60
127
|
/** Shape of a bundle asset — loose enough to handle both Rollup and Rolldown. */
|
|
61
128
|
export type BundleAsset = Record<string, unknown> & {
|
|
62
129
|
type: string;
|
|
@@ -87,6 +154,7 @@ export declare function findDevOnlyModules(chunks: ChunkInfo[]): Array<{
|
|
|
87
154
|
export declare function generateAuditReport(chunks: ChunkInfo[], options?: {
|
|
88
155
|
chunkSizeThreshold?: number;
|
|
89
156
|
totalSizeThreshold?: number;
|
|
157
|
+
perFrameworkThresholds?: Partial<PerFrameworkThresholds>;
|
|
90
158
|
}): BundleAuditReport;
|
|
91
159
|
/**
|
|
92
160
|
* Format a byte count as a human-readable KiB string.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const DEFAULT_CHUNK_THRESHOLD=20*1024;export const DEFAULT_TOTAL_THRESHOLD=20*1024;const
|
|
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:
|
|
1
|
+
import{readFileSync as e}from"node:fs";import{z as t}from"zod";export const BenchmarkBaselineSchema=t.object({version:t.number(),timestamp:t.string(),islands:t.record(t.string(),t.object({size:t.number(),framework:t.string()})),references:t.object({astroSolidCounter:t.number()})});export function compareBenchmarkBaseline(e,t,n=500){let r=t.references.astroSolidCounter,i=e.map(e=>{let n=t.islands[e.island];if(n){let t=e.size-n.size,i=t/n.size*100;return{island:e.island,currentSize:e.size,baselineSize:n.size,deltaBytes:t,deltaPercent:i,framework:e.framework,astroReferenceSize:r}}return{island:e.island,currentSize:e.size,baselineSize:null,deltaBytes:0,deltaPercent:0,framework:e.framework,astroReferenceSize:r}});return{results:i,hasRegression:i.some(e=>e.deltaBytes>n)}}export const DEFAULT_CHUNK_THRESHOLD=20*1024;export const DEFAULT_TOTAL_THRESHOLD=20*1024;export const DEFAULT_PER_FRAMEWORK_THRESHOLDS={solid:6*1024,preact:10*1024,react:10*1024,vue:10*1024,svelte:10*1024};const s=[`.solid.`,`.preact.`,`.react.`,`.vue.`,`.svelte.`];export function detectFrameworkFromFileName(e){for(let t of s)if(e.includes(t))return t.slice(1,-1);return null}const l=[/\/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 n of e)for(let e of n.moduleIds)for(let r of l)r.test(e)&&t.push({chunkFileName:n.fileName,moduleId:e,pattern:r.source});return t}export function generateAuditReport(e,t={}){let n=t.chunkSizeThreshold??DEFAULT_CHUNK_THRESHOLD,r=e.reduce((e,t)=>e+t.size,0),a=e.filter(e=>e.isIsland).reduce((e,t)=>e+t.size,0),s=new Set(e.filter(e=>e.isIsland&&e.size>n).map(e=>e.fileName)),l=new Set;if(t.perFrameworkThresholds){let n={...DEFAULT_PER_FRAMEWORK_THRESHOLDS,...t.perFrameworkThresholds};for(let t of e){if(!t.isIsland)continue;let e=detectFrameworkFromFileName(t.fileName);if(e&&e in n){let r=n[e];t.size>r&&l.add(t.fileName)}}}let u=new Set([...s,...l]);return{chunks:e,totalJsBytes:r,islandJsBytes:a,oversizedChunks:e.filter(e=>u.has(e.fileName)),duplicatedModules:findDuplicatedModules(e)}}export function formatKiB(e){return`${(e/1024).toFixed(2)} KiB`}function f(e){return e.isIsland?` [island]`:e.isEntry?` [entry]`:` `}function p(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=f(n),r=p(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=p(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:a=DEFAULT_CHUNK_THRESHOLD,totalSizeThreshold:o=20480,logReport:s=!0,warnOnOversized:l=!0,perFrameworkThresholds:d,benchmarkBaseline:f,regressionThreshold:p}=t,h=!1;return{name:`avalon:bundle-audit`,enforce:`post`,config(e,{command:t}){h=t===`build`},configResolved(e){if(h&&s){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(t,i){if(!h)return;let m=extractChunks(i),g=generateAuditReport(m,{chunkSizeThreshold:a,totalSizeThreshold:o,perFrameworkThresholds:d});if(s&&console.log(formatAuditReport(g)),l&&g.oversizedChunks.length>0)for(let e of g.oversizedChunks)this.warn(`Island chunk "${e.fileName}" is ${formatKiB(e.size)} (threshold: ${formatKiB(a)})`);l&&g.totalJsBytes>o&&this.warn(`Total JS size ${formatKiB(g.totalJsBytes)} exceeds threshold of ${formatKiB(o)}`);let _=findDevOnlyModules(m);if(_.length>0)for(let e of _)this.warn(`Dev-only module "${e.moduleId}" found in chunk "${e.chunkFileName}"`);if(f)try{let t=e(f,`utf-8`),i=BenchmarkBaselineSchema.parse(JSON.parse(t)),{results:a,hasRegression:o}=compareBenchmarkBaseline(g.chunks.filter(e=>e.isIsland).map(e=>({island:e.fileName,size:e.size,framework:detectFrameworkFromFileName(e.fileName)??`unknown`})),i,p);for(let e of a){let t=e.baselineSize==null?`N/A`:formatKiB(e.baselineSize),n=e.baselineSize==null?`new`:`${e.deltaBytes>0?`+`:``}${e.deltaBytes}B (${e.deltaPercent.toFixed(1)}%)`;console.log(`📊 [benchmark] ${e.island}: ${formatKiB(e.currentSize)} (baseline: ${t}, delta: ${n})`)}o&&this.error(`Bundle size regression detected — one or more islands exceed the baseline by more than the allowed threshold`)}catch(e){if(e.code===`ENOENT`)this.warn(`Benchmark baseline file not found: ${f}`);else throw e}}}}
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* include all CSS files from the client build output.
|
|
11
11
|
*/
|
|
12
12
|
import type { Plugin } from "vite";
|
|
13
|
-
import type { ResolvedAvalonConfig } from "../vite-plugin/types.ts";
|
|
14
13
|
import type { AvalonNitroConfig } from "../nitro/config.ts";
|
|
14
|
+
import type { TreeshakeConfig } from "../post-build/isolated-island-builder.ts";
|
|
15
|
+
import type { ResolvedAvalonConfig } from "../vite-plugin/types.ts";
|
|
15
16
|
/**
|
|
16
17
|
* Creates a Vite plugin that emits island components as separate client chunks.
|
|
17
18
|
*/
|
|
18
|
-
export declare function islandClientBundlerPlugin(config: ResolvedAvalonConfig,
|
|
19
|
+
export declare function islandClientBundlerPlugin(config: ResolvedAvalonConfig, _nitroConfig?: AvalonNitroConfig, treeshakeOverrides?: Partial<TreeshakeConfig>): Plugin;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{existsSync as e,readdirSync as t,readFileSync as n,statSync as r}from"node:fs";import{dirname as i,relative as a,resolve as o}from"node:path";export function islandClientBundlerPlugin(e,t,n){let r=process.cwd(),i=new Map,a=!1,o=c(e,r);for(let e of o)d(e,r,i);let s=`\0avalon-island-entry:`,l=!1,u=[],f={},p=`dist`;return{name:`avalon:island-client-bundler`,enforce:`pre`,configResolved(e){l=e.command===`serve`,a=e.command===`build`,u=e.resolve?.alias??[],f=e.define??{},p=e.build?.outDir??`dist`},resolveId(e){return e.startsWith(s)?e:null},load(e){if(!e.startsWith(s))return null;let t=e.slice(21),n=JSON.stringify(t);if(t.includes(`.qwik.`))return a?[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
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.`),
|
|
4
|
-
`)},async buildStart(){if(
|
|
3
|
+
`);let r=t.includes(`.lit.`),i=t.includes(`.solid.`),o=t.includes(`.preact.`),c=t.includes(`.react.`),l=t.includes(`.vue.`),u=t.includes(`.svelte.`),d=[];if(r&&a&&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;`),a){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=i?`solid`:o?`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(l)return;let t=this.environment;if(!(t&&t.name!==`client`)&&i.size>0){for(let[,e]of i)this.emitFile({type:`chunk`,id:s+e.filePath,fileName:`islands/${e.bundleKey}.js`,preserveSignature:`exports-only`});e.verbose&&console.log(`🏝️ Emitting ${i.size} island client bundles`)}},async closeBundle(){if(l||!a||i.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 i){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(r,p,t,u,f,n?{treeshake:n}:void 0)}}}function c(n,r){let i=[];if(n.pagesDir){let t=o(r,n.pagesDir);e(t)&&i.push(t)}if(n.layoutsDir){let t=o(r,n.layoutsDir);e(t)&&i.push(t)}if(n.modules){let a=o(r,n.modules.dir);if(e(a))try{for(let n of t(a,{withFileTypes:!0}))if(n.isDirectory())for(let t of[`pages`,`layouts`,`components`]){let r=o(a,n.name,t);e(r)&&i.push(r)}}catch{}}return i}const l=[`.qwik.`];function u(e){return l.some(t=>e.includes(t))}function d(e,r,i){let a;try{a=t(e,{withFileTypes:!0})}catch{return}for(let t of a){let a=o(e,t.name);if(t.isDirectory()){d(a,r,i);continue}if(/\.(tsx?|jsx?|mdx?)$/.test(t.name))try{let e=n(a,`utf-8`),t=e.includes(`island=`)||e.includes(`island `),o=/import\s+\w+\s+from\s+['"][^'"]*\.qwik\.[^'"]*['"]/m.test(e);if(!t&&!o)continue;f(e,a,r,i)}catch{}}}function f(e,t,n,r){let i=/<([A-Z]\w*)\s+[^>]*\bisland\b/g,o=new Set,s=null;for(s=i.exec(e);s!==null;s=i.exec(e))o.add(s[1]);let c=/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,l=new Set,d=[];for(s=c.exec(e);s!==null;s=c.exec(e))d.push([s[1],s[2]]),u(s[2])&&RegExp(`<${s[1]}[\\s/>]`).test(e)&&l.add(s[1]);if(!(o.size===0&&l.size===0))for(let[e,i]of d){if(!o.has(e)&&!l.has(e))continue;let s=p(i,t,n);if(!s)continue;let c=a(n,s).replaceAll(`\\`,`/`).replace(/\.(tsx?|jsx?)$/,``);r.has(s)||r.set(s,{filePath:s,bundleKey:c})}}function p(t,n,a){let s;if(t.startsWith(`@shared/`))s=o(a,`app/shared`,t.slice(8));else if(t.startsWith(`@modules/`))s=o(a,`app/modules`,t.slice(9));else if(t.startsWith(`@/`))s=o(a,`app`,t.slice(2));else if(t.startsWith(`.`))s=o(i(n),t);else return null;if(e(s)&&r(s).isFile())return s;for(let t of[`.tsx`,`.ts`,`.jsx`,`.js`,`.vue`,`.svelte`])if(e(s+t))return s+t;return null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BaseFrameworkAdapter as e}from"../framework-adapter.js";export class LitHMRAdapter extends e{name=`lit`;elementConstructors=new WeakMap;tagNames=new WeakMap;canHandle(e){if(!e)return!1;if(typeof e==`function`){let t=e;if(t.__litElement)return!0;try{let t=e.prototype;if(t){if(`render`in t&&`requestUpdate`in t&&`updateComplete`in t)return!0;let e=t;for(;e&&e!==Object.prototype;){let t=e.constructor;if(t&&t.name===`LitElement`)return!0;e=Object.getPrototypeOf(e)}}}catch{}if(t.elementName||t.tagName)return!0;try{let t=e.toString();if(t.includes(`LitElement`)||t.includes(`customElement`)||t.includes("html`")||t.includes("css`")||t.includes(`render()`)||t.includes(`requestUpdate`))return!0}catch{}}if(typeof e!=`object`)return!1;let t=e;return t.default&&typeof t.default==`function`?this.canHandle(t.default):!!t.__litElement}preserveState(e){try{let t=super.preserveState(e);if(!t)return null;let n=e.getAttribute(`data-props`),r=n?JSON.parse(n):{},i=e.getAttribute(`data-src`)||``,a=this.extractComponentName(i),o=e.getAttribute(`data-tag-name`)||this.tagNames.get(e),s=o?e.querySelector(o):e.querySelector(`[data-lit-element]`),c={},l={};if(s){for(let e in s)if(
|
|
1
|
+
import{BaseFrameworkAdapter as e}from"../framework-adapter.js";export class LitHMRAdapter extends e{name=`lit`;elementConstructors=new WeakMap;tagNames=new WeakMap;canHandle(e){if(!e)return!1;if(typeof e==`function`){let t=e;if(t.__litElement)return!0;try{let t=e.prototype;if(t){if(`render`in t&&`requestUpdate`in t&&`updateComplete`in t)return!0;let e=t;for(;e&&e!==Object.prototype;){let t=e.constructor;if(t&&t.name===`LitElement`)return!0;e=Object.getPrototypeOf(e)}}}catch{}if(t.elementName||t.tagName)return!0;try{let t=e.toString();if(t.includes(`LitElement`)||t.includes(`customElement`)||t.includes("html`")||t.includes("css`")||t.includes(`render()`)||t.includes(`requestUpdate`))return!0}catch{}}if(typeof e!=`object`)return!1;let t=e;return t.default&&typeof t.default==`function`?this.canHandle(t.default):!!t.__litElement}preserveState(e){try{let t=super.preserveState(e);if(!t)return null;let n=e.getAttribute(`data-props`),r=n?JSON.parse(n):{},i=e.getAttribute(`data-src`)||``,a=this.extractComponentName(i),o=e.getAttribute(`data-tag-name`)||this.tagNames.get(e),s=o?e.querySelector(o):e.querySelector(`[data-lit-element]`),c={},l={};if(s){for(let e in s)if(Object.hasOwn(s,e)&&!e.startsWith(`_`))try{let t=s[e];t!=null&&typeof t!=`function`&&typeof t!=`symbol`&&(c[e]=t)}catch{}for(let e=0;e<s.attributes.length;e++){let t=s.attributes[e];l[t.name]=t.value}}return{...t,framework:`lit`,data:{componentName:a,tagName:o||void 0,capturedProps:r,elementProperties:c,elementAttributes:l}}}catch(e){return console.warn(`Failed to preserve Lit state:`,e),null}}async update(e,t,n){if(!this.canHandle(t))throw Error(`Component is not a valid Lit component`);let r;if(typeof t==`object`&&t){let e=t;if(e.default&&typeof e.default==`function`)r=e.default;else throw Error(`Lit component object must have a default export`)}else if(typeof t==`function`)r=t;else throw Error(`Invalid Lit component type`);try{let t=e.getAttribute(`data-tag-name`);if(t||=r.elementName,t||=r.name.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),!t||!t.includes(`-`))throw Error(`Invalid custom element tag name: `+t);this.tagNames.set(e,t),e.setAttribute(`data-tag-name`,t);let i=Array.from(e.querySelectorAll(t));if(i.length===0){let i=customElements.get(t);i?i!==r&&await this.reregisterCustomElement(t,r):customElements.define(t,r);let a=document.createElement(t);Object.entries(n).forEach(([e,t])=>{try{a[e]=t}catch(t){console.warn(`Failed to set property ${e} on Lit element:`,t)}}),e.appendChild(a),this.elementConstructors.set(e,r),e.setAttribute(`data-hydrated`,`true`),e.setAttribute(`data-hydration-status`,`success`);return}let a=customElements.get(t);if(a&&a!==r){await this.reregisterCustomElement(t,r);for(let e of i){let n={},r={};for(let t in e)if(Object.hasOwn(e,t)&&!t.startsWith(`_`))try{let r=e[t];r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&(n[t]=r)}catch{}for(let t=0;t<e.attributes.length;t++){let n=e.attributes[t];r[n.name]=n.value}let i=document.createElement(t);Object.entries(r).forEach(([e,t])=>{i.setAttribute(e,t)}),Object.entries(n).forEach(([e,t])=>{try{i[e]=t}catch(t){console.warn(`Failed to restore property ${e}:`,t)}}),e.parentNode?.replaceChild(i,e)}}else if(a)for(let e of i)Object.entries(n).forEach(([t,n])=>{try{e[t]=n}catch(e){console.warn(`Failed to update property ${t}:`,e)}}),e.requestUpdate&&e.requestUpdate();else{customElements.define(t,r);for(let e of i)e.requestUpdate&&e.requestUpdate()}this.elementConstructors.set(e,r),e.setAttribute(`data-hydrated`,`true`),e.setAttribute(`data-hydration-status`,`success`)}catch(t){throw console.error(`Lit HMR update failed:`,t),e.setAttribute(`data-hydration-status`,`error`),t}}async reregisterCustomElement(e,t){console.warn(`Custom element ${e} is already defined. Replacing all instances with new definition.`)}restoreState(e,t){try{super.restoreState(e,t);let n=t,r=n.data.tagName;if(r){let t=e.querySelector(r);t&&(n.data.elementProperties&&Object.entries(n.data.elementProperties).forEach(([e,n])=>{try{t[e]=n}catch(t){console.warn(`Failed to restore property ${e}:`,t)}}),n.data.elementAttributes&&Object.entries(n.data.elementAttributes).forEach(([e,n])=>{try{t.setAttribute(e,n)}catch(t){console.warn(`Failed to restore attribute ${e}:`,t)}}),t.requestUpdate&&t.requestUpdate())}}catch(e){console.warn(`Failed to restore Lit state:`,e)}}handleError(e,t){console.error(`Lit HMR error:`,t),super.handleError(e,t);let n=e.querySelector(`.hmr-error-indicator`);if(n){let e=t.message,r=``;e.includes(`custom element`)||e.includes(`define`)?r=` (Hint: Check that your element has a valid tag name with a hyphen)`:e.includes(`tag name`)?r=` (Hint: Custom element tag names must contain a hyphen)`:e.includes(`property`)||e.includes(`attribute`)?r=` (Hint: Check @property decorators and attribute names)`:e.includes(`render`)?r=` (Hint: Check the render() method for errors)`:e.includes(`shadow`)&&(r=` (Hint: Check Shadow DOM usage and styles)`),n.textContent=`Lit HMR Error: ${e}${r}`}}extractComponentName(e){let t=e.split(`/`);return t[t.length-1].replace(/\.lit\.(ts|js)$/,``).replace(/\.(ts|js)$/,``)}unmount(e){try{let t=this.tagNames.get(e);t&&(e.querySelectorAll(t).forEach(e=>{e.remove()}),this.tagNames.delete(e)),this.elementConstructors.delete(e)}catch(e){console.warn(`Failed to unmount Lit element:`,e)}}}export const litAdapter=new LitHMRAdapter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{getCSSHMRHandler as e}from"./css-hmr-handler.js";import{AdapterRegistry as t}from"./framework-adapter.js";export class HMRCoordinator{registry=new t;stateSnapshots=new Map;updateQueue=new Set;isProcessing=!1;initialize(){import.meta.hot&&(import.meta.hot.accept(),import.meta.hot.on(`vite:beforeUpdate`,e=>{this.handleUpdate(e)}),import.meta.hot.on(`vite:beforeFullReload`,()=>{this.handleBeforeFullReload()}),import.meta.hot.on(`vite:error`,e=>{console.error(`[HMR] error:`,e),this.handleError(e)}),document.addEventListener(`hmr-update-required`,e=>{let{src:t,reason:n}=e.detail;n===`css-module-update`&&(this.updateQueue.add(this.normalizePath(t)),this.isProcessing||this.processUpdateQueue().catch(e=>{console.error(`[HMR] Failed to process CSS module update:`,e)}))}))}registerAdapter(e,t){this.registry.register(e,t)}getRegistry(){return this.registry}async handleUpdate(e){if(e.type!==`update`||!e.updates)return;let t=[],n=[];for(let r of e.updates)r.type===`css-update`?t.push(r):n.push(r);this.processCSSUpdates(t),this.queueJSUpdates(n),!this.isProcessing&&this.updateQueue.size>0&&await this.processUpdateQueue()}processCSSUpdates(t){if(t.length===0)return;let n=e();for(let e of t)try{n.handleCSSUpdate(e)}catch(e){console.error(`[HMR] CSS update failed:`,e)}}queueJSUpdates(e){for(let t of e){let e=this.normalizePath(t.path||t.acceptedPath);this.isIslandModule(e)&&this.updateQueue.add(e)}}async processUpdateQueue(){if(this.updateQueue.size!==0){this.isProcessing=!0;try{let e=Array.from(this.updateQueue);this.updateQueue.clear();for(let t of e){let e=this.findAffectedIslands(t);for(let t of e)try{await this.updateIsland(t)}catch(e){console.error(`[HMR] Failed to update island:`,e)}}}finally{this.isProcessing=!1}}}findAffectedIslands(e){let t=[],n=this.normalizePath(e),r=document.querySelectorAll(`[data-src]`);for(let e of r){let r=e.dataset.src;if(!r)continue;let i=this.normalizePath(r);(i===n||i.endsWith(n)||n.endsWith(i)||i.split(`/`).pop()===n.split(`/`).pop())&&t.push(e)}return t}async updateIsland(e){let t=e.dataset.framework,n=e.dataset.src,r=e.dataset.props;if(!t||!n){console.warn(`[HMR] Island missing framework or src attribute`,e);return}let i=this.registry.get(t.toLowerCase());if(!i){console.warn(`[HMR] No adapter registered for framework: ${t}`);return}try{let a=r?JSON.parse(r):{},o=i.preserveState(e);o&&this.stateSnapshots.set(this.getIslandId(e),o),delete e.dataset.hydrated,delete e.dataset.hydrationStatus,e.querySelector(`.hydration-error-indicator, .hmr-error-indicator`)?.remove();let s=Date.now(),c=await import(n.includes(`?`)?`${n}&t=${s}`:`${n}?t=${s}`),l=this.resolveComponent(c,n);await i.update(e,l,a),o&&(i.restoreState(e,o),this.stateSnapshots.delete(this.getIslandId(e))),e.dataset.hydrated=`true`,e.dispatchEvent(new CustomEvent(`hmr-update`,{detail:{framework:t,src:n,timestamp:Date.now(),success:!0},bubbles:!0}))}catch(r){throw console.error(`[HMR] Failed to update ${t} island ${n}:`,r),i.handleError(e,r),e.dispatchEvent(new CustomEvent(`hmr-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now()},bubbles:!0})),r}}resolveComponent(e,t){if(e.default)return e.default;for(let t of Object.keys(e)){if(t===`default`)continue;let n=e[t];if(typeof n==`function`&&n.prototype)return n}throw Error(`Component ${t} has no default export`)}handleBeforeFullReload(){let e=document.querySelectorAll(`[data-hydrated="true"]`),t={};for(let n of e){let e=n.dataset.framework,r=n.dataset.src;if(!e||!r)continue;let i=this.registry.get(e.toLowerCase());if(!i)continue;let a=i.preserveState(n);a&&(t[r]=a)}try{sessionStorage.setItem(`__avalon_hmr_states__`,JSON.stringify(t))}catch(e){console.warn(`[HMR] Failed to save states:`,e)}}handleError(e){let t=Error(e.err.message);t.stack=e.err.stack,console.error(`[HMR] Error:`,t),globalThis.window!==void 0&&import(`./hmr-error-overlay.js`).then(({showHMRErrorOverlay:n})=>{n({framework:`unknown`,src:`unknown`,error:t,filePath:e.err.id||e.err.loc?.file||`unknown`,line:e.err.loc?.line,column:e.err.loc?.column})}).catch(()=>{console.error(`[HMR] Failed to show error overlay`)})}normalizePath(e){return e.replaceAll(`\\`,`/`).replace(/^\//,``).replace(/\?.*$/,``).replace(/#.*$/,``).replace(/^src\//,``)}isIslandModule(e){return e.includes(`/islands/`)||e.includes(`\\islands\\`)}getIslandId(e){let t=e.dataset.src??``;return`${e.dataset.framework??``}:${t}:${Array.from(document.querySelectorAll(`[data-src="${t}"]`)).indexOf(e)}`}}let r=null;export function getHMRCoordinator(){return r??=new HMRCoordinator,r}export function initializeHMR(){import.meta.hot&&getHMRCoordinator().initialize()}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(
|
|
1
|
+
(()=>{var e=document.querySelectorAll(`[data-framework]`);if(!e.length)return;var t=!1;e.forEach(e=>{var r=e.dataset.framework,i=e.dataset.condition||`on:client`;e.dataset.renderStrategy!==`ssr-only`&&(i===`on:client`?(window.requestIdleCallback||requestAnimationFrame)(()=>{n(e,r)}):t=!0)}),t&&import(`./strategies.js`).then(t=>{e.forEach(e=>{var r=e.dataset.condition||`on:client`;r!==`on:client`&&e.dataset.renderStrategy!==`ssr-only`&&!e.dataset.hydrated&&t.setup(e,e.dataset.framework,r,n)})});async function n(e,t){if(!e.dataset.hydrated){var n=e.dataset.src;if(n)try{var r=e.dataset.props?JSON.parse(e.dataset.props):{},i=await import(`virtual:avalon/integration-loader`);t===`lit`&&await i.preLitHydration();var a=await import(n),o=a.default||Object.values(a).find(e=>typeof e==`function`&&e.prototype)||a,s=await i.loadIntegrationModule(t);s.hydrate&&await s.hydrate(e,o,r),e.dataset.hydrated=`true`}catch(e){console.error(`Hydration error for `+t+` island `+n+`:`,e)}}}})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function setup(e,t,n,r){if(n===`on:visible`)try{var i=new IntersectionObserver(
|
|
1
|
+
export function setup(e,t,n,r){if(n===`on:visible`)try{var i=new IntersectionObserver(n=>{n[0].isIntersecting&&(r(e,t),i.disconnect())},{rootMargin:`50px`,threshold:0});i.observe(e)}catch{r(e,t)}else if(n===`on:interaction`){var a=[`click`,`touchstart`,`mouseenter`,`focusin`],o=!1,s=()=>{o||(o=!0,a.forEach(t=>{e.removeEventListener(t,s)}),r(e,t))};a.forEach(t=>{e.addEventListener(t,s,{once:!0,passive:!0})})}else if(n===`on:idle`){var c=()=>{r(e,t)};`requestIdleCallback`in globalThis?globalThis.requestIdleCallback(c,{timeout:5e3}):document.readyState===`complete`?setTimeout(c,200):globalThis.addEventListener(`load`,()=>{setTimeout(c,200)},{once:!0})}else if(n.startsWith(`media:`)){var l=n.slice(6);try{var u=globalThis.matchMedia(l);u.matches?r(e,t):u.addEventListener(`change`,function n(i){i.matches&&(r(e,t),u.removeEventListener(`change`,n))})}catch{r(e,t)}}else e.dataset.customDirective?import(`./custom-directives.js`).then(i=>{i.hasClientDirective&&i.hasClientDirective(n)?i.executeCustomDirective(e,n,()=>{r(e,t)}):r(e,t)}).catch(()=>{r(e,t)}):r(e,t)}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readdir as e,readFile as t}from"node:fs/promises";import{
|
|
2
|
-
`))}else await
|
|
1
|
+
import{readdir as e,readFile as t}from"node:fs/promises";import{join as n,resolve as r}from"node:path";export async function inlineIslandChunks(e,n={}){let{verbose:i=!1}=n,o=await a(r(e,`islands`));if(o.length===0)return i&&console.log(`🏝️ No island files found, skipping inlining`),[];let s=null,c;if(globalThis.Bun!==void 0)s=`bun`;else try{c=await import(`esbuild`),s=`esbuild`}catch{}if(!s)return console.warn(`🏝️ No bundler available (Bun or esbuild), skipping island inlining`),[];console.log(`🏝️ Inlining shared chunks into ${o.length} islands...`);let l=[];for(let n of o){let a=n.replace(`${e}/`,``);if(a.includes(`.qwik.`)){l.push({island:a,beforeSize:0,afterSize:0,success:!0,skipped:!0});continue}try{let e=await t(n,`utf-8`),o=Buffer.byteLength(e,`utf-8`);if(!e.includes(`from"../`)&&!e.includes(`from'../`)){l.push({island:a,beforeSize:o,afterSize:o,success:!0});continue}if(s===`bun`){let e={entrypoints:[n],outdir:r(n,`..`),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 c.build({entryPoints:[n],outfile:n,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 u=await t(n,`utf-8`),d=Buffer.byteLength(u,`utf-8`);if(l.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}`),l.push({island:a,beforeSize:0,afterSize:0,success:!1,error:t})}}let u=l.filter(e=>e.success).length,d=l.filter(e=>!e.success).length;return console.log(`🏝️ Done: ${u} inlined${d?`, ${d} failed`:``}`),l}async function a(t,r=[]){try{let i=await e(t,{withFileTypes:!0});for(let e of i){let i=n(t,e.name);e.isDirectory()?await a(i,r):e.name.endsWith(`.js`)&&!e.name.endsWith(`.map`)&&r.push(i)}}catch{}return r}
|
|
@@ -1,29 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Isolated Island Builder
|
|
3
3
|
*
|
|
4
|
-
* Builds each island as a separate
|
|
4
|
+
* Builds each island as a separate tsdown build with fresh framework unplugins.
|
|
5
5
|
* This produces self-contained island files with the framework runtime inlined
|
|
6
6
|
* and tree-shaken — matching Astro's approach.
|
|
7
7
|
*
|
|
8
8
|
* Each build:
|
|
9
|
-
* 1. Loads framework
|
|
9
|
+
* 1. Loads framework unplugins fresh (unplugin-solid, unplugin-vue, etc.)
|
|
10
10
|
* 2. Compiles from source (.tsx/.vue/.svelte)
|
|
11
|
-
* 3. Outputs a single file with
|
|
11
|
+
* 3. Outputs a single file with format: "esm"
|
|
12
12
|
* 4. Tree-shakes aggressively — only used runtime functions remain
|
|
13
13
|
*
|
|
14
14
|
* Runs as a post-build step after the main Vite build.
|
|
15
15
|
*/
|
|
16
|
-
interface IslandBuildResult {
|
|
16
|
+
export interface IslandBuildResult {
|
|
17
17
|
island: string;
|
|
18
18
|
success: boolean;
|
|
19
19
|
size?: number;
|
|
20
20
|
error?: string;
|
|
21
|
+
elapsedMs?: number;
|
|
21
22
|
}
|
|
22
23
|
interface IslandSource {
|
|
23
24
|
filePath: string;
|
|
24
25
|
bundleKey: string;
|
|
25
26
|
framework: string;
|
|
26
27
|
}
|
|
28
|
+
/** Tree-shaking configuration for isolated island builds */
|
|
29
|
+
export interface TreeshakeConfig {
|
|
30
|
+
annotations: boolean;
|
|
31
|
+
moduleSideEffects: boolean;
|
|
32
|
+
propertyReadSideEffects: false | "always";
|
|
33
|
+
unknownGlobalSideEffects: boolean;
|
|
34
|
+
manualPureFunctions: string[];
|
|
35
|
+
}
|
|
36
|
+
/** Default tree-shaking config with aggressive settings and Solid runtime pure functions */
|
|
37
|
+
export declare const DEFAULT_TREESHAKE_CONFIG: TreeshakeConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Deep-merge treeshake overrides into defaults.
|
|
40
|
+
* Scalar fields from overrides replace defaults.
|
|
41
|
+
* `manualPureFunctions` is concatenated (not replaced).
|
|
42
|
+
*/
|
|
43
|
+
export declare function mergeTreeshakeConfig(defaults: TreeshakeConfig, overrides: Partial<TreeshakeConfig>): TreeshakeConfig;
|
|
44
|
+
/** Options for the isolated island builder */
|
|
45
|
+
export interface IsolatedIslandBuilderOptions {
|
|
46
|
+
treeshake?: Partial<TreeshakeConfig>;
|
|
47
|
+
}
|
|
48
|
+
export declare function loadFrameworkUnplugin(framework: string): Promise<any | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Generate the wrapper code for an island.
|
|
51
|
+
*
|
|
52
|
+
* For hydration frameworks (solid, preact, react, vue, svelte) the output is exactly 5 lines:
|
|
53
|
+
* 1. Default import of the component
|
|
54
|
+
* 2. `var Component = __C;`
|
|
55
|
+
* 3. Named re-export of `Component` and `default`
|
|
56
|
+
* 4. `globalThis.__avalonIsland` assignment
|
|
57
|
+
* 5. `__hydrateIsland` re-export from the framework adapter
|
|
58
|
+
*
|
|
59
|
+
* Qwik uses a different export shape (re-export all + `_hW`).
|
|
60
|
+
* Lit islands are skipped by the isolated builder and never reach this function.
|
|
61
|
+
*/
|
|
62
|
+
export declare function generateWrapperCode(filePath: string, framework: string): string;
|
|
63
|
+
/** Generate the integration loader module for a specific framework */
|
|
64
|
+
export declare function generateIntegrationLoaderForFramework(framework: string): string;
|
|
27
65
|
/**
|
|
28
66
|
* Build all islands in isolation.
|
|
29
67
|
*
|
|
@@ -32,6 +70,7 @@ interface IslandSource {
|
|
|
32
70
|
* @param islands - Map of discovered islands (filePath → bundleKey)
|
|
33
71
|
* @param resolveAliases - Resolve aliases from the main Vite config
|
|
34
72
|
* @param defineValues - Define replacements from the main Vite config
|
|
73
|
+
* @param options - Optional builder configuration (treeshake overrides, etc.)
|
|
35
74
|
*/
|
|
36
|
-
export declare function buildIsolatedIslands(cwd: string, distDir: string, islands: Map<string, IslandSource>, resolveAliases: any[], defineValues: Record<string, unknown
|
|
75
|
+
export declare function buildIsolatedIslands(cwd: string, distDir: string, islands: Map<string, IslandSource>, resolveAliases: any[], defineValues: Record<string, unknown>, options?: IsolatedIslandBuilderOptions): Promise<IslandBuildResult[]>;
|
|
37
76
|
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import{existsSync as e,statSync as t}from"node:fs";import{resolve as n}from"node:path";function
|
|
2
|
-
`);let r=[]
|
|
3
|
-
`)}
|
|
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
|
+
import{existsSync as e,statSync as t}from"node:fs";import{resolve as n}from"node:path";import{build as r}from"tsdown";export const DEFAULT_TREESHAKE_CONFIG={annotations:!0,moduleSideEffects:!1,propertyReadSideEffects:!1,unknownGlobalSideEffects:!1,manualPureFunctions:[`createSignal`,`createEffect`,`createMemo`,`createComponent`,`template`,`insert`,`delegateEvents`,`setAttribute`,`effect`,`memo`,`spread`,`mergeProps`,`splitProps`,`className`,`classList`,`style`,`addEventListener`]};export function mergeTreeshakeConfig(e,t){return{annotations:t.annotations??e.annotations,moduleSideEffects:t.moduleSideEffects??e.moduleSideEffects,propertyReadSideEffects:t.propertyReadSideEffects??e.propertyReadSideEffects,unknownGlobalSideEffects:t.unknownGlobalSideEffects??e.unknownGlobalSideEffects,manualPureFunctions:[...e.manualPureFunctions,...t.manualPureFunctions??[]]}}const o=new Set([`solid`,`vue`,`svelte`]);export async function loadFrameworkUnplugin(e){switch(e){case`solid`:try{return(await import(`unplugin-solid`)).default.rolldown({ssr:!1})}catch(e){let t=e instanceof Error?e.message:String(e);return console.warn(`⚠ Could not load solid unplugin: ${t}`),null}case`vue`:try{return(await import(`unplugin-vue`)).default.rolldown()}catch(e){let t=e instanceof Error?e.message:String(e);return console.warn(`⚠ Could not load vue unplugin: ${t}`),null}case`svelte`:try{return(await import(`rollup-plugin-svelte`)).default()}catch(e){let t=e instanceof Error?e.message:String(e);return console.warn(`⚠ Could not load svelte plugin: ${t}`),null}default:return null}}const s={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`};export function generateWrapperCode(e,t){let n=JSON.stringify(e);if(t===`qwik`)return[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
|
+
`);let r=s[t],i=[`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`];return r&&i.push(`export { hydrate as __hydrateIsland } from ${JSON.stringify(r)};`),i.join(`
|
|
3
|
+
`)}export function generateIntegrationLoaderForFramework(e){let t=s[e];return t?`export { hydrate } from ${JSON.stringify(t)};`:`export {};`}export async function buildIsolatedIslands(s,l,u,d,f,p){if(u.size===0)return[];console.log(`🏝️ Building ${u.size} islands in isolation...`);let m=performance.now(),h=p?.treeshake?mergeTreeshakeConfig(DEFAULT_TREESHAKE_CONFIG,p.treeshake):DEFAULT_TREESHAKE_CONFIG,g=[];for(let[,i]of u){let{filePath:a,bundleKey:u,framework:d}=i,f=`islands/${u}.js`;if(d===`qwik`||d===`lit`){g.push({island:f,success:!0});continue}let p=generateWrapperCode(a,d),m=`\0isolated-island-entry`,_=`\0virtual:avalon/integration-loader`;try{let i=await loadFrameworkUnplugin(d);if(o.has(d)&&i==null){console.warn(` ⚠ Skipping ${u}: ${d} unplugin is not installed`),g.push({island:f,success:!0});continue}let a={name:`avalon:isolated-island-virtual`,resolveId(e){return e===m?e:e===`virtual:avalon/integration-loader`||e===_?_:null},load(e){return e===m?p:e===_?generateIntegrationLoaderForFramework(d):null}},c={name:`avalon:css-stub`,resolveId(e){return e.endsWith(`.css`)||e.endsWith(`.scss`)||e.endsWith(`.less`)?{id:`\0css-stub`,external:!1}:null},load(e){return e===`\0css-stub`?`export default '';`:null}},v=performance.now();await r({entry:{[u]:m},outDir:n(s,l,`islands`),format:`esm`,target:`es2020`,minify:!0,clean:!1,dts:!1,treeshake:!0,report:!1,define:{__DEV__:`false`,__PROD__:`true`,"process.env.NODE_ENV":`"production"`},inputOptions:e=>{e.treeshake=h;let t=d===`preact`?{jsx:`react-jsx`,jsxImportSource:`preact`}:d===`react`?{jsx:`react-jsx`}:{};return Object.keys(t).length>0&&(e.transform={...e.transform,...t}),e},plugins:[c,a,i].filter(Boolean)});let y=performance.now()-v,b=n(s,l,f),x=e(b)?t(b).size:0;g.push({island:f,success:!0,size:x,elapsedMs:y}),console.log(` ✅ ${u} (${d}): ${(x/1024).toFixed(1)} KiB`),y>2e3&&console.warn(` ⚠ ${u} (${d}): build took ${y.toFixed(0)}ms (>2s)`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${u} (${d}): ${t}`),g.push({island:f,success:!1,error:t})}}let _=((performance.now()-m)/1e3).toFixed(1),v=g.filter(e=>e.success).length,y=g.filter(e=>!e.success).length;return console.log(`🏝️ Done in ${_}s: ${v} built${y?`, ${y} failed`:``}`),g}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -127,7 +127,10 @@
|
|
|
127
127
|
"vite": "^8.0.0",
|
|
128
128
|
"nitro": "^3.0.260311-beta",
|
|
129
129
|
"vite-imagetools": "^7.0.0",
|
|
130
|
-
"rehype-highlight": "^7.0.0"
|
|
130
|
+
"rehype-highlight": "^7.0.0",
|
|
131
|
+
"unplugin-solid": "^1.0.0",
|
|
132
|
+
"unplugin-vue": "^7.0.0",
|
|
133
|
+
"rollup-plugin-svelte": "^7.0.0"
|
|
131
134
|
},
|
|
132
135
|
"peerDependenciesMeta": {
|
|
133
136
|
"nitro": {
|
|
@@ -141,11 +144,21 @@
|
|
|
141
144
|
},
|
|
142
145
|
"rehype-highlight": {
|
|
143
146
|
"optional": true
|
|
147
|
+
},
|
|
148
|
+
"unplugin-solid": {
|
|
149
|
+
"optional": true
|
|
150
|
+
},
|
|
151
|
+
"unplugin-vue": {
|
|
152
|
+
"optional": true
|
|
153
|
+
},
|
|
154
|
+
"rollup-plugin-svelte": {
|
|
155
|
+
"optional": true
|
|
144
156
|
}
|
|
145
157
|
},
|
|
146
158
|
"dependencies": {
|
|
147
159
|
"@useavalon/core": "^0.1.7",
|
|
148
160
|
"@mdx-js/rollup": "^3.0.0",
|
|
161
|
+
"tsdown": "^0.21.7",
|
|
149
162
|
"h3": "^2.0.1-rc.16",
|
|
150
163
|
"marked": "^17.0.4",
|
|
151
164
|
"oxc-transform": "^0.117.0",
|