ezfw-core 1.0.79 → 1.0.82
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/components/EzBaseComponent.ts +5 -0
- package/core/renderer.ts +17 -12
- package/islands/StaticHtmlRenderer.d.ts +3 -1
- package/islands/StaticHtmlRenderer.js +18 -5
- package/islands/StaticHtmlRenderer.ts +26 -4
- package/islands/analyzer.d.ts +2 -1
- package/islands/analyzer.js +5 -2
- package/islands/analyzer.ts +2 -1
- package/package.json +1 -1
|
@@ -1094,6 +1094,11 @@ export class EzBaseComponent {
|
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
1096
|
destroy(): void {
|
|
1097
|
+
// Call controller onDestroy if exists
|
|
1098
|
+
if (this.controller && typeof (this.controller as EzController & { onDestroy?: () => void }).onDestroy === 'function') {
|
|
1099
|
+
(this.controller as EzController & { onDestroy?: () => void }).onDestroy!();
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1097
1102
|
// Destroy children first
|
|
1098
1103
|
if (this._children) {
|
|
1099
1104
|
for (const child of this._children) {
|
package/core/renderer.ts
CHANGED
|
@@ -421,30 +421,35 @@ export class EzRenderer {
|
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
async executeOnInit(
|
|
425
|
-
|
|
424
|
+
async executeOnInit(instance: EzBaseComponent | null, config: ComponentConfig): Promise<void> {
|
|
425
|
+
// Prefer instance's config and controller when available
|
|
426
|
+
const effectiveConfig = (instance?.config || config) as ComponentConfig;
|
|
426
427
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
let controller: ControllerInstance | null = null;
|
|
429
|
+
|
|
430
|
+
if (instance?.controller) {
|
|
431
|
+
controller = instance.controller as unknown as ControllerInstance;
|
|
432
|
+
} else if (effectiveConfig.controller) {
|
|
433
|
+
const ctrlName = typeof effectiveConfig.controller === 'string'
|
|
434
|
+
? effectiveConfig.controller
|
|
430
435
|
: null;
|
|
431
436
|
if (ctrlName) {
|
|
432
|
-
controller = this.ez._controllers[ctrlName]
|
|
433
|
-
|
|
434
|
-
} else if (typeof
|
|
435
|
-
controller =
|
|
437
|
+
controller = this.ez._controllers[ctrlName] ||
|
|
438
|
+
this.ez._controllers[`${ctrlName}Controller`];
|
|
439
|
+
} else if (typeof effectiveConfig.controller === 'object') {
|
|
440
|
+
controller = effectiveConfig.controller as ControllerInstance;
|
|
436
441
|
}
|
|
437
442
|
}
|
|
438
443
|
|
|
439
444
|
if (!controller) return;
|
|
440
445
|
|
|
441
|
-
let fn: unknown =
|
|
446
|
+
let fn: unknown = effectiveConfig.init;
|
|
442
447
|
if (typeof fn === 'string') {
|
|
443
448
|
fn = controller[fn];
|
|
444
449
|
}
|
|
445
450
|
|
|
446
451
|
if (typeof fn === 'function') {
|
|
447
|
-
await fn.call(controller,
|
|
452
|
+
await fn.call(controller, instance);
|
|
448
453
|
}
|
|
449
454
|
}
|
|
450
455
|
|
|
@@ -1051,7 +1056,7 @@ export class EzRenderer {
|
|
|
1051
1056
|
|
|
1052
1057
|
if (config.init && config.skipInit !== true) {
|
|
1053
1058
|
queueMicrotask(() => {
|
|
1054
|
-
this.executeOnInit(
|
|
1059
|
+
this.executeOnInit(instance || null, config)
|
|
1055
1060
|
.catch(err => this.ez.handleFrameworkError(err));
|
|
1056
1061
|
});
|
|
1057
1062
|
}
|
|
@@ -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,6 +75,11 @@ 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
|
+
return this.renderStaticComponent(name, definition, props, ctx);
|
|
82
|
+
}
|
|
79
83
|
// Analyze the component
|
|
80
84
|
const analysis = this.analyzer.analyze(name, definition);
|
|
81
85
|
if (analysis.type === 'island') {
|
|
@@ -94,6 +98,10 @@ export class StaticHtmlRenderer {
|
|
|
94
98
|
ctx.styles.add(definition.css);
|
|
95
99
|
}
|
|
96
100
|
let config;
|
|
101
|
+
// If this component has a template, mark context so children render as static
|
|
102
|
+
// Template-based components are re-rendered by SPA, so their children don't need island markers
|
|
103
|
+
const hasTemplate = !!definition.template;
|
|
104
|
+
const childCtx = hasTemplate ? { ...ctx, insideTemplate: true } : ctx;
|
|
97
105
|
// Get component config from template or items
|
|
98
106
|
if (definition.template) {
|
|
99
107
|
// Get controller state for SSR
|
|
@@ -138,8 +146,8 @@ export class StaticHtmlRenderer {
|
|
|
138
146
|
config[key] = definition[key];
|
|
139
147
|
}
|
|
140
148
|
}
|
|
141
|
-
// Render the config tree
|
|
142
|
-
return this.renderConfig(config,
|
|
149
|
+
// Render the config tree using childCtx (which has insideTemplate=true if this has a template)
|
|
150
|
+
return this.renderConfig(config, childCtx);
|
|
143
151
|
}
|
|
144
152
|
/**
|
|
145
153
|
* Render an island as a placeholder with hydration data
|
|
@@ -158,7 +166,10 @@ export class StaticHtmlRenderer {
|
|
|
158
166
|
};
|
|
159
167
|
ctx.islands.push(islandData);
|
|
160
168
|
// Render the full static content (for SEO)
|
|
161
|
-
|
|
169
|
+
// Mark that we're inside an island so child components don't become islands
|
|
170
|
+
// (they will be re-rendered when this island hydrates on the client)
|
|
171
|
+
const islandCtx = { ...ctx, insideIsland: true };
|
|
172
|
+
const staticContent = await this.renderStaticComponent(name, definition, props, islandCtx);
|
|
162
173
|
// Wrap in island container with data attributes for hydration
|
|
163
174
|
const propsJson = this.escapeAttr(JSON.stringify(islandData.props));
|
|
164
175
|
return `<div data-ez-island="${name}" data-ez-island-id="${islandId}" data-ez-props="${propsJson}">${staticContent}</div>`;
|
|
@@ -447,10 +458,12 @@ hydrateIslands(window.__EZ_ISLANDS__);
|
|
|
447
458
|
return result;
|
|
448
459
|
}
|
|
449
460
|
/**
|
|
450
|
-
* Reset island counter
|
|
461
|
+
* Reset island counter and clear analyzer cache
|
|
462
|
+
* Called before each SSR render to ensure fresh analysis
|
|
451
463
|
*/
|
|
452
464
|
resetIslandCounter() {
|
|
453
465
|
islandIdCounter = 0;
|
|
466
|
+
this.analyzer.clearCache();
|
|
454
467
|
}
|
|
455
468
|
}
|
|
456
469
|
// 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,6 +156,12 @@ 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
|
+
return this.renderStaticComponent(name, definition, props, ctx);
|
|
163
|
+
}
|
|
164
|
+
|
|
153
165
|
// Analyze the component
|
|
154
166
|
const analysis = this.analyzer.analyze(name, definition);
|
|
155
167
|
|
|
@@ -178,6 +190,11 @@ export class StaticHtmlRenderer {
|
|
|
178
190
|
|
|
179
191
|
let config: ComponentConfig;
|
|
180
192
|
|
|
193
|
+
// If this component has a template, mark context so children render as static
|
|
194
|
+
// Template-based components are re-rendered by SPA, so their children don't need island markers
|
|
195
|
+
const hasTemplate = !!definition.template;
|
|
196
|
+
const childCtx = hasTemplate ? { ...ctx, insideTemplate: true } : ctx;
|
|
197
|
+
|
|
181
198
|
// Get component config from template or items
|
|
182
199
|
if (definition.template) {
|
|
183
200
|
// Get controller state for SSR
|
|
@@ -224,8 +241,8 @@ export class StaticHtmlRenderer {
|
|
|
224
241
|
}
|
|
225
242
|
}
|
|
226
243
|
|
|
227
|
-
// Render the config tree
|
|
228
|
-
return this.renderConfig(config,
|
|
244
|
+
// Render the config tree using childCtx (which has insideTemplate=true if this has a template)
|
|
245
|
+
return this.renderConfig(config, childCtx);
|
|
229
246
|
}
|
|
230
247
|
|
|
231
248
|
/**
|
|
@@ -252,7 +269,10 @@ export class StaticHtmlRenderer {
|
|
|
252
269
|
ctx.islands.push(islandData);
|
|
253
270
|
|
|
254
271
|
// Render the full static content (for SEO)
|
|
255
|
-
|
|
272
|
+
// Mark that we're inside an island so child components don't become islands
|
|
273
|
+
// (they will be re-rendered when this island hydrates on the client)
|
|
274
|
+
const islandCtx = { ...ctx, insideIsland: true };
|
|
275
|
+
const staticContent = await this.renderStaticComponent(name, definition, props, islandCtx);
|
|
256
276
|
|
|
257
277
|
// Wrap in island container with data attributes for hydration
|
|
258
278
|
const propsJson = this.escapeAttr(JSON.stringify(islandData.props));
|
|
@@ -554,10 +574,12 @@ hydrateIslands(window.__EZ_ISLANDS__);
|
|
|
554
574
|
}
|
|
555
575
|
|
|
556
576
|
/**
|
|
557
|
-
* Reset island counter
|
|
577
|
+
* Reset island counter and clear analyzer cache
|
|
578
|
+
* Called before each SSR render to ensure fresh analysis
|
|
558
579
|
*/
|
|
559
580
|
resetIslandCounter(): void {
|
|
560
581
|
islandIdCounter = 0;
|
|
582
|
+
this.analyzer.clearCache();
|
|
561
583
|
}
|
|
562
584
|
}
|
|
563
585
|
|
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
|