pulse-js-framework 1.10.4 → 1.11.1
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/README.md +11 -0
- package/cli/build.js +13 -3
- package/compiler/directives.js +356 -0
- package/compiler/lexer.js +18 -3
- package/compiler/parser/core.js +6 -0
- package/compiler/parser/view.js +2 -6
- package/compiler/preprocessor.js +43 -23
- package/compiler/sourcemap.js +3 -1
- package/compiler/transformer/actions.js +329 -0
- package/compiler/transformer/export.js +7 -0
- package/compiler/transformer/expressions.js +85 -33
- package/compiler/transformer/imports.js +3 -0
- package/compiler/transformer/index.js +2 -0
- package/compiler/transformer/store.js +1 -1
- package/compiler/transformer/style.js +45 -16
- package/compiler/transformer/view.js +23 -2
- package/loader/rollup-plugin-server-components.js +391 -0
- package/loader/vite-plugin-server-components.js +420 -0
- package/loader/webpack-loader-server-components.js +356 -0
- package/package.json +124 -82
- package/runtime/async.js +4 -0
- package/runtime/context.js +16 -3
- package/runtime/dom-adapter.js +5 -3
- package/runtime/dom-virtual-list.js +2 -1
- package/runtime/form.js +8 -3
- package/runtime/graphql/cache.js +1 -1
- package/runtime/graphql/client.js +22 -0
- package/runtime/graphql/hooks.js +12 -6
- package/runtime/graphql/subscriptions.js +2 -0
- package/runtime/hmr.js +6 -3
- package/runtime/http.js +1 -0
- package/runtime/i18n.js +2 -0
- package/runtime/lru-cache.js +3 -1
- package/runtime/native.js +46 -20
- package/runtime/pulse.js +3 -0
- package/runtime/router/core.js +5 -1
- package/runtime/router/index.js +17 -1
- package/runtime/router/psc-integration.js +301 -0
- package/runtime/security.js +58 -29
- package/runtime/server-components/actions-server.js +798 -0
- package/runtime/server-components/actions.js +389 -0
- package/runtime/server-components/client.js +447 -0
- package/runtime/server-components/error-sanitizer.js +438 -0
- package/runtime/server-components/index.js +275 -0
- package/runtime/server-components/security-csrf.js +593 -0
- package/runtime/server-components/security-errors.js +227 -0
- package/runtime/server-components/security-ratelimit.js +733 -0
- package/runtime/server-components/security-validation.js +467 -0
- package/runtime/server-components/security.js +598 -0
- package/runtime/server-components/serializer.js +617 -0
- package/runtime/server-components/server.js +382 -0
- package/runtime/server-components/types.js +383 -0
- package/runtime/server-components/utils/mutex.js +60 -0
- package/runtime/server-components/utils/path-sanitizer.js +109 -0
- package/runtime/ssr.js +2 -1
- package/runtime/store.js +19 -10
- package/runtime/utils.js +12 -128
- package/types/animation.d.ts +300 -0
- package/types/i18n.d.ts +283 -0
- package/types/persistence.d.ts +267 -0
- package/types/sse.d.ts +248 -0
- package/types/sw.d.ts +150 -0
- package/runtime/a11y.js.original +0 -1844
- package/runtime/graphql.js.original +0 -1326
- package/runtime/router.js.original +0 -1605
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Server Components (PSC) - Client Reconstructor
|
|
3
|
+
*
|
|
4
|
+
* Reconstructs DOM trees from PSC Wire Format on the client side.
|
|
5
|
+
* Handles lazy-loading of Client Component chunks and hydration.
|
|
6
|
+
*
|
|
7
|
+
* @module pulse-js-framework/runtime/server-components/client
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { PSCNodeType, isPSCElement, isPSCText, isPSCClientBoundary, isPSCFragment, isPSCComment } from './types.js';
|
|
11
|
+
import { RuntimeError } from '../errors.js';
|
|
12
|
+
import { getAdapter } from '../dom-adapter.js';
|
|
13
|
+
import { loggers } from '../logger.js';
|
|
14
|
+
|
|
15
|
+
const log = loggers.dom;
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Client Component Cache
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/** Cache of loaded Client Component modules */
|
|
22
|
+
const componentCache = new Map();
|
|
23
|
+
|
|
24
|
+
/** In-flight component loads (Promise cache to prevent duplicate requests) */
|
|
25
|
+
const loadingComponents = new Map();
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// PSC Reconstruction
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reconstruct a PSC node to DOM node(s).
|
|
33
|
+
*
|
|
34
|
+
* @param {PSCNode} pscNode - PSC node to reconstruct
|
|
35
|
+
* @param {Object} options - Reconstruction options
|
|
36
|
+
* @param {Record<string, Function>} [options.clientComponents] - Pre-loaded Client Components
|
|
37
|
+
* @param {Record<string, ClientManifestEntry>} [options.clientManifest] - Client manifest
|
|
38
|
+
* @returns {Promise<Node|Node[]>} Reconstructed DOM node(s)
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const domNode = await reconstructNode(pscNode, {
|
|
42
|
+
* clientComponents: { Button: ButtonComponent },
|
|
43
|
+
* clientManifest: { Button: { chunk: '/Button.js', exports: ['default'] } }
|
|
44
|
+
* });
|
|
45
|
+
*/
|
|
46
|
+
export async function reconstructNode(pscNode, options = {}) {
|
|
47
|
+
if (!pscNode) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const adapter = getAdapter();
|
|
52
|
+
|
|
53
|
+
// Element node
|
|
54
|
+
if (isPSCElement(pscNode)) {
|
|
55
|
+
return reconstructElement(pscNode, options, adapter);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Text node
|
|
59
|
+
if (isPSCText(pscNode)) {
|
|
60
|
+
return reconstructText(pscNode, adapter);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Client Component boundary
|
|
64
|
+
if (isPSCClientBoundary(pscNode)) {
|
|
65
|
+
return reconstructClientBoundary(pscNode, options, adapter);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fragment
|
|
69
|
+
if (isPSCFragment(pscNode)) {
|
|
70
|
+
return reconstructFragment(pscNode, options, adapter);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Comment
|
|
74
|
+
if (isPSCComment(pscNode)) {
|
|
75
|
+
return reconstructComment(pscNode, adapter);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log.warn('Unknown PSC node type:', pscNode.type);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reconstruct a PSC element to DOM element.
|
|
84
|
+
*
|
|
85
|
+
* @param {PSCElement} pscElement - PSC element
|
|
86
|
+
* @param {Object} options - Options
|
|
87
|
+
* @param {any} adapter - DOM adapter
|
|
88
|
+
* @returns {Promise<Element>} DOM element
|
|
89
|
+
*/
|
|
90
|
+
async function reconstructElement(pscElement, options, adapter) {
|
|
91
|
+
const element = adapter.createElement(pscElement.tag);
|
|
92
|
+
|
|
93
|
+
// Set attributes/properties
|
|
94
|
+
if (pscElement.props) {
|
|
95
|
+
for (const [key, value] of Object.entries(pscElement.props)) {
|
|
96
|
+
adapter.setAttribute(element, key, value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Reconstruct children
|
|
101
|
+
if (pscElement.children && pscElement.children.length > 0) {
|
|
102
|
+
for (const childPSC of pscElement.children) {
|
|
103
|
+
const childNode = await reconstructNode(childPSC, options);
|
|
104
|
+
if (childNode) {
|
|
105
|
+
if (Array.isArray(childNode)) {
|
|
106
|
+
// Multiple nodes (from fragment)
|
|
107
|
+
childNode.forEach(node => adapter.appendChild(element, node));
|
|
108
|
+
} else {
|
|
109
|
+
adapter.appendChild(element, childNode);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return element;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Reconstruct a PSC text node to DOM text node.
|
|
120
|
+
*
|
|
121
|
+
* @param {PSCText} pscText - PSC text
|
|
122
|
+
* @param {any} adapter - DOM adapter
|
|
123
|
+
* @returns {Text} DOM text node
|
|
124
|
+
*/
|
|
125
|
+
function reconstructText(pscText, adapter) {
|
|
126
|
+
return adapter.createTextNode(pscText.value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Reconstruct a PSC comment to DOM comment.
|
|
131
|
+
*
|
|
132
|
+
* @param {PSCComment} pscComment - PSC comment
|
|
133
|
+
* @param {any} adapter - DOM adapter
|
|
134
|
+
* @returns {Comment} DOM comment node
|
|
135
|
+
*/
|
|
136
|
+
function reconstructComment(pscComment, adapter) {
|
|
137
|
+
return adapter.createComment(pscComment.value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Reconstruct a PSC fragment to array of DOM nodes.
|
|
142
|
+
*
|
|
143
|
+
* @param {PSCFragment} pscFragment - PSC fragment
|
|
144
|
+
* @param {Object} options - Options
|
|
145
|
+
* @param {any} adapter - DOM adapter
|
|
146
|
+
* @returns {Promise<Node[]>} Array of DOM nodes
|
|
147
|
+
*/
|
|
148
|
+
async function reconstructFragment(pscFragment, options, adapter) {
|
|
149
|
+
const nodes = [];
|
|
150
|
+
|
|
151
|
+
if (pscFragment.children && pscFragment.children.length > 0) {
|
|
152
|
+
for (const childPSC of pscFragment.children) {
|
|
153
|
+
const childNode = await reconstructNode(childPSC, options);
|
|
154
|
+
if (childNode) {
|
|
155
|
+
if (Array.isArray(childNode)) {
|
|
156
|
+
nodes.push(...childNode);
|
|
157
|
+
} else {
|
|
158
|
+
nodes.push(childNode);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return nodes;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Reconstruct a PSC Client Component boundary.
|
|
169
|
+
* Lazy-loads the Client Component chunk if not already loaded.
|
|
170
|
+
*
|
|
171
|
+
* @param {PSCClientBoundary} pscBoundary - PSC client boundary
|
|
172
|
+
* @param {Object} options - Options
|
|
173
|
+
* @param {any} adapter - DOM adapter
|
|
174
|
+
* @returns {Promise<Node>} DOM node (component or fallback)
|
|
175
|
+
*/
|
|
176
|
+
async function reconstructClientBoundary(pscBoundary, options, adapter) {
|
|
177
|
+
const { id, props, fallback } = pscBoundary;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
// Check if component is pre-loaded
|
|
181
|
+
let ComponentFn = options.clientComponents?.[id];
|
|
182
|
+
|
|
183
|
+
// If not pre-loaded, lazy-load from manifest
|
|
184
|
+
if (!ComponentFn) {
|
|
185
|
+
const manifest = options.clientManifest || {};
|
|
186
|
+
const manifestEntry = manifest[id];
|
|
187
|
+
|
|
188
|
+
if (!manifestEntry) {
|
|
189
|
+
throw new RuntimeError(`Client Component '${id}' not found in manifest`, {
|
|
190
|
+
code: 'PSC_COMPONENT_NOT_FOUND',
|
|
191
|
+
context: `Available components: ${Object.keys(manifest).join(', ')}`,
|
|
192
|
+
suggestion: 'Ensure the component is marked with "use client" directive'
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
ComponentFn = await loadClientComponent(id, manifestEntry.chunk);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Execute Client Component with props
|
|
200
|
+
const componentResult = await ComponentFn(props);
|
|
201
|
+
|
|
202
|
+
// If component returns a DOM node, use it directly
|
|
203
|
+
if (componentResult && typeof componentResult === 'object') {
|
|
204
|
+
return componentResult;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw new RuntimeError(`Client Component '${id}' did not return a DOM node`, {
|
|
208
|
+
code: 'PSC_INVALID_COMPONENT_RETURN',
|
|
209
|
+
context: `Returned: ${typeof componentResult}`
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
} catch (error) {
|
|
213
|
+
log.error(`Failed to reconstruct Client Component '${id}':`, error.message);
|
|
214
|
+
|
|
215
|
+
// Render fallback if provided
|
|
216
|
+
if (fallback) {
|
|
217
|
+
return reconstructNode(fallback, options);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Return error placeholder
|
|
221
|
+
const errorEl = adapter.createElement('div');
|
|
222
|
+
adapter.setAttribute(errorEl, 'class', 'pulse-client-error');
|
|
223
|
+
adapter.setAttribute(errorEl, 'data-error', error.message);
|
|
224
|
+
const errorText = adapter.createTextNode(`Error loading component: ${id}`);
|
|
225
|
+
adapter.appendChild(errorEl, errorText);
|
|
226
|
+
return errorEl;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Client Component Loading
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Lazy-load a Client Component chunk from the given URL.
|
|
236
|
+
* Uses dynamic import() and caches the result.
|
|
237
|
+
*
|
|
238
|
+
* @param {string} componentId - Component ID
|
|
239
|
+
* @param {string} chunkUrl - URL to the component JS chunk
|
|
240
|
+
* @returns {Promise<Function>} Component function
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* const Button = await loadClientComponent('Button', '/assets/Button.js');
|
|
244
|
+
* const buttonEl = Button({ text: 'Click me' });
|
|
245
|
+
*/
|
|
246
|
+
export async function loadClientComponent(componentId, chunkUrl) {
|
|
247
|
+
// Check cache
|
|
248
|
+
if (componentCache.has(componentId)) {
|
|
249
|
+
return componentCache.get(componentId);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check if already loading
|
|
253
|
+
if (loadingComponents.has(componentId)) {
|
|
254
|
+
return loadingComponents.get(componentId);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Start loading
|
|
258
|
+
const loadPromise = (async () => {
|
|
259
|
+
try {
|
|
260
|
+
log.debug(`Loading Client Component '${componentId}' from ${chunkUrl}`);
|
|
261
|
+
|
|
262
|
+
// Dynamic import
|
|
263
|
+
const module = await import(/* @vite-ignore */ chunkUrl);
|
|
264
|
+
|
|
265
|
+
// Get default export or named export matching component ID
|
|
266
|
+
let ComponentFn = module.default || module[componentId];
|
|
267
|
+
|
|
268
|
+
if (!ComponentFn) {
|
|
269
|
+
throw new RuntimeError(
|
|
270
|
+
`Client Component '${componentId}' not found in module`,
|
|
271
|
+
{
|
|
272
|
+
code: 'PSC_COMPONENT_EXPORT_NOT_FOUND',
|
|
273
|
+
context: `Available exports: ${Object.keys(module).join(', ')}`,
|
|
274
|
+
suggestion: 'Ensure the component is exported as default or with matching name'
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Cache the loaded component
|
|
280
|
+
componentCache.set(componentId, ComponentFn);
|
|
281
|
+
loadingComponents.delete(componentId);
|
|
282
|
+
|
|
283
|
+
return ComponentFn;
|
|
284
|
+
|
|
285
|
+
} catch (error) {
|
|
286
|
+
loadingComponents.delete(componentId);
|
|
287
|
+
|
|
288
|
+
throw new RuntimeError(
|
|
289
|
+
`Failed to load Client Component '${componentId}'`,
|
|
290
|
+
{
|
|
291
|
+
code: 'PSC_COMPONENT_LOAD_FAILED',
|
|
292
|
+
context: `URL: ${chunkUrl}`,
|
|
293
|
+
suggestion: 'Check that the chunk URL is correct and the file exists',
|
|
294
|
+
cause: error
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
})();
|
|
299
|
+
|
|
300
|
+
loadingComponents.set(componentId, loadPromise);
|
|
301
|
+
return loadPromise;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Preload a Client Component chunk (without executing it).
|
|
306
|
+
* Useful for prefetching components on hover or intersection.
|
|
307
|
+
*
|
|
308
|
+
* @param {string} componentId - Component ID
|
|
309
|
+
* @param {string} chunkUrl - URL to the component JS chunk
|
|
310
|
+
* @returns {Promise<void>}
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* // Prefetch on hover
|
|
314
|
+
* element.addEventListener('mouseenter', () => {
|
|
315
|
+
* preloadClientComponent('Dashboard', '/assets/Dashboard.js');
|
|
316
|
+
* });
|
|
317
|
+
*/
|
|
318
|
+
export async function preloadClientComponent(componentId, chunkUrl) {
|
|
319
|
+
if (componentCache.has(componentId)) {
|
|
320
|
+
return; // Already loaded
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Use link rel="modulepreload" for faster loading
|
|
324
|
+
if (typeof document !== 'undefined' && document.createElement) {
|
|
325
|
+
const link = document.createElement('link');
|
|
326
|
+
link.rel = 'modulepreload';
|
|
327
|
+
link.href = chunkUrl;
|
|
328
|
+
document.head.appendChild(link);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Also start the actual load
|
|
332
|
+
await loadClientComponent(componentId, chunkUrl);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ============================================================================
|
|
336
|
+
// Full PSC Tree Reconstruction
|
|
337
|
+
// ============================================================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Reconstruct a complete PSC payload to DOM tree.
|
|
341
|
+
* This is the main entry point for PSC reconstruction on the client.
|
|
342
|
+
*
|
|
343
|
+
* @param {PSCPayload} payload - Complete PSC payload from server
|
|
344
|
+
* @returns {Promise<Node>} Reconstructed DOM tree
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* const response = await fetch('/products/123?_psc=1');
|
|
348
|
+
* const payload = await response.json();
|
|
349
|
+
* const domTree = await reconstructPSCTree(payload);
|
|
350
|
+
* document.getElementById('app').replaceChildren(domTree);
|
|
351
|
+
*/
|
|
352
|
+
export async function reconstructPSCTree(payload) {
|
|
353
|
+
if (!payload || !payload.root) {
|
|
354
|
+
throw new RuntimeError('Invalid PSC payload: missing root', {
|
|
355
|
+
code: 'PSC_INVALID_PAYLOAD'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Reconstruct the root node
|
|
360
|
+
const root = await reconstructNode(payload.root, {
|
|
361
|
+
clientManifest: payload.clientManifest || {},
|
|
362
|
+
clientComponents: {}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return root;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ============================================================================
|
|
369
|
+
// Client Component Hydration
|
|
370
|
+
// ============================================================================
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Hydrate Client Components in a DOM tree.
|
|
374
|
+
* Attaches event listeners and connects to reactive state.
|
|
375
|
+
*
|
|
376
|
+
* This is called after PSC reconstruction to make Client Components interactive.
|
|
377
|
+
*
|
|
378
|
+
* @param {Node} root - Root DOM node
|
|
379
|
+
* @param {Record<string, Function>} clientComponents - Client Component functions
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* const root = await reconstructPSCTree(payload);
|
|
383
|
+
* hydrateClientComponents(root, {
|
|
384
|
+
* Button: ButtonComponent,
|
|
385
|
+
* Chart: ChartComponent
|
|
386
|
+
* });
|
|
387
|
+
*/
|
|
388
|
+
export function hydrateClientComponents(root, clientComponents) {
|
|
389
|
+
// This is a placeholder for future hydration logic
|
|
390
|
+
// In the current implementation, Client Components are already rendered
|
|
391
|
+
// during reconstruction, so no additional hydration is needed.
|
|
392
|
+
//
|
|
393
|
+
// Future enhancements:
|
|
394
|
+
// 1. Identify Client Component boundaries in existing SSR'd HTML
|
|
395
|
+
// 2. Match them with Client Component functions
|
|
396
|
+
// 3. Attach event listeners without re-rendering
|
|
397
|
+
// 4. Connect to reactive state
|
|
398
|
+
|
|
399
|
+
log.debug('Client Component hydration (placeholder)');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ============================================================================
|
|
403
|
+
// Cache Management
|
|
404
|
+
// ============================================================================
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Clear the Client Component cache.
|
|
408
|
+
* Useful for testing or forced reloading.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* clearComponentCache();
|
|
412
|
+
*/
|
|
413
|
+
export function clearComponentCache() {
|
|
414
|
+
componentCache.clear();
|
|
415
|
+
loadingComponents.clear();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Get the current state of the component cache.
|
|
420
|
+
*
|
|
421
|
+
* @returns {Object} Cache statistics
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* const stats = getComponentCacheStats();
|
|
425
|
+
* console.log(`Loaded: ${stats.loaded}, Loading: ${stats.loading}`);
|
|
426
|
+
*/
|
|
427
|
+
export function getComponentCacheStats() {
|
|
428
|
+
return {
|
|
429
|
+
loaded: componentCache.size,
|
|
430
|
+
loading: loadingComponents.size,
|
|
431
|
+
components: Array.from(componentCache.keys())
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ============================================================================
|
|
436
|
+
// Exports
|
|
437
|
+
// ============================================================================
|
|
438
|
+
|
|
439
|
+
export default {
|
|
440
|
+
reconstructNode,
|
|
441
|
+
reconstructPSCTree,
|
|
442
|
+
loadClientComponent,
|
|
443
|
+
preloadClientComponent,
|
|
444
|
+
hydrateClientComponents,
|
|
445
|
+
clearComponentCache,
|
|
446
|
+
getComponentCacheStats
|
|
447
|
+
};
|