ezfw-core 1.0.78 → 1.0.80
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/islands/StaticHtmlRenderer.d.ts +3 -1
- package/islands/StaticHtmlRenderer.js +23 -5
- package/islands/StaticHtmlRenderer.ts +31 -4
- package/islands/ViteIslandsPlugin.js +4 -0
- package/islands/ViteIslandsPlugin.ts +6 -0
- package/islands/analyzer.d.ts +2 -1
- package/islands/analyzer.js +5 -2
- package/islands/analyzer.ts +2 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ export interface RenderContext {
|
|
|
11
11
|
islands: IslandData[];
|
|
12
12
|
props?: Record<string, unknown>;
|
|
13
13
|
serverData?: Record<string, unknown>;
|
|
14
|
+
insideIsland?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export interface IslandData {
|
|
16
17
|
id: string;
|
|
@@ -108,7 +109,8 @@ export declare class StaticHtmlRenderer {
|
|
|
108
109
|
*/
|
|
109
110
|
private serializableProps;
|
|
110
111
|
/**
|
|
111
|
-
* Reset island counter
|
|
112
|
+
* Reset island counter and clear analyzer cache
|
|
113
|
+
* Called before each SSR render to ensure fresh analysis
|
|
112
114
|
*/
|
|
113
115
|
resetIslandCounter(): void;
|
|
114
116
|
}
|
|
@@ -36,7 +36,6 @@ let islandIdCounter = 0;
|
|
|
36
36
|
* Main static renderer class
|
|
37
37
|
*/
|
|
38
38
|
export class StaticHtmlRenderer {
|
|
39
|
-
analyzer;
|
|
40
39
|
constructor() {
|
|
41
40
|
this.analyzer = analyzer;
|
|
42
41
|
}
|
|
@@ -76,8 +75,15 @@ export class StaticHtmlRenderer {
|
|
|
76
75
|
// Try to render as native HTML element
|
|
77
76
|
return this.renderNativeElement(name, props, ctx);
|
|
78
77
|
}
|
|
78
|
+
// If we're inside an island or template-based component, render children as static
|
|
79
|
+
// (they will be re-rendered when the parent hydrates or when SPA takes over)
|
|
80
|
+
if (ctx.insideIsland || ctx.insideTemplate) {
|
|
81
|
+
console.log(`[SSR] ${name} rendered as static (inside ${ctx.insideIsland ? 'island' : 'template'})`);
|
|
82
|
+
return this.renderStaticComponent(name, definition, props, ctx);
|
|
83
|
+
}
|
|
79
84
|
// Analyze the component
|
|
80
85
|
const analysis = this.analyzer.analyze(name, definition);
|
|
86
|
+
console.log(`[SSR] ${name} analyzed as: ${analysis.type}`);
|
|
81
87
|
if (analysis.type === 'island') {
|
|
82
88
|
// Render as island placeholder
|
|
83
89
|
return this.renderIslandPlaceholder(name, definition, props, ctx);
|
|
@@ -94,6 +100,13 @@ export class StaticHtmlRenderer {
|
|
|
94
100
|
ctx.styles.add(definition.css);
|
|
95
101
|
}
|
|
96
102
|
let config;
|
|
103
|
+
// If this component has a template, mark context so children render as static
|
|
104
|
+
// Template-based components are re-rendered by SPA, so their children don't need island markers
|
|
105
|
+
const hasTemplate = !!definition.template;
|
|
106
|
+
const childCtx = hasTemplate ? { ...ctx, insideTemplate: true } : ctx;
|
|
107
|
+
if (hasTemplate) {
|
|
108
|
+
console.log(`[SSR] ${name} has template, setting insideTemplate=true for children`);
|
|
109
|
+
}
|
|
97
110
|
// Get component config from template or items
|
|
98
111
|
if (definition.template) {
|
|
99
112
|
// Get controller state for SSR
|
|
@@ -138,8 +151,8 @@ export class StaticHtmlRenderer {
|
|
|
138
151
|
config[key] = definition[key];
|
|
139
152
|
}
|
|
140
153
|
}
|
|
141
|
-
// Render the config tree
|
|
142
|
-
return this.renderConfig(config,
|
|
154
|
+
// Render the config tree using childCtx (which has insideTemplate=true if this has a template)
|
|
155
|
+
return this.renderConfig(config, childCtx);
|
|
143
156
|
}
|
|
144
157
|
/**
|
|
145
158
|
* Render an island as a placeholder with hydration data
|
|
@@ -158,7 +171,10 @@ export class StaticHtmlRenderer {
|
|
|
158
171
|
};
|
|
159
172
|
ctx.islands.push(islandData);
|
|
160
173
|
// Render the full static content (for SEO)
|
|
161
|
-
|
|
174
|
+
// Mark that we're inside an island so child components don't become islands
|
|
175
|
+
// (they will be re-rendered when this island hydrates on the client)
|
|
176
|
+
const islandCtx = { ...ctx, insideIsland: true };
|
|
177
|
+
const staticContent = await this.renderStaticComponent(name, definition, props, islandCtx);
|
|
162
178
|
// Wrap in island container with data attributes for hydration
|
|
163
179
|
const propsJson = this.escapeAttr(JSON.stringify(islandData.props));
|
|
164
180
|
return `<div data-ez-island="${name}" data-ez-island-id="${islandId}" data-ez-props="${propsJson}">${staticContent}</div>`;
|
|
@@ -447,10 +463,12 @@ hydrateIslands(window.__EZ_ISLANDS__);
|
|
|
447
463
|
return result;
|
|
448
464
|
}
|
|
449
465
|
/**
|
|
450
|
-
* Reset island counter
|
|
466
|
+
* Reset island counter and clear analyzer cache
|
|
467
|
+
* Called before each SSR render to ensure fresh analysis
|
|
451
468
|
*/
|
|
452
469
|
resetIslandCounter() {
|
|
453
470
|
islandIdCounter = 0;
|
|
471
|
+
this.analyzer.clearCache();
|
|
454
472
|
}
|
|
455
473
|
}
|
|
456
474
|
// Export singleton
|
|
@@ -19,6 +19,12 @@ export interface RenderContext {
|
|
|
19
19
|
props?: Record<string, unknown>;
|
|
20
20
|
// Server-side data loaded via load()
|
|
21
21
|
serverData?: Record<string, unknown>;
|
|
22
|
+
// True when rendering inside an island - children should not be islands
|
|
23
|
+
// (they will be re-rendered when the parent island hydrates)
|
|
24
|
+
insideIsland?: boolean;
|
|
25
|
+
// True when rendering inside a template-based component
|
|
26
|
+
// Template components are re-rendered by SPA, so children don't need islands
|
|
27
|
+
insideTemplate?: boolean;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export interface IslandData {
|
|
@@ -150,8 +156,16 @@ export class StaticHtmlRenderer {
|
|
|
150
156
|
return this.renderNativeElement(name, props as ComponentConfig, ctx);
|
|
151
157
|
}
|
|
152
158
|
|
|
159
|
+
// If we're inside an island or template-based component, render children as static
|
|
160
|
+
// (they will be re-rendered when the parent hydrates or when SPA takes over)
|
|
161
|
+
if (ctx.insideIsland || ctx.insideTemplate) {
|
|
162
|
+
console.log(`[SSR] ${name} rendered as static (inside ${ctx.insideIsland ? 'island' : 'template'})`);
|
|
163
|
+
return this.renderStaticComponent(name, definition, props, ctx);
|
|
164
|
+
}
|
|
165
|
+
|
|
153
166
|
// Analyze the component
|
|
154
167
|
const analysis = this.analyzer.analyze(name, definition);
|
|
168
|
+
console.log(`[SSR] ${name} analyzed as: ${analysis.type}`);
|
|
155
169
|
|
|
156
170
|
if (analysis.type === 'island') {
|
|
157
171
|
// Render as island placeholder
|
|
@@ -178,6 +192,14 @@ export class StaticHtmlRenderer {
|
|
|
178
192
|
|
|
179
193
|
let config: ComponentConfig;
|
|
180
194
|
|
|
195
|
+
// If this component has a template, mark context so children render as static
|
|
196
|
+
// Template-based components are re-rendered by SPA, so their children don't need island markers
|
|
197
|
+
const hasTemplate = !!definition.template;
|
|
198
|
+
const childCtx = hasTemplate ? { ...ctx, insideTemplate: true } : ctx;
|
|
199
|
+
if (hasTemplate) {
|
|
200
|
+
console.log(`[SSR] ${name} has template, setting insideTemplate=true for children`);
|
|
201
|
+
}
|
|
202
|
+
|
|
181
203
|
// Get component config from template or items
|
|
182
204
|
if (definition.template) {
|
|
183
205
|
// Get controller state for SSR
|
|
@@ -224,8 +246,8 @@ export class StaticHtmlRenderer {
|
|
|
224
246
|
}
|
|
225
247
|
}
|
|
226
248
|
|
|
227
|
-
// Render the config tree
|
|
228
|
-
return this.renderConfig(config,
|
|
249
|
+
// Render the config tree using childCtx (which has insideTemplate=true if this has a template)
|
|
250
|
+
return this.renderConfig(config, childCtx);
|
|
229
251
|
}
|
|
230
252
|
|
|
231
253
|
/**
|
|
@@ -252,7 +274,10 @@ export class StaticHtmlRenderer {
|
|
|
252
274
|
ctx.islands.push(islandData);
|
|
253
275
|
|
|
254
276
|
// Render the full static content (for SEO)
|
|
255
|
-
|
|
277
|
+
// Mark that we're inside an island so child components don't become islands
|
|
278
|
+
// (they will be re-rendered when this island hydrates on the client)
|
|
279
|
+
const islandCtx = { ...ctx, insideIsland: true };
|
|
280
|
+
const staticContent = await this.renderStaticComponent(name, definition, props, islandCtx);
|
|
256
281
|
|
|
257
282
|
// Wrap in island container with data attributes for hydration
|
|
258
283
|
const propsJson = this.escapeAttr(JSON.stringify(islandData.props));
|
|
@@ -554,10 +579,12 @@ hydrateIslands(window.__EZ_ISLANDS__);
|
|
|
554
579
|
}
|
|
555
580
|
|
|
556
581
|
/**
|
|
557
|
-
* Reset island counter
|
|
582
|
+
* Reset island counter and clear analyzer cache
|
|
583
|
+
* Called before each SSR render to ensure fresh analysis
|
|
558
584
|
*/
|
|
559
585
|
resetIslandCounter(): void {
|
|
560
586
|
islandIdCounter = 0;
|
|
587
|
+
this.analyzer.clearCache();
|
|
561
588
|
}
|
|
562
589
|
}
|
|
563
590
|
|
|
@@ -665,6 +665,8 @@ async function renderPageDev(page, server, opts, renderer, islands, root) {
|
|
|
665
665
|
islands: [],
|
|
666
666
|
props: {}
|
|
667
667
|
};
|
|
668
|
+
// Reset island counter before each render to ensure consistent IDs
|
|
669
|
+
renderer.resetIslandCounter();
|
|
668
670
|
// Render body
|
|
669
671
|
const body = await renderer.renderPage(page.name, definition, ctx);
|
|
670
672
|
// Generate hydration script (pass CSS modules collected during render)
|
|
@@ -754,6 +756,8 @@ async function renderPageBuild(page, opts, renderer, analyzer, root, islands) {
|
|
|
754
756
|
islands: [],
|
|
755
757
|
props: {}
|
|
756
758
|
};
|
|
759
|
+
// Reset island counter before each render to ensure consistent IDs
|
|
760
|
+
renderer.resetIslandCounter();
|
|
757
761
|
// Render body
|
|
758
762
|
const body = await renderer.renderPage(page.name, definition, ctx);
|
|
759
763
|
// Generate hydration script (pass CSS modules collected during render)
|
|
@@ -857,6 +857,9 @@ async function renderPageDev(
|
|
|
857
857
|
props: {}
|
|
858
858
|
};
|
|
859
859
|
|
|
860
|
+
// Reset island counter before each render to ensure consistent IDs
|
|
861
|
+
renderer.resetIslandCounter();
|
|
862
|
+
|
|
860
863
|
// Render body
|
|
861
864
|
const body = await renderer.renderPage(page.name, definition, ctx);
|
|
862
865
|
|
|
@@ -965,6 +968,9 @@ async function renderPageBuild(
|
|
|
965
968
|
props: {}
|
|
966
969
|
};
|
|
967
970
|
|
|
971
|
+
// Reset island counter before each render to ensure consistent IDs
|
|
972
|
+
renderer.resetIslandCounter();
|
|
973
|
+
|
|
968
974
|
// Render body
|
|
969
975
|
const body = await renderer.renderPage(page.name, definition, ctx);
|
|
970
976
|
|
package/islands/analyzer.d.ts
CHANGED
|
@@ -47,7 +47,8 @@ export interface ComponentDefinition {
|
|
|
47
47
|
export declare class ComponentAnalyzer {
|
|
48
48
|
private cache;
|
|
49
49
|
/**
|
|
50
|
-
* Analyze a component definition and determine its type
|
|
50
|
+
* Analyze a component definition and determine its type.
|
|
51
|
+
* Cache is cleared by StaticHtmlRenderer.resetIslandCounter() before each SSR render.
|
|
51
52
|
*/
|
|
52
53
|
analyze(name: string, definition: ComponentDefinition): AnalysisResult;
|
|
53
54
|
/**
|
package/islands/analyzer.js
CHANGED
|
@@ -58,9 +58,12 @@ const BROWSER_API_PATTERNS = [
|
|
|
58
58
|
* Main analyzer class
|
|
59
59
|
*/
|
|
60
60
|
export class ComponentAnalyzer {
|
|
61
|
-
|
|
61
|
+
constructor() {
|
|
62
|
+
this.cache = new Map();
|
|
63
|
+
}
|
|
62
64
|
/**
|
|
63
|
-
* Analyze a component definition and determine its type
|
|
65
|
+
* Analyze a component definition and determine its type.
|
|
66
|
+
* Cache is cleared by StaticHtmlRenderer.resetIslandCounter() before each SSR render.
|
|
64
67
|
*/
|
|
65
68
|
analyze(name, definition) {
|
|
66
69
|
// Check cache first
|
package/islands/analyzer.ts
CHANGED
|
@@ -95,7 +95,8 @@ export class ComponentAnalyzer {
|
|
|
95
95
|
private cache: Map<string, AnalysisResult> = new Map();
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
* Analyze a component definition and determine its type
|
|
98
|
+
* Analyze a component definition and determine its type.
|
|
99
|
+
* Cache is cleared by StaticHtmlRenderer.resetIslandCounter() before each SSR render.
|
|
99
100
|
*/
|
|
100
101
|
analyze(name: string, definition: ComponentDefinition): AnalysisResult {
|
|
101
102
|
// Check cache first
|