@coherent.js/core 1.1.2 → 2.0.0-rc.0
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 +60 -72
- package/dist/index.js +849 -506
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/types/index.d.ts +123 -21
package/dist/index.js
CHANGED
|
@@ -473,13 +473,25 @@ function createPerformanceMonitor(options = {}) {
|
|
|
473
473
|
if (startTime !== void 0) {
|
|
474
474
|
const duration = performance.now() - startTime;
|
|
475
475
|
_renderTimers.delete(id);
|
|
476
|
-
recordMetric("
|
|
476
|
+
recordMetric("renderTime", duration);
|
|
477
477
|
return duration;
|
|
478
478
|
}
|
|
479
479
|
return 0;
|
|
480
480
|
}
|
|
481
|
+
function recordRender(operation, duration, fromCache = false, metadata = {}) {
|
|
482
|
+
if (operation === "render") {
|
|
483
|
+
recordMetric("renderTime", duration, { fromCache, ...metadata });
|
|
484
|
+
} else {
|
|
485
|
+
recordMetric("componentCount", 1, { operation, fromCache });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function recordError(operation, error, metadata = {}) {
|
|
489
|
+
recordMetric("errorCount", 1, { operation, error: error?.message, ...metadata });
|
|
490
|
+
}
|
|
481
491
|
return {
|
|
482
492
|
recordMetric,
|
|
493
|
+
recordRender,
|
|
494
|
+
recordError,
|
|
483
495
|
measure,
|
|
484
496
|
measureAsync,
|
|
485
497
|
addMetric,
|
|
@@ -506,95 +518,98 @@ function createPerformanceMonitor(options = {}) {
|
|
|
506
518
|
var performanceMonitor = createPerformanceMonitor();
|
|
507
519
|
|
|
508
520
|
// src/core/html-utils.js
|
|
521
|
+
var HTML_ESCAPE_TEST = /[&<>"']/;
|
|
522
|
+
var HTML_ESCAPE = /[&<>"']/g;
|
|
523
|
+
var HTML_ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
509
524
|
function escapeHtml(text) {
|
|
510
525
|
if (typeof text !== "string") return text;
|
|
511
|
-
|
|
526
|
+
if (!HTML_ESCAPE_TEST.test(text)) return text;
|
|
527
|
+
return text.replace(HTML_ESCAPE, (ch) => HTML_ESCAPES[ch]);
|
|
528
|
+
}
|
|
529
|
+
var TRUSTED_CONTENT = /* @__PURE__ */ Symbol.for("coherent.js.trustedContent");
|
|
530
|
+
function createTrustedContent(content) {
|
|
531
|
+
const marker = { __html: String(content), __trusted: true };
|
|
532
|
+
Object.defineProperty(marker, TRUSTED_CONTENT, { value: true });
|
|
533
|
+
return Object.freeze(marker);
|
|
512
534
|
}
|
|
513
535
|
function isTrustedContent(value) {
|
|
514
|
-
return Boolean(value) && typeof value === "object" && value
|
|
536
|
+
return Boolean(value) && typeof value === "object" && value[TRUSTED_CONTENT] === true && typeof value.__html === "string";
|
|
537
|
+
}
|
|
538
|
+
var INVALID_ATTRIBUTE_NAME = /[\s"'<>/=\u0000-\u001F\u007F-\u009F]/;
|
|
539
|
+
function isValidAttributeName(name) {
|
|
540
|
+
return typeof name === "string" && name.length > 0 && !INVALID_ATTRIBUTE_NAME.test(name);
|
|
515
541
|
}
|
|
516
542
|
function isVoidElement(tagName) {
|
|
517
543
|
if (typeof tagName !== "string") {
|
|
518
544
|
return false;
|
|
519
545
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
"
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
"
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
"source",
|
|
533
|
-
"track",
|
|
534
|
-
"wbr"
|
|
535
|
-
]);
|
|
536
|
-
return voidElements.has(tagName.toLowerCase());
|
|
546
|
+
return voidElements.has(tagName) || voidElements.has(tagName.toLowerCase());
|
|
547
|
+
}
|
|
548
|
+
var ENUMERATED_BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set(["spellcheck", "draggable", "contenteditable"]);
|
|
549
|
+
function normalizeClassValue(value) {
|
|
550
|
+
if (Array.isArray(value)) {
|
|
551
|
+
return value.map(normalizeClassValue).filter(Boolean).join(" ");
|
|
552
|
+
}
|
|
553
|
+
if (value && typeof value === "object") {
|
|
554
|
+
return Object.keys(value).filter((name) => value[name]).join(" ");
|
|
555
|
+
}
|
|
556
|
+
if (value === null || value === void 0 || value === false) return "";
|
|
557
|
+
return String(value);
|
|
537
558
|
}
|
|
538
|
-
function
|
|
559
|
+
function callAttribute(key, value) {
|
|
560
|
+
try {
|
|
561
|
+
return value();
|
|
562
|
+
} catch (_error) {
|
|
563
|
+
console.warn(`Error executing function for attribute '${key}':`, {
|
|
564
|
+
error: _error.message,
|
|
565
|
+
stack: _error.stack,
|
|
566
|
+
attributeKey: key
|
|
567
|
+
});
|
|
568
|
+
return "";
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
var ATTRIBUTE_NAMES = {
|
|
572
|
+
className: "class",
|
|
573
|
+
// Written as is, browsers read `htmlFor` as an unknown `htmlfor`
|
|
574
|
+
// attribute: the label was not associated with its control.
|
|
575
|
+
htmlFor: "for"
|
|
576
|
+
};
|
|
577
|
+
function styleToCss(style) {
|
|
578
|
+
return Object.entries(style).filter(([, val]) => val !== null && val !== void 0 && val !== false).map(([prop, val]) => {
|
|
579
|
+
const name = prop.startsWith("--") ? prop : prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
580
|
+
return `${name}: ${val}`;
|
|
581
|
+
}).join("; ");
|
|
582
|
+
}
|
|
583
|
+
function formatAttributes(props, skip) {
|
|
584
|
+
if (props && props.class !== void 0 && props.className !== void 0) {
|
|
585
|
+
const { className, ...rest } = props;
|
|
586
|
+
const resolve = (key, value) => normalizeClassValue(typeof value === "function" ? callAttribute(key, value) : value);
|
|
587
|
+
props = { ...rest, class: [resolve("class", props.class), resolve("className", className)].filter(Boolean).join(" ") };
|
|
588
|
+
}
|
|
539
589
|
let formatted = "";
|
|
540
590
|
for (const key in props) {
|
|
541
|
-
if (
|
|
591
|
+
if (Object.prototype.hasOwnProperty.call(props, key) && !(skip && skip.has(key))) {
|
|
542
592
|
let value = props[key];
|
|
543
|
-
const attributeName = key
|
|
593
|
+
const attributeName = ATTRIBUTE_NAMES[key] ?? key;
|
|
594
|
+
if (!isValidAttributeName(attributeName)) {
|
|
595
|
+
throw new Error(`Invalid attribute name ${JSON.stringify(key)}: attribute names cannot contain whitespace, quotes, '<', '>', '/', '=' or control characters`);
|
|
596
|
+
}
|
|
544
597
|
if (typeof value === "function") {
|
|
545
598
|
if (attributeName.startsWith("on")) {
|
|
546
|
-
const actionId = `__coherent_action_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
547
|
-
const DEBUG = typeof process !== "undefined" && process && process.env && (process.env.COHERENT_DEBUG === "1" || true) || typeof window !== "undefined" && window && window.COHERENT_DEBUG === true;
|
|
548
|
-
if (typeof global !== "undefined") {
|
|
549
|
-
if (!global.__coherentActionRegistry) {
|
|
550
|
-
global.__coherentActionRegistry = {};
|
|
551
|
-
if (DEBUG) console.log("Initialized global action registry");
|
|
552
|
-
}
|
|
553
|
-
global.__coherentActionRegistry[actionId] = value;
|
|
554
|
-
if (DEBUG) console.log(`Added action ${actionId} to global registry, total: ${Object.keys(global.__coherentActionRegistry).length}`);
|
|
555
|
-
if (DEBUG) console.log(`Global registry keys: ${Object.keys(global.__coherentActionRegistry).join(", ")}`);
|
|
556
|
-
if (DEBUG) {
|
|
557
|
-
if (typeof global.__coherentActionRegistryLog === "undefined") {
|
|
558
|
-
global.__coherentActionRegistryLog = [];
|
|
559
|
-
}
|
|
560
|
-
global.__coherentActionRegistryLog.push({
|
|
561
|
-
action: "add",
|
|
562
|
-
actionId,
|
|
563
|
-
timestamp: Date.now(),
|
|
564
|
-
registrySize: Object.keys(global.__coherentActionRegistry).length
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
} else if (typeof window !== "undefined") {
|
|
568
|
-
if (!window.__coherentActionRegistry) {
|
|
569
|
-
window.__coherentActionRegistry = {};
|
|
570
|
-
if (DEBUG) console.log("Initialized window action registry");
|
|
571
|
-
}
|
|
572
|
-
window.__coherentActionRegistry[actionId] = value;
|
|
573
|
-
if (DEBUG) console.log(`Added action ${actionId} to window registry, total: ${Object.keys(window.__coherentActionRegistry).length}`);
|
|
574
|
-
if (DEBUG) console.log(`Window registry keys: ${Object.keys(window.__coherentActionRegistry).join(", ")}`);
|
|
575
|
-
}
|
|
576
|
-
const eventType = attributeName.substring(2);
|
|
577
|
-
formatted += ` data-action="${actionId}" data-event="${eventType}"`;
|
|
578
599
|
continue;
|
|
579
600
|
} else {
|
|
580
|
-
|
|
581
|
-
value = value();
|
|
582
|
-
} catch (_error) {
|
|
583
|
-
console.warn(`Error executing function for attribute '${key}':`, {
|
|
584
|
-
_error: _error.message,
|
|
585
|
-
stack: _error.stack,
|
|
586
|
-
attributeKey: key
|
|
587
|
-
});
|
|
588
|
-
value = "";
|
|
589
|
-
}
|
|
601
|
+
value = callAttribute(key, value);
|
|
590
602
|
}
|
|
591
603
|
}
|
|
604
|
+
if (attributeName === "class" && typeof value === "object" && value !== null) {
|
|
605
|
+
value = normalizeClassValue(value);
|
|
606
|
+
}
|
|
607
|
+
if (typeof value === "boolean" && (attributeName.startsWith("aria-") || ENUMERATED_BOOLEAN_ATTRIBUTES.has(attributeName.toLowerCase()))) {
|
|
608
|
+
value = String(value);
|
|
609
|
+
}
|
|
592
610
|
if (attributeName === "style" && typeof value === "object" && value !== null) {
|
|
593
|
-
const cssString =
|
|
594
|
-
|
|
595
|
-
return `${kebabProp}: ${val}`;
|
|
596
|
-
}).join("; ");
|
|
597
|
-
formatted += ` ${attributeName}="${escapeHtml(cssString)}"`;
|
|
611
|
+
const cssString = styleToCss(value);
|
|
612
|
+
if (cssString) formatted += ` ${attributeName}="${escapeHtml(cssString)}"`;
|
|
598
613
|
} else if (value === true) {
|
|
599
614
|
formatted += ` ${attributeName}`;
|
|
600
615
|
} else if (value !== false && value !== null && value !== void 0) {
|
|
@@ -616,10 +631,69 @@ function stripComments(html) {
|
|
|
616
631
|
cursor = end + 3;
|
|
617
632
|
}
|
|
618
633
|
}
|
|
634
|
+
function endsInComment(html) {
|
|
635
|
+
let cursor = 0;
|
|
636
|
+
for (; ; ) {
|
|
637
|
+
const start = html.indexOf("<!--", cursor);
|
|
638
|
+
if (start === -1) return false;
|
|
639
|
+
const end = html.indexOf("-->", start + 4);
|
|
640
|
+
if (end === -1 || end + 3 === html.length) return true;
|
|
641
|
+
cursor = end + 3;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function createStreamMinifier() {
|
|
645
|
+
let pending = "";
|
|
646
|
+
let started = false;
|
|
647
|
+
const minifyPiece = (text, last) => {
|
|
648
|
+
let out = stripComments(started ? `>${text}` : text).replace(/\s+/g, " ").replace(/>\s+</g, "><");
|
|
649
|
+
if (started) {
|
|
650
|
+
out = out.slice(1);
|
|
651
|
+
} else {
|
|
652
|
+
out = out.trimStart();
|
|
653
|
+
}
|
|
654
|
+
if (last) out = out.trimEnd();
|
|
655
|
+
started = true;
|
|
656
|
+
return out;
|
|
657
|
+
};
|
|
658
|
+
return {
|
|
659
|
+
push(chunk) {
|
|
660
|
+
pending += chunk;
|
|
661
|
+
let cut = pending.lastIndexOf(">");
|
|
662
|
+
while (cut !== -1 && endsInComment(pending.slice(0, cut + 1))) {
|
|
663
|
+
cut = cut === 0 ? -1 : pending.lastIndexOf(">", cut - 1);
|
|
664
|
+
}
|
|
665
|
+
if (cut === -1) return "";
|
|
666
|
+
const head = pending.slice(0, cut + 1);
|
|
667
|
+
pending = pending.slice(cut + 1);
|
|
668
|
+
return minifyPiece(head, false);
|
|
669
|
+
},
|
|
670
|
+
end() {
|
|
671
|
+
const rest = pending;
|
|
672
|
+
pending = "";
|
|
673
|
+
return rest || !started ? minifyPiece(rest, true) : "";
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
619
677
|
function minifyHtml(html, options = {}) {
|
|
620
678
|
if (!options.minify) return html;
|
|
621
679
|
return stripComments(html).replace(/\s+/g, " ").replace(/>\s+</g, "><").trim();
|
|
622
680
|
}
|
|
681
|
+
var voidElements = /* @__PURE__ */ new Set([
|
|
682
|
+
"area",
|
|
683
|
+
"base",
|
|
684
|
+
"br",
|
|
685
|
+
"col",
|
|
686
|
+
"embed",
|
|
687
|
+
"hr",
|
|
688
|
+
"img",
|
|
689
|
+
"input",
|
|
690
|
+
"link",
|
|
691
|
+
"meta",
|
|
692
|
+
"param",
|
|
693
|
+
"source",
|
|
694
|
+
"track",
|
|
695
|
+
"wbr"
|
|
696
|
+
]);
|
|
623
697
|
|
|
624
698
|
// src/core/object-utils.js
|
|
625
699
|
function deepClone(obj, seen = /* @__PURE__ */ new WeakMap()) {
|
|
@@ -706,6 +780,9 @@ function validateComponent(component, path = "root") {
|
|
|
706
780
|
if (typeof component === "function") {
|
|
707
781
|
return true;
|
|
708
782
|
}
|
|
783
|
+
if (isTrustedContent(component)) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
709
786
|
if (Array.isArray(component)) {
|
|
710
787
|
component.forEach((child, index) => {
|
|
711
788
|
validateComponent(child, `${path}[${index}]`);
|
|
@@ -747,22 +824,6 @@ function isCoherentObject(obj) {
|
|
|
747
824
|
return /^[a-zA-Z][a-zA-Z0-9-]*$/.test(key);
|
|
748
825
|
});
|
|
749
826
|
}
|
|
750
|
-
function extractProps(coherentObj) {
|
|
751
|
-
if (!isCoherentObject(coherentObj)) {
|
|
752
|
-
return {};
|
|
753
|
-
}
|
|
754
|
-
const props = {};
|
|
755
|
-
const keys = Object.keys(coherentObj);
|
|
756
|
-
keys.forEach((tag) => {
|
|
757
|
-
const value = coherentObj[tag];
|
|
758
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
759
|
-
props[tag] = { ...value };
|
|
760
|
-
} else {
|
|
761
|
-
props[tag] = { text: value };
|
|
762
|
-
}
|
|
763
|
-
});
|
|
764
|
-
return props;
|
|
765
|
-
}
|
|
766
827
|
function hasChildren(component) {
|
|
767
828
|
if (Array.isArray(component)) {
|
|
768
829
|
return component.length > 0;
|
|
@@ -784,6 +845,15 @@ function normalizeChildren(children) {
|
|
|
784
845
|
return [];
|
|
785
846
|
}
|
|
786
847
|
if (Array.isArray(children)) {
|
|
848
|
+
let clean = true;
|
|
849
|
+
for (let i = 0; i < children.length; i++) {
|
|
850
|
+
const child = children[i];
|
|
851
|
+
if (child === null || child === void 0 || Array.isArray(child)) {
|
|
852
|
+
clean = false;
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (clean) return children;
|
|
787
857
|
return children.flat().filter((child) => child !== null && child !== void 0);
|
|
788
858
|
}
|
|
789
859
|
return [children];
|
|
@@ -797,7 +867,9 @@ var DEFAULT_RENDERER_CONFIG = {
|
|
|
797
867
|
enableMonitoring: false,
|
|
798
868
|
validateInput: true,
|
|
799
869
|
// HTML Renderer specific options
|
|
800
|
-
|
|
870
|
+
// Off by default: keying a render on its content costs a full walk of
|
|
871
|
+
// the tree, which only pays off when identical trees are re-rendered.
|
|
872
|
+
enableCache: false,
|
|
801
873
|
minify: false,
|
|
802
874
|
cacheSize: 1e3,
|
|
803
875
|
cacheTTL: 3e5,
|
|
@@ -883,7 +955,7 @@ var BaseRenderer = class {
|
|
|
883
955
|
return {
|
|
884
956
|
...baseConfig,
|
|
885
957
|
// HTML-specific defaults
|
|
886
|
-
enableCache: baseConfig.enableCache
|
|
958
|
+
enableCache: baseConfig.enableCache === true,
|
|
887
959
|
enableMonitoring: baseConfig.enableMonitoring !== false
|
|
888
960
|
};
|
|
889
961
|
case "streaming":
|
|
@@ -916,11 +988,14 @@ var BaseRenderer = class {
|
|
|
916
988
|
/**
|
|
917
989
|
* Check if component is valid for rendering
|
|
918
990
|
*/
|
|
919
|
-
isValidComponent(component) {
|
|
991
|
+
isValidComponent(component, depth = 0) {
|
|
920
992
|
if (component === null || component === void 0) return true;
|
|
921
993
|
if (typeof component === "string" || typeof component === "number") return true;
|
|
922
994
|
if (typeof component === "function") return true;
|
|
923
|
-
if (Array.isArray(component))
|
|
995
|
+
if (Array.isArray(component)) {
|
|
996
|
+
if (depth >= this.config.maxDepth) return true;
|
|
997
|
+
return component.every((child) => this.isValidComponent(child, depth + 1));
|
|
998
|
+
}
|
|
924
999
|
if (isCoherentObject(component)) return true;
|
|
925
1000
|
return false;
|
|
926
1001
|
}
|
|
@@ -942,7 +1017,10 @@ var BaseRenderer = class {
|
|
|
942
1017
|
if (typeof component === "string") {
|
|
943
1018
|
return { type: "text", value: component };
|
|
944
1019
|
}
|
|
945
|
-
if (typeof component === "
|
|
1020
|
+
if (typeof component === "boolean") {
|
|
1021
|
+
return { type: "empty", value: "" };
|
|
1022
|
+
}
|
|
1023
|
+
if (typeof component === "number") {
|
|
946
1024
|
return { type: "text", value: String(component) };
|
|
947
1025
|
}
|
|
948
1026
|
if (typeof component === "function") {
|
|
@@ -957,31 +1035,23 @@ var BaseRenderer = class {
|
|
|
957
1035
|
return { type: "unknown", value: component };
|
|
958
1036
|
}
|
|
959
1037
|
/**
|
|
960
|
-
* Execute function components
|
|
1038
|
+
* Execute function components. Errors propagate: they used to be
|
|
1039
|
+
* swallowed here (the component rendered as nothing, logged only when
|
|
1040
|
+
* NODE_ENV=development), so a broken component produced a partial page
|
|
1041
|
+
* with a 200 and error boundaries never saw nested failures.
|
|
961
1042
|
*/
|
|
962
1043
|
executeFunctionComponent(func, depth = 0) {
|
|
963
1044
|
try {
|
|
964
|
-
const
|
|
965
|
-
let result;
|
|
966
|
-
if (isContextProvider) {
|
|
967
|
-
result = func((children) => {
|
|
968
|
-
return this.renderComponent(children, this.config, depth + 1);
|
|
969
|
-
});
|
|
970
|
-
} else {
|
|
971
|
-
result = func();
|
|
972
|
-
}
|
|
1045
|
+
const result = func();
|
|
973
1046
|
if (typeof result === "function") {
|
|
974
1047
|
return this.executeFunctionComponent(result, depth);
|
|
975
1048
|
}
|
|
976
1049
|
return result;
|
|
977
|
-
} catch (
|
|
1050
|
+
} catch (error) {
|
|
978
1051
|
if (this.config.enableMonitoring) {
|
|
979
|
-
performanceMonitor.recordError("functionComponent",
|
|
980
|
-
}
|
|
981
|
-
if (typeof process !== "undefined" && process.env && true) {
|
|
982
|
-
console.warn("Coherent.js Function Component Error:", _error.message);
|
|
1052
|
+
performanceMonitor.recordError("functionComponent", error);
|
|
983
1053
|
}
|
|
984
|
-
|
|
1054
|
+
throw error;
|
|
985
1055
|
}
|
|
986
1056
|
}
|
|
987
1057
|
/**
|
|
@@ -1082,88 +1152,43 @@ var BaseRenderer = class {
|
|
|
1082
1152
|
throw new Error("render must be implemented by subclass");
|
|
1083
1153
|
}
|
|
1084
1154
|
};
|
|
1085
|
-
var
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
if (
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
return true;
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
return false;
|
|
1121
|
-
},
|
|
1122
|
-
/**
|
|
1123
|
-
* Get element complexity score
|
|
1124
|
-
*/
|
|
1125
|
-
getElementComplexity(element) {
|
|
1126
|
-
if (!element || typeof element !== "object") return 1;
|
|
1127
|
-
let complexity = Object.keys(element).length;
|
|
1128
|
-
if (element.children && Array.isArray(element.children)) {
|
|
1129
|
-
complexity += element.children.reduce(
|
|
1130
|
-
(sum, child) => sum + RendererUtils.getElementComplexity(child),
|
|
1131
|
-
0
|
|
1132
|
-
);
|
|
1133
|
-
}
|
|
1134
|
-
return complexity;
|
|
1135
|
-
},
|
|
1136
|
-
/**
|
|
1137
|
-
* Generate cache key for element
|
|
1138
|
-
*/
|
|
1139
|
-
generateCacheKey(tagName, element) {
|
|
1140
|
-
try {
|
|
1141
|
-
const keyData = {
|
|
1142
|
-
tag: tagName,
|
|
1143
|
-
props: extractProps(element),
|
|
1144
|
-
hasChildren: hasChildren(element),
|
|
1145
|
-
childrenType: Array.isArray(element.children) ? "array" : typeof element.children
|
|
1146
|
-
};
|
|
1147
|
-
return `element:${JSON.stringify(keyData)}`;
|
|
1148
|
-
} catch (_error) {
|
|
1149
|
-
if (typeof process !== "undefined" && process.env && true) {
|
|
1150
|
-
console.warn("Failed to generate cache key:", _error);
|
|
1151
|
-
}
|
|
1152
|
-
return null;
|
|
1155
|
+
var UNCACHEABLE = /* @__PURE__ */ Symbol("uncacheable");
|
|
1156
|
+
function serializeForCache(value) {
|
|
1157
|
+
try {
|
|
1158
|
+
return serialize(value, /* @__PURE__ */ new Set());
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
if (error === UNCACHEABLE) return null;
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function serialize(value, ancestors) {
|
|
1165
|
+
switch (typeof value) {
|
|
1166
|
+
case "string":
|
|
1167
|
+
return JSON.stringify(value);
|
|
1168
|
+
case "number":
|
|
1169
|
+
return `n${value}`;
|
|
1170
|
+
case "boolean":
|
|
1171
|
+
return value ? "t" : "f";
|
|
1172
|
+
case "undefined":
|
|
1173
|
+
return "u";
|
|
1174
|
+
case "object": {
|
|
1175
|
+
if (value === null) return "z";
|
|
1176
|
+
if (isTrustedContent(value)) return `T${JSON.stringify(value.__html)}`;
|
|
1177
|
+
if (ancestors.has(value)) throw UNCACHEABLE;
|
|
1178
|
+
const isArray = Array.isArray(value);
|
|
1179
|
+
if (!isArray) {
|
|
1180
|
+
const proto = Object.getPrototypeOf(value);
|
|
1181
|
+
if (proto !== Object.prototype && proto !== null) throw UNCACHEABLE;
|
|
1182
|
+
}
|
|
1183
|
+
ancestors.add(value);
|
|
1184
|
+
const body = isArray ? value.map((item) => serialize(item, ancestors)).join(",") : Object.keys(value).map((key) => `${JSON.stringify(key)}:${serialize(value[key], ancestors)}`).join(",");
|
|
1185
|
+
ancestors.delete(value);
|
|
1186
|
+
return isArray ? `[${body}]` : `{${body}}`;
|
|
1153
1187
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
* Check if element is cacheable
|
|
1157
|
-
*/
|
|
1158
|
-
isCacheable(element, options) {
|
|
1159
|
-
if (!options.enableCache) return false;
|
|
1160
|
-
if (RendererUtils.hasFunctions(element)) return false;
|
|
1161
|
-
if (RendererUtils.getElementComplexity(element) > 1e3) return false;
|
|
1162
|
-
const cacheKey = RendererUtils.generateCacheKey(element.tagName || "unknown", element);
|
|
1163
|
-
if (!cacheKey) return false;
|
|
1164
|
-
return true;
|
|
1188
|
+
default:
|
|
1189
|
+
throw UNCACHEABLE;
|
|
1165
1190
|
}
|
|
1166
|
-
}
|
|
1191
|
+
}
|
|
1167
1192
|
|
|
1168
1193
|
// src/core/html-nesting-rules.js
|
|
1169
1194
|
var FORBIDDEN_CHILDREN = {
|
|
@@ -1249,12 +1274,13 @@ var HTMLNestingError = class extends Error {
|
|
|
1249
1274
|
// src/performance/cache-manager.js
|
|
1250
1275
|
function createCacheManager(options = {}) {
|
|
1251
1276
|
const {
|
|
1252
|
-
maxCacheSize = 1e3,
|
|
1253
1277
|
maxMemoryMB = 100,
|
|
1254
1278
|
ttlMs = 1e3 * 60 * 5,
|
|
1255
1279
|
// 5 minutes
|
|
1256
1280
|
enableStatistics = true
|
|
1257
1281
|
} = options;
|
|
1282
|
+
const maxCacheSize = options.maxCacheSize ?? options.maxSize ?? 1e3;
|
|
1283
|
+
const maxMemoryBytes = maxMemoryMB * 1024 * 1024;
|
|
1258
1284
|
const caches = {
|
|
1259
1285
|
static: /* @__PURE__ */ new Map(),
|
|
1260
1286
|
// Never-changing components
|
|
@@ -1297,51 +1323,57 @@ function createCacheManager(options = {}) {
|
|
|
1297
1323
|
return `${extractComponentName(component)}_${hash}`;
|
|
1298
1324
|
}
|
|
1299
1325
|
function get(key, type = "component") {
|
|
1300
|
-
const
|
|
1326
|
+
const cacheType = caches[type] ? type : "component";
|
|
1327
|
+
const cache = caches[cacheType];
|
|
1301
1328
|
const entry = cache.get(key);
|
|
1302
1329
|
if (!entry) {
|
|
1303
1330
|
stats.misses++;
|
|
1304
|
-
if (enableStatistics) stats.accessCount[
|
|
1331
|
+
if (enableStatistics) stats.accessCount[cacheType]++;
|
|
1305
1332
|
return null;
|
|
1306
1333
|
}
|
|
1307
|
-
if (Date.now() - entry.timestamp >
|
|
1334
|
+
if (Date.now() - entry.timestamp > entry.ttl) {
|
|
1308
1335
|
cache.delete(key);
|
|
1309
1336
|
updateMemoryUsage(-entry.size);
|
|
1310
1337
|
stats.misses++;
|
|
1311
|
-
if (enableStatistics) stats.accessCount[
|
|
1338
|
+
if (enableStatistics) stats.accessCount[cacheType]++;
|
|
1312
1339
|
return null;
|
|
1313
1340
|
}
|
|
1341
|
+
cache.delete(key);
|
|
1342
|
+
cache.set(key, entry);
|
|
1314
1343
|
entry.lastAccess = Date.now();
|
|
1315
1344
|
entry.accessCount++;
|
|
1316
1345
|
stats.hits++;
|
|
1317
1346
|
if (enableStatistics) {
|
|
1318
|
-
stats.accessCount[
|
|
1319
|
-
stats.hitRate[
|
|
1347
|
+
stats.accessCount[cacheType]++;
|
|
1348
|
+
stats.hitRate[cacheType] = stats.hits / (stats.hits + stats.misses) * 100;
|
|
1320
1349
|
}
|
|
1321
1350
|
return entry.value;
|
|
1322
1351
|
}
|
|
1323
1352
|
function set(key, value, type = "component", metadata = {}) {
|
|
1324
|
-
const
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1353
|
+
const cacheType = caches[type] ? type : "component";
|
|
1354
|
+
const cache = caches[cacheType];
|
|
1355
|
+
const size = calculateSize(value) + calculateSize(key);
|
|
1356
|
+
if (size > maxMemoryBytes) return;
|
|
1357
|
+
const existing = cache.get(key);
|
|
1358
|
+
if (existing) {
|
|
1359
|
+
cache.delete(key);
|
|
1360
|
+
updateMemoryUsage(-existing.size);
|
|
1328
1361
|
}
|
|
1329
|
-
const
|
|
1362
|
+
const now = Date.now();
|
|
1363
|
+
cache.set(key, {
|
|
1330
1364
|
value,
|
|
1331
|
-
timestamp:
|
|
1332
|
-
lastAccess:
|
|
1365
|
+
timestamp: now,
|
|
1366
|
+
lastAccess: now,
|
|
1333
1367
|
size,
|
|
1334
1368
|
metadata,
|
|
1369
|
+
ttl: typeof metadata.ttlMs === "number" ? metadata.ttlMs : ttlMs,
|
|
1335
1370
|
accessCount: 0
|
|
1336
|
-
};
|
|
1337
|
-
const existing = cache.get(key);
|
|
1338
|
-
if (existing) {
|
|
1339
|
-
updateMemoryUsage(-existing.size);
|
|
1340
|
-
}
|
|
1341
|
-
cache.set(key, entry);
|
|
1371
|
+
});
|
|
1342
1372
|
updateMemoryUsage(size);
|
|
1343
|
-
|
|
1344
|
-
|
|
1373
|
+
evict(cache, () => cache.size > maxCacheSize);
|
|
1374
|
+
for (const other of Object.values(caches)) {
|
|
1375
|
+
if (memoryUsage <= maxMemoryBytes) break;
|
|
1376
|
+
evict(other, () => memoryUsage > maxMemoryBytes);
|
|
1345
1377
|
}
|
|
1346
1378
|
}
|
|
1347
1379
|
function remove(key, type) {
|
|
@@ -1368,11 +1400,14 @@ function createCacheManager(options = {}) {
|
|
|
1368
1400
|
if (type) {
|
|
1369
1401
|
const cache = caches[type];
|
|
1370
1402
|
if (cache) {
|
|
1403
|
+
for (const entry of cache.values()) {
|
|
1404
|
+
updateMemoryUsage(-entry.size);
|
|
1405
|
+
}
|
|
1371
1406
|
cache.clear();
|
|
1372
1407
|
}
|
|
1373
|
-
|
|
1374
|
-
Object.values(caches).forEach((cache) => cache.clear());
|
|
1408
|
+
return;
|
|
1375
1409
|
}
|
|
1410
|
+
Object.values(caches).forEach((cache) => cache.clear());
|
|
1376
1411
|
memoryUsage = 0;
|
|
1377
1412
|
}
|
|
1378
1413
|
function getStats() {
|
|
@@ -1391,7 +1426,7 @@ function createCacheManager(options = {}) {
|
|
|
1391
1426
|
let freed = 0;
|
|
1392
1427
|
for (const [, cache] of Object.entries(caches)) {
|
|
1393
1428
|
for (const [key, entry] of cache.entries()) {
|
|
1394
|
-
if (now - entry.timestamp >
|
|
1429
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
1395
1430
|
cache.delete(key);
|
|
1396
1431
|
updateMemoryUsage(-entry.size);
|
|
1397
1432
|
freed++;
|
|
@@ -1416,17 +1451,15 @@ function createCacheManager(options = {}) {
|
|
|
1416
1451
|
function updateMemoryUsage(delta) {
|
|
1417
1452
|
memoryUsage = Math.max(0, memoryUsage + delta);
|
|
1418
1453
|
}
|
|
1419
|
-
function
|
|
1420
|
-
const cache = caches[type] || caches.component;
|
|
1421
|
-
const entries = Array.from(cache.entries()).sort(([, a], [, b]) => a.lastAccess - b.lastAccess);
|
|
1454
|
+
function evict(cache, shouldEvict) {
|
|
1422
1455
|
let freed = 0;
|
|
1423
|
-
for (const [key, entry] of
|
|
1424
|
-
if (
|
|
1456
|
+
for (const [key, entry] of cache) {
|
|
1457
|
+
if (!shouldEvict()) break;
|
|
1425
1458
|
cache.delete(key);
|
|
1426
1459
|
updateMemoryUsage(-entry.size);
|
|
1427
1460
|
freed += entry.size;
|
|
1428
1461
|
}
|
|
1429
|
-
return
|
|
1462
|
+
return freed;
|
|
1430
1463
|
}
|
|
1431
1464
|
function simpleHash(str) {
|
|
1432
1465
|
let hash = 0;
|
|
@@ -1581,7 +1614,7 @@ var ErrorHandler = class {
|
|
|
1581
1614
|
enableStackTrace: options.enableStackTrace !== false,
|
|
1582
1615
|
enableSuggestions: options.enableSuggestions !== false,
|
|
1583
1616
|
enableLogging: options.enableLogging ?? defaultEnableLogging,
|
|
1584
|
-
logLevel: options.logLevel || "
|
|
1617
|
+
logLevel: options.logLevel || "error",
|
|
1585
1618
|
maxErrorHistory: options.maxErrorHistory || 100,
|
|
1586
1619
|
...options
|
|
1587
1620
|
};
|
|
@@ -1732,7 +1765,7 @@ var ErrorHandler = class {
|
|
|
1732
1765
|
if (suggestions.length === 0) {
|
|
1733
1766
|
suggestions.push(
|
|
1734
1767
|
"Enable development tools for more detailed debugging",
|
|
1735
|
-
"Check browser console for additional
|
|
1768
|
+
"Check browser console for additional error details",
|
|
1736
1769
|
"Use component validation tools to identify issues"
|
|
1737
1770
|
);
|
|
1738
1771
|
}
|
|
@@ -1874,15 +1907,34 @@ var ErrorHandler = class {
|
|
|
1874
1907
|
var globalErrorHandler = new ErrorHandler();
|
|
1875
1908
|
|
|
1876
1909
|
// src/rendering/html-renderer.js
|
|
1910
|
+
var RESERVED_PROPS = /* @__PURE__ */ new Set(["children", "text", "key", "html"]);
|
|
1877
1911
|
var rendererCache = createCacheManager({
|
|
1878
|
-
|
|
1912
|
+
maxCacheSize: 1e3,
|
|
1879
1913
|
ttlMs: 3e5
|
|
1880
1914
|
// 5 minutes
|
|
1881
1915
|
});
|
|
1916
|
+
function assertNotThenable(value, path) {
|
|
1917
|
+
if (value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") {
|
|
1918
|
+
throw new RenderingError(
|
|
1919
|
+
`Cannot render a Promise at ${path === "root" ? "root" : formatRenderPath(path)}: render() is synchronous. Await async components (and their data) before rendering.`,
|
|
1920
|
+
void 0,
|
|
1921
|
+
{ path: path === "root" ? "root" : formatRenderPath(path), renderer: "html" }
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
function childPath(parent, segment) {
|
|
1926
|
+
return { parent, segment };
|
|
1927
|
+
}
|
|
1882
1928
|
function formatRenderPath(path) {
|
|
1883
|
-
|
|
1929
|
+
const segments = [];
|
|
1930
|
+
if (Array.isArray(path)) {
|
|
1931
|
+
segments.push(...path);
|
|
1932
|
+
} else {
|
|
1933
|
+
for (let node = path; node; node = node.parent) segments.push(node.segment);
|
|
1934
|
+
segments.reverse();
|
|
1935
|
+
}
|
|
1884
1936
|
let rendered = "root";
|
|
1885
|
-
for (const segment of
|
|
1937
|
+
for (const segment of segments) {
|
|
1886
1938
|
if (typeof segment !== "string" || segment.length === 0) continue;
|
|
1887
1939
|
if (segment.startsWith("[")) {
|
|
1888
1940
|
rendered += segment;
|
|
@@ -1895,15 +1947,15 @@ function formatRenderPath(path) {
|
|
|
1895
1947
|
var HTMLRenderer = class extends BaseRenderer {
|
|
1896
1948
|
constructor(options = {}) {
|
|
1897
1949
|
super({
|
|
1898
|
-
enableCache: options.enableCache !== false,
|
|
1899
1950
|
enableMonitoring: options.enableMonitoring !== false,
|
|
1900
1951
|
minify: options.minify || false,
|
|
1901
1952
|
streaming: options.streaming || false,
|
|
1902
1953
|
maxDepth: options.maxDepth || 100,
|
|
1903
|
-
...options
|
|
1954
|
+
...options,
|
|
1955
|
+
enableCache: options.enableCache === true
|
|
1904
1956
|
});
|
|
1905
|
-
if (this.config.enableCache
|
|
1906
|
-
this.cache = rendererCache;
|
|
1957
|
+
if (this.config.enableCache) {
|
|
1958
|
+
this.cache = options.cache || rendererCache;
|
|
1907
1959
|
}
|
|
1908
1960
|
}
|
|
1909
1961
|
/**
|
|
@@ -1931,15 +1983,30 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
1931
1983
|
const config = { ...this.config, ...options };
|
|
1932
1984
|
this.startTiming();
|
|
1933
1985
|
try {
|
|
1986
|
+
assertNotThenable(component, "root");
|
|
1934
1987
|
if (config.validateInput && !this.isValidComponent(component)) {
|
|
1935
1988
|
throw new Error("Invalid component structure");
|
|
1936
1989
|
}
|
|
1990
|
+
const cacheKey = this.cache && config.enableCache ? serializeForCache(component) : null;
|
|
1991
|
+
const fullKey = cacheKey === null ? null : `render:${config.minify ? "min" : "raw"}:${cacheKey}`;
|
|
1992
|
+
if (fullKey !== null) {
|
|
1993
|
+
const cached = this.cache.get(fullKey);
|
|
1994
|
+
if (cached !== null) {
|
|
1995
|
+
this.endTiming();
|
|
1996
|
+
return cached;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1937
1999
|
const renderOptions = {
|
|
1938
2000
|
...config,
|
|
1939
2001
|
seenObjects: /* @__PURE__ */ new WeakSet()
|
|
1940
2002
|
};
|
|
1941
|
-
const html = this.renderComponent(component, renderOptions, 0,
|
|
2003
|
+
const html = this.renderComponent(component, renderOptions, 0, null);
|
|
1942
2004
|
const finalHtml = config.minify ? minifyHtml(html, config) : html;
|
|
2005
|
+
if (fullKey !== null) {
|
|
2006
|
+
this.cache.set(fullKey, finalHtml, "component", {
|
|
2007
|
+
ttlMs: typeof config.cacheTTL === "number" ? config.cacheTTL : void 0
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
1943
2010
|
this.endTiming();
|
|
1944
2011
|
this.recordPerformance("render", this.metrics.startTime, false, {
|
|
1945
2012
|
cacheEnabled: config.enableCache
|
|
@@ -1959,7 +2026,7 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
1959
2026
|
/**
|
|
1960
2027
|
* Render a single component with full optimization pipeline
|
|
1961
2028
|
*/
|
|
1962
|
-
renderComponent(component, options, depth = 0, path =
|
|
2029
|
+
renderComponent(component, options, depth = 0, path = null) {
|
|
1963
2030
|
if (component === null || component === void 0) {
|
|
1964
2031
|
return "";
|
|
1965
2032
|
}
|
|
@@ -1969,8 +2036,13 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
1969
2036
|
if (isTrustedContent(component)) {
|
|
1970
2037
|
return component.__html;
|
|
1971
2038
|
}
|
|
1972
|
-
if (typeof component === "object" && component
|
|
1973
|
-
|
|
2039
|
+
if (typeof component === "object" && component.__isLazy === true && typeof component.evaluate === "function") {
|
|
2040
|
+
return this.renderComponent(component.evaluate(), options, depth + 1, childPath(path, "()"));
|
|
2041
|
+
}
|
|
2042
|
+
assertNotThenable(component, path);
|
|
2043
|
+
const tracked = options.seenObjects && typeof component === "object" && component !== null ? component : null;
|
|
2044
|
+
if (tracked) {
|
|
2045
|
+
if (options.seenObjects.has(tracked)) {
|
|
1974
2046
|
throw new RenderingError(
|
|
1975
2047
|
"Circular reference detected in component tree",
|
|
1976
2048
|
component,
|
|
@@ -1978,9 +2050,7 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
1978
2050
|
["Remove the circular reference", "Use lazy loading to break the cycle"]
|
|
1979
2051
|
);
|
|
1980
2052
|
}
|
|
1981
|
-
|
|
1982
|
-
options.seenObjects.add(component);
|
|
1983
|
-
}
|
|
2053
|
+
options.seenObjects.add(tracked);
|
|
1984
2054
|
}
|
|
1985
2055
|
this.validateDepth(depth);
|
|
1986
2056
|
try {
|
|
@@ -1991,8 +2061,8 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
1991
2061
|
case "text":
|
|
1992
2062
|
return escapeHtml(value);
|
|
1993
2063
|
case "function": {
|
|
1994
|
-
const result = this.
|
|
1995
|
-
return this.renderComponent(result, options, depth + 1,
|
|
2064
|
+
const result = this.runFunctionComponent(value, options, depth, path);
|
|
2065
|
+
return this.renderComponent(result, options, depth + 1, childPath(path, "()"));
|
|
1996
2066
|
}
|
|
1997
2067
|
case "array":
|
|
1998
2068
|
if (typeof process !== "undefined" && process.env && true && value.length > 1) {
|
|
@@ -2010,11 +2080,23 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
2010
2080
|
);
|
|
2011
2081
|
}
|
|
2012
2082
|
}
|
|
2013
|
-
|
|
2083
|
+
{
|
|
2084
|
+
let html = "";
|
|
2085
|
+
for (let index = 0; index < value.length; index++) {
|
|
2086
|
+
html += this.renderComponent(value[index], options, depth + 1, childPath(path, `[${index}]`));
|
|
2087
|
+
}
|
|
2088
|
+
return html;
|
|
2089
|
+
}
|
|
2014
2090
|
case "element": {
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2091
|
+
const tagNames = Object.keys(value);
|
|
2092
|
+
if (tagNames.length === 1) {
|
|
2093
|
+
return this.renderElement(tagNames[0], value[tagNames[0]], options, depth, childPath(path, tagNames[0]));
|
|
2094
|
+
}
|
|
2095
|
+
let html = "";
|
|
2096
|
+
for (const tagName of tagNames) {
|
|
2097
|
+
html += this.renderElement(tagName, value[tagName], options, depth, childPath(path, tagName));
|
|
2098
|
+
}
|
|
2099
|
+
return html;
|
|
2018
2100
|
}
|
|
2019
2101
|
default:
|
|
2020
2102
|
this.recordError("renderComponent", new Error(`Unknown component type: ${type}`));
|
|
@@ -2030,16 +2112,34 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
2030
2112
|
}
|
|
2031
2113
|
throw _error;
|
|
2032
2114
|
}
|
|
2033
|
-
|
|
2115
|
+
const wrapped = new RenderingError(_error.message, void 0, { path: renderPath, renderer: "html" });
|
|
2116
|
+
wrapped.cause = _error;
|
|
2117
|
+
throw wrapped;
|
|
2118
|
+
} finally {
|
|
2119
|
+
if (tracked) options.seenObjects.delete(tracked);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Run a function component, giving `options.onError` the chance to
|
|
2124
|
+
* replace a component that throws.
|
|
2125
|
+
*/
|
|
2126
|
+
runFunctionComponent(func, options, depth, path) {
|
|
2127
|
+
try {
|
|
2128
|
+
return this.executeFunctionComponent(func, depth);
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
if (typeof options.onError === "function") {
|
|
2131
|
+
return options.onError(error, { path: formatRenderPath(path) });
|
|
2132
|
+
}
|
|
2133
|
+
throw error;
|
|
2034
2134
|
}
|
|
2035
2135
|
}
|
|
2036
2136
|
/**
|
|
2037
2137
|
* Render an HTML element with advanced caching and optimization
|
|
2038
2138
|
*/
|
|
2039
|
-
renderElement(tagName, element, options, depth = 0, path =
|
|
2040
|
-
const
|
|
2041
|
-
if (
|
|
2042
|
-
if (options.seenObjects
|
|
2139
|
+
renderElement(tagName, element, options, depth = 0, path = null) {
|
|
2140
|
+
const tracked = options.seenObjects && element && typeof element === "object" && !Array.isArray(element) ? element : null;
|
|
2141
|
+
if (tracked) {
|
|
2142
|
+
if (options.seenObjects.has(tracked)) {
|
|
2043
2143
|
throw new RenderingError(
|
|
2044
2144
|
"Circular reference detected in component tree",
|
|
2045
2145
|
element,
|
|
@@ -2047,32 +2147,31 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
2047
2147
|
["Remove the circular reference", "Use lazy loading to break the cycle"]
|
|
2048
2148
|
);
|
|
2049
2149
|
}
|
|
2050
|
-
|
|
2051
|
-
options.seenObjects.add(element);
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
if (options.enableMonitoring && this.cache) {
|
|
2150
|
+
options.seenObjects.add(tracked);
|
|
2055
2151
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
if (cached) {
|
|
2061
|
-
this.recordPerformance(tagName, startTime, true);
|
|
2062
|
-
return cached.value;
|
|
2063
|
-
}
|
|
2064
|
-
} catch {
|
|
2065
|
-
}
|
|
2152
|
+
try {
|
|
2153
|
+
return this.renderElementContent(tagName, element, options, depth, path);
|
|
2154
|
+
} finally {
|
|
2155
|
+
if (tracked) options.seenObjects.delete(tracked);
|
|
2066
2156
|
}
|
|
2157
|
+
}
|
|
2158
|
+
renderElementContent(tagName, element, options, depth = 0, path = null) {
|
|
2159
|
+
const startTime = options.enableMonitoring ? performance.now() : 0;
|
|
2067
2160
|
if (typeof element === "string" || typeof element === "number" || typeof element === "boolean") {
|
|
2068
2161
|
const html2 = isVoidElement(tagName) ? `<${tagName}>` : `<${tagName}>${escapeHtml(String(element))}</${tagName}>`;
|
|
2069
|
-
this.cacheIfStatic(tagName, element, html2, options);
|
|
2070
2162
|
this.recordPerformance(tagName, startTime, false);
|
|
2071
2163
|
return html2;
|
|
2072
2164
|
}
|
|
2073
2165
|
if (typeof element === "function") {
|
|
2074
|
-
|
|
2075
|
-
|
|
2166
|
+
let result;
|
|
2167
|
+
try {
|
|
2168
|
+
result = this.executeFunctionComponent(element, depth);
|
|
2169
|
+
} catch (error) {
|
|
2170
|
+
if (typeof options.onError !== "function") throw error;
|
|
2171
|
+
const replacement = options.onError(error, { path: formatRenderPath(path) });
|
|
2172
|
+
return this.renderComponent(replacement, options, depth + 1, childPath(path, "()"));
|
|
2173
|
+
}
|
|
2174
|
+
return this.renderElement(tagName, result, options, depth, childPath(path, "()"));
|
|
2076
2175
|
}
|
|
2077
2176
|
if (element && typeof element === "object") {
|
|
2078
2177
|
return this.renderObjectElement(tagName, element, options, depth, path);
|
|
@@ -2087,105 +2186,255 @@ var HTMLRenderer = class extends BaseRenderer {
|
|
|
2087
2186
|
return html;
|
|
2088
2187
|
}
|
|
2089
2188
|
/**
|
|
2090
|
-
*
|
|
2189
|
+
* Render complex object elements with attributes and children
|
|
2091
2190
|
*/
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2191
|
+
renderObjectElement(tagName, element, options, depth = 0, path = null) {
|
|
2192
|
+
const startTime = options.enableMonitoring ? performance.now() : 0;
|
|
2193
|
+
const parts = elementParts(tagName, element);
|
|
2194
|
+
let html = parts.open + parts.content;
|
|
2195
|
+
if (parts.children) {
|
|
2196
|
+
const forbidden = FORBIDDEN_CHILDREN[tagName.toLowerCase()];
|
|
2197
|
+
for (let index = 0; index < parts.children.length; index++) {
|
|
2198
|
+
const child = parts.children[index];
|
|
2199
|
+
const segment = childPath(path, `children[${index}]`);
|
|
2200
|
+
checkNesting(tagName, forbidden, child, segment);
|
|
2201
|
+
html += this.renderComponent(child, options, depth + 1, segment);
|
|
2103
2202
|
}
|
|
2104
2203
|
}
|
|
2204
|
+
html += parts.close;
|
|
2205
|
+
this.recordPerformance(tagName, startTime, false);
|
|
2206
|
+
return html;
|
|
2105
2207
|
}
|
|
2106
2208
|
/**
|
|
2107
|
-
*
|
|
2209
|
+
* Streaming counterpart of renderComponent: yields HTML pieces. Elements
|
|
2210
|
+
* with many children are streamed child by child; everything else goes
|
|
2211
|
+
* through the synchronous renderer, which is exact and faster, so the
|
|
2212
|
+
* streamed output is the same as render()'s by construction.
|
|
2108
2213
|
*/
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2214
|
+
async *streamComponent(component, options, depth = 0, path = null) {
|
|
2215
|
+
if (component === null || component === void 0) return;
|
|
2216
|
+
this.validateDepth(depth);
|
|
2217
|
+
if (typeof component === "function") {
|
|
2218
|
+
const result = this.runFunctionComponent(component, options, depth, path);
|
|
2219
|
+
yield* this.streamComponent(result, options, depth + 1, childPath(path, "()"));
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
if (Array.isArray(component)) {
|
|
2223
|
+
yield* this.streamTracked(component, options, path, async function* (renderer) {
|
|
2224
|
+
for (let index = 0; index < component.length; index++) {
|
|
2225
|
+
yield* renderer.streamComponent(component[index], options, depth + 1, childPath(path, `[${index}]`));
|
|
2118
2226
|
}
|
|
2227
|
+
});
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
if (typeof component === "object" && !isTrustedContent(component) && component.__isLazy !== true) {
|
|
2231
|
+
const { type, value } = this.processComponentType(component);
|
|
2232
|
+
if (type === "element") {
|
|
2233
|
+
yield* this.streamTracked(component, options, path, async function* (renderer) {
|
|
2234
|
+
for (const tagName of Object.keys(value)) {
|
|
2235
|
+
yield* renderer.streamElement(tagName, value[tagName], options, depth, childPath(path, tagName));
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
return;
|
|
2119
2239
|
}
|
|
2120
2240
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
if (
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2241
|
+
yield this.renderComponent(component, options, depth, path);
|
|
2242
|
+
}
|
|
2243
|
+
async *streamElement(tagName, element, options, depth, path) {
|
|
2244
|
+
if (!element || typeof element !== "object" || Array.isArray(element) || !shouldStream(element.children)) {
|
|
2245
|
+
yield this.renderElement(tagName, element, options, depth, path);
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
yield* this.streamTracked(element, options, path, async function* (renderer) {
|
|
2249
|
+
const parts = elementParts(tagName, element);
|
|
2250
|
+
yield parts.open + parts.content;
|
|
2251
|
+
if (parts.children) {
|
|
2252
|
+
const forbidden = FORBIDDEN_CHILDREN[tagName.toLowerCase()];
|
|
2253
|
+
for (let index = 0; index < parts.children.length; index++) {
|
|
2254
|
+
const child = parts.children[index];
|
|
2255
|
+
const segment = childPath(path, `children[${index}]`);
|
|
2256
|
+
checkNesting(tagName, forbidden, child, segment);
|
|
2257
|
+
yield* renderer.streamComponent(child, options, depth + 1, segment);
|
|
2129
2258
|
}
|
|
2130
2259
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2260
|
+
yield parts.close;
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Run `body` with `value` on the ancestor path used for cycle detection.
|
|
2265
|
+
*/
|
|
2266
|
+
async *streamTracked(value, options, path, body) {
|
|
2267
|
+
if (options.seenObjects.has(value)) {
|
|
2268
|
+
throw new RenderingError(
|
|
2269
|
+
"Circular reference detected in component tree",
|
|
2270
|
+
value,
|
|
2271
|
+
{ path: formatRenderPath(path) },
|
|
2272
|
+
["Remove the circular reference", "Use lazy loading to break the cycle"]
|
|
2273
|
+
);
|
|
2133
2274
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2275
|
+
options.seenObjects.add(value);
|
|
2276
|
+
try {
|
|
2277
|
+
yield* body(this);
|
|
2278
|
+
} finally {
|
|
2279
|
+
options.seenObjects.delete(value);
|
|
2139
2280
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
var STREAM_MIN_CHILDREN = 8;
|
|
2284
|
+
function shouldStream(children) {
|
|
2285
|
+
if (children === void 0 || children === null) return false;
|
|
2286
|
+
const list = Array.isArray(children) ? children : [children];
|
|
2287
|
+
if (list.length >= STREAM_MIN_CHILDREN) return true;
|
|
2288
|
+
for (const child of list) {
|
|
2289
|
+
if (typeof child === "function" || Array.isArray(child)) return true;
|
|
2290
|
+
if (child && typeof child === "object") {
|
|
2291
|
+
for (const key in child) {
|
|
2292
|
+
const content = child[key];
|
|
2293
|
+
if (typeof content === "function") return true;
|
|
2294
|
+
if (content && typeof content === "object" && content.children !== void 0 && content.children !== null) return true;
|
|
2154
2295
|
}
|
|
2155
2296
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2297
|
+
}
|
|
2298
|
+
return false;
|
|
2299
|
+
}
|
|
2300
|
+
function elementParts(tagName, element) {
|
|
2301
|
+
const { children, text: rawText, html: rawHtml } = element || {};
|
|
2302
|
+
const attributeString = formatAttributes(element, RESERVED_PROPS);
|
|
2303
|
+
const open = attributeString ? `<${tagName} ${attributeString}>` : `<${tagName}>`;
|
|
2304
|
+
if (isVoidElement(tagName)) {
|
|
2305
|
+
return { open, content: "", children: null, close: "" };
|
|
2306
|
+
}
|
|
2307
|
+
const close = `</${tagName}>`;
|
|
2308
|
+
const html = typeof rawHtml === "function" ? rawHtml() : rawHtml;
|
|
2309
|
+
if (html !== void 0 && html !== null) {
|
|
2310
|
+
return { open, content: isTrustedContent(html) ? html.__html : String(html), children: null, close };
|
|
2311
|
+
}
|
|
2312
|
+
const text = typeof rawText === "function" && !isTrustedContent(rawText) ? rawText() : rawText;
|
|
2313
|
+
if (isTrustedContent(text)) {
|
|
2314
|
+
return { open, content: text.__html, children: null, close };
|
|
2315
|
+
}
|
|
2316
|
+
let content = "";
|
|
2317
|
+
if (text !== void 0 && text !== null) {
|
|
2318
|
+
const raw = String(text);
|
|
2319
|
+
if (tagName === "script" || tagName === "style") {
|
|
2320
|
+
content = raw.replace(/<\/(script)/gi, "<\\/$1").replace(/<\/(style)/gi, "<\\/$1").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
2321
|
+
} else {
|
|
2322
|
+
content = escapeHtml(raw);
|
|
2168
2323
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2324
|
+
}
|
|
2325
|
+
const normalized = children !== void 0 && children !== null ? normalizeChildren(children) : null;
|
|
2326
|
+
return { open, content, children: normalized && normalized.length > 0 ? normalized : null, close };
|
|
2327
|
+
}
|
|
2328
|
+
function checkNesting(tagName, forbidden, child, path) {
|
|
2329
|
+
if (forbidden && child && typeof child === "object" && !Array.isArray(child)) {
|
|
2330
|
+
const childTagName = Object.keys(child)[0];
|
|
2331
|
+
if (childTagName && forbidden.has(childTagName.toLowerCase())) {
|
|
2332
|
+
validateNesting(tagName, childTagName, formatRenderPath(path));
|
|
2175
2333
|
}
|
|
2176
|
-
this.recordPerformance(tagName, startTime, false);
|
|
2177
|
-
return html;
|
|
2178
2334
|
}
|
|
2179
|
-
}
|
|
2335
|
+
}
|
|
2180
2336
|
function render(component, options = {}) {
|
|
2181
2337
|
const mergedOptions = {
|
|
2182
|
-
enableCache: true,
|
|
2183
2338
|
enableMonitoring: false,
|
|
2184
2339
|
...options
|
|
2185
2340
|
};
|
|
2186
2341
|
const renderer = new HTMLRenderer(mergedOptions);
|
|
2187
2342
|
return renderer.render(component, mergedOptions);
|
|
2188
2343
|
}
|
|
2344
|
+
async function* renderToStream(component, options = {}) {
|
|
2345
|
+
const { chunkSize = 8192, ...renderOptions } = options;
|
|
2346
|
+
const renderer = new HTMLRenderer({ enableMonitoring: false, ...renderOptions, enableCache: false });
|
|
2347
|
+
const config = { ...renderer.config, seenObjects: /* @__PURE__ */ new WeakSet() };
|
|
2348
|
+
assertNotThenable(component, "root");
|
|
2349
|
+
if (config.validateInput && !renderer.isValidComponent(component)) {
|
|
2350
|
+
throw new Error("Invalid component structure");
|
|
2351
|
+
}
|
|
2352
|
+
const minifier = config.minify ? createStreamMinifier() : null;
|
|
2353
|
+
let buffer = "";
|
|
2354
|
+
for await (const piece of renderer.streamComponent(component, config, 0, null)) {
|
|
2355
|
+
buffer += minifier ? minifier.push(piece) : piece;
|
|
2356
|
+
if (buffer.length >= chunkSize) {
|
|
2357
|
+
yield buffer;
|
|
2358
|
+
buffer = "";
|
|
2359
|
+
await yieldToEventLoop();
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (minifier) buffer += minifier.end();
|
|
2363
|
+
if (buffer) yield buffer;
|
|
2364
|
+
}
|
|
2365
|
+
var yieldToEventLoop = typeof setImmediate === "function" ? () => new Promise((resolve) => setImmediate(resolve)) : () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
2366
|
+
function waitForDrain(response) {
|
|
2367
|
+
if (response.destroyed) return Promise.resolve(false);
|
|
2368
|
+
return new Promise((resolve) => {
|
|
2369
|
+
const settle = (drained) => () => {
|
|
2370
|
+
response.off("drain", onDrain);
|
|
2371
|
+
response.off("close", onClose);
|
|
2372
|
+
response.off("error", onClose);
|
|
2373
|
+
resolve(drained);
|
|
2374
|
+
};
|
|
2375
|
+
const onDrain = settle(true);
|
|
2376
|
+
const onClose = settle(false);
|
|
2377
|
+
response.on("drain", onDrain);
|
|
2378
|
+
response.on("close", onClose);
|
|
2379
|
+
response.on("error", onClose);
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
var streamingUtils = {
|
|
2383
|
+
/**
|
|
2384
|
+
* Collect all chunks into a single string
|
|
2385
|
+
*/
|
|
2386
|
+
async collectChunks(chunkGenerator) {
|
|
2387
|
+
let html = "";
|
|
2388
|
+
for await (const chunk of chunkGenerator) {
|
|
2389
|
+
html += chunk;
|
|
2390
|
+
}
|
|
2391
|
+
return html;
|
|
2392
|
+
},
|
|
2393
|
+
/**
|
|
2394
|
+
* Stream directly to a Node.js response, with backpressure.
|
|
2395
|
+
*
|
|
2396
|
+
* Resolves with the number of bytes written once the response has ended.
|
|
2397
|
+
* If the client disconnects first, rendering stops (the generator is
|
|
2398
|
+
* closed) and it resolves with the bytes written so far without ending
|
|
2399
|
+
* the response. If rendering fails, the response is destroyed and it
|
|
2400
|
+
* rejects with the rendering error.
|
|
2401
|
+
*/
|
|
2402
|
+
async streamToResponse(chunkGenerator, response) {
|
|
2403
|
+
let totalBytes = 0;
|
|
2404
|
+
if (!response.headersSent && !response.getHeader?.("Content-Type")) {
|
|
2405
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
2406
|
+
}
|
|
2407
|
+
try {
|
|
2408
|
+
for await (const chunk of chunkGenerator) {
|
|
2409
|
+
if (response.destroyed) return totalBytes;
|
|
2410
|
+
totalBytes += Buffer.byteLength(chunk);
|
|
2411
|
+
if (!response.write(chunk) && !await waitForDrain(response)) {
|
|
2412
|
+
return totalBytes;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
} catch (error) {
|
|
2416
|
+
response.destroy(error);
|
|
2417
|
+
throw error;
|
|
2418
|
+
}
|
|
2419
|
+
response.end();
|
|
2420
|
+
return totalBytes;
|
|
2421
|
+
},
|
|
2422
|
+
/**
|
|
2423
|
+
* Stream with progress callback
|
|
2424
|
+
*/
|
|
2425
|
+
async *streamWithProgress(chunkGenerator, onProgress) {
|
|
2426
|
+
let totalBytes = 0;
|
|
2427
|
+
let chunkCount = 0;
|
|
2428
|
+
for await (const chunk of chunkGenerator) {
|
|
2429
|
+
totalBytes += Buffer.byteLength(chunk);
|
|
2430
|
+
chunkCount++;
|
|
2431
|
+
if (onProgress) {
|
|
2432
|
+
onProgress({ chunkCount, totalBytes, chunk });
|
|
2433
|
+
}
|
|
2434
|
+
yield chunk;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2189
2438
|
|
|
2190
2439
|
// src/components/component-system.js
|
|
2191
2440
|
var COMPONENT_REGISTRY = /* @__PURE__ */ new Map();
|
|
@@ -2232,7 +2481,7 @@ var ComponentState = class {
|
|
|
2232
2481
|
try {
|
|
2233
2482
|
listener(newState, oldState);
|
|
2234
2483
|
} catch (_error) {
|
|
2235
|
-
console.error("State listener
|
|
2484
|
+
console.error("State listener error:", _error);
|
|
2236
2485
|
}
|
|
2237
2486
|
});
|
|
2238
2487
|
this.isUpdating = false;
|
|
@@ -2401,7 +2650,7 @@ var Component = class _Component {
|
|
|
2401
2650
|
return this.rendered;
|
|
2402
2651
|
} catch (_error) {
|
|
2403
2652
|
this.handleError(_error);
|
|
2404
|
-
return { div: { className: "component-
|
|
2653
|
+
return { div: { className: "component-error", text: `Error in ${this.name}` } };
|
|
2405
2654
|
}
|
|
2406
2655
|
}
|
|
2407
2656
|
/**
|
|
@@ -2647,7 +2896,7 @@ function lazy(factory, options = {}) {
|
|
|
2647
2896
|
if (onError) {
|
|
2648
2897
|
onError(_error);
|
|
2649
2898
|
} else {
|
|
2650
|
-
console.error("Lazy evaluation
|
|
2899
|
+
console.error("Lazy evaluation error:", _error);
|
|
2651
2900
|
}
|
|
2652
2901
|
return fallback;
|
|
2653
2902
|
} finally {
|
|
@@ -2750,27 +2999,32 @@ function evaluateWithTimeout(factory, timeout, args, fallback) {
|
|
|
2750
2999
|
}
|
|
2751
3000
|
}).catch(() => fallback);
|
|
2752
3001
|
}
|
|
3002
|
+
var functionIds = /* @__PURE__ */ new WeakMap();
|
|
3003
|
+
var nextFunctionId = 0;
|
|
3004
|
+
function functionId(fn) {
|
|
3005
|
+
let id = functionIds.get(fn);
|
|
3006
|
+
if (id === void 0) {
|
|
3007
|
+
id = ++nextFunctionId;
|
|
3008
|
+
functionIds.set(fn, id);
|
|
3009
|
+
}
|
|
3010
|
+
return id;
|
|
3011
|
+
}
|
|
3012
|
+
function serializeMemoKey(value) {
|
|
3013
|
+
return JSON.stringify(value, (_key, v) => typeof v === "function" ? `\0fn:${functionId(v)}` : v);
|
|
3014
|
+
}
|
|
2753
3015
|
function memo(fn, options = {}) {
|
|
2754
3016
|
const {
|
|
2755
3017
|
// Caching strategy
|
|
2756
3018
|
strategy = "lru",
|
|
2757
3019
|
// 'lru', 'ttl', 'weak', 'simple'
|
|
2758
3020
|
maxSize = 100,
|
|
2759
|
-
// Maximum cache entries
|
|
3021
|
+
// Maximum cache entries ('lru' and 'ttl')
|
|
2760
3022
|
ttl = null,
|
|
2761
3023
|
// Time to live in milliseconds
|
|
2762
3024
|
// Key generation
|
|
2763
3025
|
keyFn = null,
|
|
2764
3026
|
// Custom key function
|
|
2765
|
-
keySerializer =
|
|
2766
|
-
// Default serialization
|
|
2767
|
-
// Comparison
|
|
2768
|
-
// eslint-disable-next-line no-unused-vars
|
|
2769
|
-
compareFn = null,
|
|
2770
|
-
// Custom equality comparison
|
|
2771
|
-
// eslint-disable-next-line no-unused-vars
|
|
2772
|
-
shallow = false,
|
|
2773
|
-
// Shallow comparison for objects
|
|
3027
|
+
keySerializer = serializeMemoKey,
|
|
2774
3028
|
// Lifecycle hooks
|
|
2775
3029
|
onHit = null,
|
|
2776
3030
|
// Called on cache hit
|
|
@@ -2785,63 +3039,74 @@ function memo(fn, options = {}) {
|
|
|
2785
3039
|
debug = false
|
|
2786
3040
|
// Debug logging
|
|
2787
3041
|
} = options;
|
|
2788
|
-
|
|
2789
|
-
const
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
case "weak":
|
|
2798
|
-
cache = /* @__PURE__ */ new WeakMap();
|
|
2799
|
-
break;
|
|
2800
|
-
default:
|
|
2801
|
-
cache = /* @__PURE__ */ new Map();
|
|
2802
|
-
}
|
|
3042
|
+
const statsData = { hits: 0, misses: 0, evictions: 0 };
|
|
3043
|
+
const expiresIn = strategy === "ttl" && ttl === null ? 5e3 : ttl;
|
|
3044
|
+
const store = strategy === "simple" ? /* @__PURE__ */ new Map() : new LRUCache(maxSize, {
|
|
3045
|
+
onEvict: (key, entry) => {
|
|
3046
|
+
statsData.evictions++;
|
|
3047
|
+
if (onEvict) onEvict(key, entry.value);
|
|
3048
|
+
}
|
|
3049
|
+
});
|
|
3050
|
+
const weakStore = strategy === "weak" ? /* @__PURE__ */ new WeakMap() : null;
|
|
2803
3051
|
const generateKey = keyFn || ((...args) => {
|
|
2804
3052
|
if (args.length === 0) return "__empty__";
|
|
2805
3053
|
if (args.length === 1) return keySerializer(args[0]);
|
|
2806
3054
|
return keySerializer(args);
|
|
2807
3055
|
});
|
|
3056
|
+
const isFresh = (entry) => entry.expires === null || Date.now() < entry.expires;
|
|
3057
|
+
const lookup = (args) => {
|
|
3058
|
+
if (weakStore) {
|
|
3059
|
+
const [first] = args;
|
|
3060
|
+
const cacheable = first !== null && (typeof first === "object" || typeof first === "function");
|
|
3061
|
+
return { cacheable, key: first, entries: weakStore };
|
|
3062
|
+
}
|
|
3063
|
+
try {
|
|
3064
|
+
return { cacheable: true, key: generateKey(...args), entries: store };
|
|
3065
|
+
} catch {
|
|
3066
|
+
return { cacheable: false };
|
|
3067
|
+
}
|
|
3068
|
+
};
|
|
2808
3069
|
const memoizedFn = (...args) => {
|
|
2809
|
-
const key =
|
|
2810
|
-
if (
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
3070
|
+
const { cacheable, key, entries } = lookup(args);
|
|
3071
|
+
if (!cacheable) return fn(...args);
|
|
3072
|
+
const cached = entries.get(key);
|
|
3073
|
+
if (cached !== void 0) {
|
|
3074
|
+
if (isFresh(cached)) {
|
|
3075
|
+
if (debug) console.log(`Memo cache hit for key: ${String(key)}`);
|
|
2814
3076
|
if (onHit) onHit(key, cached.value, args);
|
|
2815
|
-
|
|
2816
|
-
return cached.value
|
|
2817
|
-
} else {
|
|
2818
|
-
cache.delete(key);
|
|
3077
|
+
statsData.hits++;
|
|
3078
|
+
return cached.value;
|
|
2819
3079
|
}
|
|
3080
|
+
entries.delete(key);
|
|
2820
3081
|
}
|
|
2821
|
-
if (debug) console.log(`Memo cache miss for key: ${key}`);
|
|
3082
|
+
if (debug) console.log(`Memo cache miss for key: ${String(key)}`);
|
|
2822
3083
|
if (onMiss) onMiss(key, args);
|
|
2823
|
-
|
|
2824
|
-
const
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
3084
|
+
statsData.misses++;
|
|
3085
|
+
const value = fn(...args);
|
|
3086
|
+
entries.set(key, { value, expires: expiresIn ? Date.now() + expiresIn : null });
|
|
3087
|
+
return value;
|
|
3088
|
+
};
|
|
3089
|
+
memoizedFn.cache = weakStore || (store instanceof LRUCache ? store.cache : store);
|
|
3090
|
+
memoizedFn.clear = () => {
|
|
3091
|
+
if (!weakStore) store.clear();
|
|
2828
3092
|
};
|
|
2829
|
-
memoizedFn.
|
|
2830
|
-
memoizedFn.
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
3093
|
+
memoizedFn.delete = (key) => (weakStore || store).delete(key);
|
|
3094
|
+
memoizedFn.has = (key) => {
|
|
3095
|
+
const entry = (weakStore || store).get(key);
|
|
3096
|
+
return entry !== void 0 && isFresh(entry);
|
|
3097
|
+
};
|
|
3098
|
+
memoizedFn.size = () => weakStore ? void 0 : store.size;
|
|
3099
|
+
if (stats) {
|
|
3100
|
+
memoizedFn.stats = () => ({ ...statsData });
|
|
2836
3101
|
memoizedFn.resetStats = () => {
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3102
|
+
statsData.hits = 0;
|
|
3103
|
+
statsData.misses = 0;
|
|
3104
|
+
statsData.evictions = 0;
|
|
2840
3105
|
};
|
|
2841
3106
|
}
|
|
2842
3107
|
memoizedFn.refresh = (...args) => {
|
|
2843
|
-
const key =
|
|
2844
|
-
|
|
3108
|
+
const { cacheable, key, entries } = lookup(args);
|
|
3109
|
+
if (cacheable) entries.delete(key);
|
|
2845
3110
|
return memoizedFn(...args);
|
|
2846
3111
|
};
|
|
2847
3112
|
return memoizedFn;
|
|
@@ -2905,66 +3170,6 @@ var LRUCache = class {
|
|
|
2905
3170
|
return this.cache.size;
|
|
2906
3171
|
}
|
|
2907
3172
|
};
|
|
2908
|
-
var TTLCache = class {
|
|
2909
|
-
constructor(ttl, options = {}) {
|
|
2910
|
-
this.ttl = ttl;
|
|
2911
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
2912
|
-
this.timers = /* @__PURE__ */ new Map();
|
|
2913
|
-
this.onEvict = options.onEvict;
|
|
2914
|
-
}
|
|
2915
|
-
get(key) {
|
|
2916
|
-
if (this.cache.has(key)) {
|
|
2917
|
-
const entry = this.cache.get(key);
|
|
2918
|
-
if (Date.now() < entry.expires) {
|
|
2919
|
-
return entry.value;
|
|
2920
|
-
} else {
|
|
2921
|
-
this.delete(key);
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
return void 0;
|
|
2925
|
-
}
|
|
2926
|
-
set(key, value) {
|
|
2927
|
-
if (this.timers.has(key)) {
|
|
2928
|
-
clearTimeout(this.timers.get(key));
|
|
2929
|
-
}
|
|
2930
|
-
const expires = Date.now() + this.ttl;
|
|
2931
|
-
this.cache.set(key, { value, expires });
|
|
2932
|
-
const timer = setTimeout(() => {
|
|
2933
|
-
this.delete(key);
|
|
2934
|
-
}, this.ttl);
|
|
2935
|
-
this.timers.set(key, timer);
|
|
2936
|
-
}
|
|
2937
|
-
has(key) {
|
|
2938
|
-
if (this.cache.has(key)) {
|
|
2939
|
-
const entry = this.cache.get(key);
|
|
2940
|
-
return Date.now() < entry.expires;
|
|
2941
|
-
}
|
|
2942
|
-
return false;
|
|
2943
|
-
}
|
|
2944
|
-
delete(key) {
|
|
2945
|
-
const had = this.cache.has(key);
|
|
2946
|
-
if (had) {
|
|
2947
|
-
const entry = this.cache.get(key);
|
|
2948
|
-
this.cache.delete(key);
|
|
2949
|
-
if (this.timers.has(key)) {
|
|
2950
|
-
clearTimeout(this.timers.get(key));
|
|
2951
|
-
this.timers.delete(key);
|
|
2952
|
-
}
|
|
2953
|
-
if (this.onEvict) {
|
|
2954
|
-
this.onEvict(key, entry.value);
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
return had;
|
|
2958
|
-
}
|
|
2959
|
-
clear() {
|
|
2960
|
-
this.timers.forEach((timer) => clearTimeout(timer));
|
|
2961
|
-
this.timers.clear();
|
|
2962
|
-
this.cache.clear();
|
|
2963
|
-
}
|
|
2964
|
-
get size() {
|
|
2965
|
-
return this.cache.size;
|
|
2966
|
-
}
|
|
2967
|
-
};
|
|
2968
3173
|
function shallowEqual(a, b) {
|
|
2969
3174
|
if (a === b) return true;
|
|
2970
3175
|
if (!a || !b) return false;
|
|
@@ -3158,7 +3363,9 @@ function createStateContainer(initialState, options) {
|
|
|
3158
3363
|
storageKey,
|
|
3159
3364
|
storage,
|
|
3160
3365
|
reducer,
|
|
3161
|
-
|
|
3366
|
+
// Defaulted here: withStateUtils.shared() passes {}, and spreading
|
|
3367
|
+
// an undefined middleware list threw "middleware is not iterable".
|
|
3368
|
+
middleware = [],
|
|
3162
3369
|
validator,
|
|
3163
3370
|
onStateChange,
|
|
3164
3371
|
debug
|
|
@@ -3213,7 +3420,7 @@ function createStateContainer(initialState, options) {
|
|
|
3213
3420
|
try {
|
|
3214
3421
|
listener(state, prevState);
|
|
3215
3422
|
} catch (_error) {
|
|
3216
|
-
if (debug) console.error("State listener
|
|
3423
|
+
if (debug) console.error("State listener error:", _error);
|
|
3217
3424
|
}
|
|
3218
3425
|
});
|
|
3219
3426
|
if (onStateChange) {
|
|
@@ -3357,8 +3564,10 @@ var withStateUtils = {
|
|
|
3357
3564
|
}
|
|
3358
3565
|
}),
|
|
3359
3566
|
/**
|
|
3360
|
-
* State with loading/
|
|
3567
|
+
* State with loading/error handling
|
|
3361
3568
|
*/
|
|
3569
|
+
// `_loading` / `_error` are the documented state keys (docs/components/state.md).
|
|
3570
|
+
/* eslint-disable no-restricted-syntax */
|
|
3362
3571
|
withLoading: async (initialState) => withState({
|
|
3363
3572
|
...initialState,
|
|
3364
3573
|
_loading: false,
|
|
@@ -3388,6 +3597,7 @@ var withStateUtils = {
|
|
|
3388
3597
|
}
|
|
3389
3598
|
}
|
|
3390
3599
|
}),
|
|
3600
|
+
/* eslint-enable no-restricted-syntax */
|
|
3391
3601
|
/**
|
|
3392
3602
|
* State with undo/redo functionality
|
|
3393
3603
|
*/
|
|
@@ -3488,7 +3698,7 @@ var LIFECYCLE_PHASES = {
|
|
|
3488
3698
|
UPDATED: "updated",
|
|
3489
3699
|
BEFORE_UNMOUNT: "beforeUnmount",
|
|
3490
3700
|
UNMOUNTED: "unmounted",
|
|
3491
|
-
ERROR: "
|
|
3701
|
+
ERROR: "error"
|
|
3492
3702
|
};
|
|
3493
3703
|
var componentInstances = /* @__PURE__ */ new WeakMap();
|
|
3494
3704
|
var componentRegistry = /* @__PURE__ */ new Map();
|
|
@@ -3808,7 +4018,7 @@ var ComponentEventSystem = class {
|
|
|
3808
4018
|
handler(event);
|
|
3809
4019
|
} catch (_error) {
|
|
3810
4020
|
globalErrorHandler.handle(_error, {
|
|
3811
|
-
type: "event-handler-
|
|
4021
|
+
type: "event-handler-error",
|
|
3812
4022
|
context: { event, handler: handler.toString() }
|
|
3813
4023
|
});
|
|
3814
4024
|
}
|
|
@@ -3827,7 +4037,7 @@ var ComponentEventSystem = class {
|
|
|
3827
4037
|
handler(event);
|
|
3828
4038
|
} catch (_error) {
|
|
3829
4039
|
globalErrorHandler.handle(_error, {
|
|
3830
|
-
type: "global-event-handler-
|
|
4040
|
+
type: "global-event-handler-error",
|
|
3831
4041
|
context: { event, handler: handler.toString() }
|
|
3832
4042
|
});
|
|
3833
4043
|
}
|
|
@@ -4150,6 +4360,7 @@ var ComponentCache = class {
|
|
|
4150
4360
|
this.cleanupTimer = setInterval(() => {
|
|
4151
4361
|
this.cleanup();
|
|
4152
4362
|
}, this.options.cleanupInterval);
|
|
4363
|
+
this.cleanupTimer.unref?.();
|
|
4153
4364
|
}
|
|
4154
4365
|
}
|
|
4155
4366
|
/**
|
|
@@ -4439,6 +4650,37 @@ function memoize(component, keyGenerator, options = {}) {
|
|
|
4439
4650
|
}
|
|
4440
4651
|
|
|
4441
4652
|
// src/components/error-boundary.js
|
|
4653
|
+
function isBrowser() {
|
|
4654
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
4655
|
+
}
|
|
4656
|
+
function resolveNestedComponents(node, depth = 0) {
|
|
4657
|
+
if (depth > 1e3) return node;
|
|
4658
|
+
if (typeof node === "function") {
|
|
4659
|
+
return resolveNestedComponents(node(), depth + 1);
|
|
4660
|
+
}
|
|
4661
|
+
if (Array.isArray(node)) {
|
|
4662
|
+
let changed = false;
|
|
4663
|
+
const resolved = node.map((child) => {
|
|
4664
|
+
const result = resolveNestedComponents(child, depth + 1);
|
|
4665
|
+
if (result !== child) changed = true;
|
|
4666
|
+
return result;
|
|
4667
|
+
});
|
|
4668
|
+
return changed ? resolved : node;
|
|
4669
|
+
}
|
|
4670
|
+
if (node && typeof node === "object" && !isTrustedContent(node)) {
|
|
4671
|
+
const keys = Object.keys(node);
|
|
4672
|
+
if (keys.length !== 1) return node;
|
|
4673
|
+
const tag = keys[0];
|
|
4674
|
+
const props = node[tag];
|
|
4675
|
+
if (props && typeof props === "object" && !Array.isArray(props) && props.children !== void 0) {
|
|
4676
|
+
const children = resolveNestedComponents(props.children, depth + 1);
|
|
4677
|
+
if (children !== props.children) {
|
|
4678
|
+
return { [tag]: { ...props, children } };
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
return node;
|
|
4683
|
+
}
|
|
4442
4684
|
var ErrorBoundaryState = class {
|
|
4443
4685
|
constructor() {
|
|
4444
4686
|
this.hasError = false;
|
|
@@ -4470,19 +4712,23 @@ function createErrorBoundary(options = {}) {
|
|
|
4470
4712
|
maxErrors = Infinity,
|
|
4471
4713
|
resetTimeout = null
|
|
4472
4714
|
} = options;
|
|
4473
|
-
const
|
|
4715
|
+
const sharedState = new ErrorBoundaryState();
|
|
4474
4716
|
let previousProps = {};
|
|
4475
4717
|
let resetTimer = null;
|
|
4476
4718
|
return function errorBoundaryWrapper(component) {
|
|
4477
4719
|
return function wrappedComponent(props = {}) {
|
|
4720
|
+
const persistent = isBrowser();
|
|
4721
|
+
const state = persistent ? sharedState : new ErrorBoundaryState();
|
|
4478
4722
|
try {
|
|
4479
|
-
if (resetOnPropsChange && shouldReset(props, previousProps, resetKeys)) {
|
|
4723
|
+
if (persistent && resetOnPropsChange && shouldReset(props, previousProps, resetKeys)) {
|
|
4480
4724
|
state.reset();
|
|
4481
4725
|
if (onReset) {
|
|
4482
4726
|
onReset();
|
|
4483
4727
|
}
|
|
4484
4728
|
}
|
|
4485
|
-
|
|
4729
|
+
if (persistent) {
|
|
4730
|
+
previousProps = { ...props };
|
|
4731
|
+
}
|
|
4486
4732
|
if (state.hasError) {
|
|
4487
4733
|
if (state.errorCount >= maxErrors) {
|
|
4488
4734
|
return typeof fallback === "function" ? fallback(state.error, state.errorInfo, { permanent: true }) : fallback;
|
|
@@ -4499,7 +4745,7 @@ function createErrorBoundary(options = {}) {
|
|
|
4499
4745
|
return fallbackComponent;
|
|
4500
4746
|
}
|
|
4501
4747
|
const result = typeof component === "function" ? component(props) : component;
|
|
4502
|
-
return result;
|
|
4748
|
+
return resolveNestedComponents(result);
|
|
4503
4749
|
} catch (error) {
|
|
4504
4750
|
const errorInfo = {
|
|
4505
4751
|
componentStack: error.stack,
|
|
@@ -4514,7 +4760,7 @@ function createErrorBoundary(options = {}) {
|
|
|
4514
4760
|
console.error("Error in onError callback:", callbackError);
|
|
4515
4761
|
}
|
|
4516
4762
|
}
|
|
4517
|
-
if (resetTimeout && !resetTimer) {
|
|
4763
|
+
if (persistent && resetTimeout && !resetTimer) {
|
|
4518
4764
|
resetTimer = setTimeout(() => {
|
|
4519
4765
|
state.reset();
|
|
4520
4766
|
resetTimer = null;
|
|
@@ -4630,9 +4876,10 @@ function createAsyncErrorBoundary(options = {}) {
|
|
|
4630
4876
|
} = options;
|
|
4631
4877
|
return function asyncBoundaryWrapper(asyncComponent) {
|
|
4632
4878
|
return async function wrappedAsyncComponent(props = {}) {
|
|
4879
|
+
let timer;
|
|
4633
4880
|
try {
|
|
4634
4881
|
const timeoutPromise = new Promise((_, reject) => {
|
|
4635
|
-
setTimeout(() => reject(new Error("Component load timeout")), timeout);
|
|
4882
|
+
timer = setTimeout(() => reject(new Error("Component load timeout")), timeout);
|
|
4636
4883
|
});
|
|
4637
4884
|
const result = await Promise.race([
|
|
4638
4885
|
typeof asyncComponent === "function" ? asyncComponent(props) : asyncComponent,
|
|
@@ -4644,6 +4891,8 @@ function createAsyncErrorBoundary(options = {}) {
|
|
|
4644
4891
|
onError(error, { props, async: true });
|
|
4645
4892
|
}
|
|
4646
4893
|
return typeof errorFallback === "function" ? errorFallback(error, { props }) : errorFallback;
|
|
4894
|
+
} finally {
|
|
4895
|
+
clearTimeout(timer);
|
|
4647
4896
|
}
|
|
4648
4897
|
};
|
|
4649
4898
|
};
|
|
@@ -4738,7 +4987,7 @@ function renderWithTemplate(component, options = {}) {
|
|
|
4738
4987
|
template = "<!DOCTYPE html>\n{{content}}"
|
|
4739
4988
|
} = options;
|
|
4740
4989
|
const html = renderWithMonitoring(component, options);
|
|
4741
|
-
return template.replace("{{content}}", html);
|
|
4990
|
+
return template.replace("{{content}}", () => html);
|
|
4742
4991
|
}
|
|
4743
4992
|
async function renderComponentFactory(componentFactory, factoryArgs, options = {}) {
|
|
4744
4993
|
const component = await Promise.resolve(
|
|
@@ -5276,6 +5525,29 @@ var EventBus = class {
|
|
|
5276
5525
|
}
|
|
5277
5526
|
return false;
|
|
5278
5527
|
}
|
|
5528
|
+
/**
|
|
5529
|
+
* A view of this bus whose event and action names are prefixed with
|
|
5530
|
+
* `${scope}:`. It shares listeners with the bus, so `scope:event` can
|
|
5531
|
+
* still be observed from the unscoped bus. withEventBus({ scope }) and
|
|
5532
|
+
* emitEvent(name, { scope }) called this, but it didn't exist, so any
|
|
5533
|
+
* scoped usage threw "createScope is not a function".
|
|
5534
|
+
*
|
|
5535
|
+
* @param {string} scope - Scope name
|
|
5536
|
+
* @returns {Object} Scoped event bus
|
|
5537
|
+
*/
|
|
5538
|
+
createScope(scope) {
|
|
5539
|
+
const prefix = `${scope}:`;
|
|
5540
|
+
return {
|
|
5541
|
+
emit: (event, data) => this.emit(prefix + event, data),
|
|
5542
|
+
emitSync: (event, data) => this.emitSync(prefix + event, data),
|
|
5543
|
+
on: (event, listener, options) => this.on(prefix + event, listener, options),
|
|
5544
|
+
once: (event, listener, options) => this.once(prefix + event, listener, options),
|
|
5545
|
+
off: (event, listenerId) => this.off(prefix + event, listenerId),
|
|
5546
|
+
registerAction: (action, handler) => this.registerAction(prefix + action, handler),
|
|
5547
|
+
handleAction: (action, element, event, data) => this.handleAction(prefix + action, element, event, data),
|
|
5548
|
+
createScope: (child) => this.createScope(prefix + child)
|
|
5549
|
+
};
|
|
5550
|
+
}
|
|
5279
5551
|
/**
|
|
5280
5552
|
* Remove all listeners for an event
|
|
5281
5553
|
*/
|
|
@@ -5889,7 +6161,8 @@ function withEventBus(options = {}) {
|
|
|
5889
6161
|
function EventBusComponent(props = {}, state = {}, context = {}) {
|
|
5890
6162
|
const bus = scope ? eventBus.createScope(scope) : eventBus;
|
|
5891
6163
|
const listenerIds = /* @__PURE__ */ new Map();
|
|
5892
|
-
|
|
6164
|
+
const interactive = typeof window !== "undefined" && typeof document !== "undefined";
|
|
6165
|
+
if (interactive) Object.entries(events).forEach(([event, handler]) => {
|
|
5893
6166
|
const listenerId = bus.on(event, (data, eventName) => {
|
|
5894
6167
|
if (typeof handler === "function") {
|
|
5895
6168
|
handler.call(this, data, eventName, { props, state, context });
|
|
@@ -5897,7 +6170,7 @@ function withEventBus(options = {}) {
|
|
|
5897
6170
|
});
|
|
5898
6171
|
listenerIds.set(event, listenerId);
|
|
5899
6172
|
});
|
|
5900
|
-
Object.entries(actions).forEach(([action, handler]) => {
|
|
6173
|
+
if (interactive) Object.entries(actions).forEach(([action, handler]) => {
|
|
5901
6174
|
bus.registerAction(action, (actionContext) => {
|
|
5902
6175
|
if (typeof handler === "function") {
|
|
5903
6176
|
handler.call(this, actionContext, { props, state, context });
|
|
@@ -5941,7 +6214,10 @@ function withEventBus(options = {}) {
|
|
|
5941
6214
|
originalUnmount.call(this);
|
|
5942
6215
|
};
|
|
5943
6216
|
} else {
|
|
5944
|
-
result
|
|
6217
|
+
Object.defineProperty(result, "__eventBusCleanup", {
|
|
6218
|
+
value: eventUtils.cleanup,
|
|
6219
|
+
configurable: true
|
|
6220
|
+
});
|
|
5945
6221
|
}
|
|
5946
6222
|
}
|
|
5947
6223
|
return result;
|
|
@@ -6269,6 +6545,7 @@ var eventSystem2 = {
|
|
|
6269
6545
|
registerAction: globalEventBus.registerAction.bind(globalEventBus),
|
|
6270
6546
|
registerActions: globalEventBus.registerActions.bind(globalEventBus),
|
|
6271
6547
|
handleAction: globalEventBus.handleAction.bind(globalEventBus),
|
|
6548
|
+
createScope: globalEventBus.createScope.bind(globalEventBus),
|
|
6272
6549
|
// Statistics and debugging
|
|
6273
6550
|
getStats: globalEventBus.getStats.bind(globalEventBus),
|
|
6274
6551
|
resetStats: globalEventBus.resetStats.bind(globalEventBus),
|
|
@@ -6372,23 +6649,74 @@ var compose = {
|
|
|
6372
6649
|
};
|
|
6373
6650
|
|
|
6374
6651
|
// src/index.js
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6652
|
+
function generateScopeId(cssText) {
|
|
6653
|
+
let hash = 2166136261;
|
|
6654
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
6655
|
+
hash ^= cssText.charCodeAt(i);
|
|
6656
|
+
hash = Math.imul(hash, 16777619);
|
|
6657
|
+
}
|
|
6658
|
+
return `coh-${(hash >>> 0).toString(36)}`;
|
|
6659
|
+
}
|
|
6660
|
+
var GROUPING_AT_RULES = /* @__PURE__ */ new Set(["media", "supports", "container", "layer", "document", "scope"]);
|
|
6661
|
+
function splitSelectorList(prelude) {
|
|
6662
|
+
const parts = [];
|
|
6663
|
+
let depth = 0;
|
|
6664
|
+
let start = 0;
|
|
6665
|
+
for (let i = 0; i < prelude.length; i++) {
|
|
6666
|
+
const ch = prelude[i];
|
|
6667
|
+
if (ch === "(" || ch === "[") depth++;
|
|
6668
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
6669
|
+
else if (ch === "," && depth === 0) {
|
|
6670
|
+
parts.push(prelude.slice(start, i));
|
|
6671
|
+
start = i + 1;
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
parts.push(prelude.slice(start));
|
|
6675
|
+
return parts;
|
|
6676
|
+
}
|
|
6677
|
+
function scopeSelector(selector, scopeId) {
|
|
6678
|
+
const trimmed = selector.trim();
|
|
6679
|
+
if (!trimmed) return selector;
|
|
6680
|
+
if (trimmed.includes(":")) {
|
|
6681
|
+
return trimmed.replace(/([^:]+)(:.*)?/, `$1[${scopeId}]$2`);
|
|
6682
|
+
}
|
|
6683
|
+
return `${trimmed}[${scopeId}]`;
|
|
6378
6684
|
}
|
|
6379
6685
|
function scopeCSS(css, scopeId) {
|
|
6380
6686
|
if (!css || typeof css !== "string") return css;
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6687
|
+
let result = "";
|
|
6688
|
+
let i = 0;
|
|
6689
|
+
while (i < css.length) {
|
|
6690
|
+
const open = css.indexOf("{", i);
|
|
6691
|
+
if (open === -1) {
|
|
6692
|
+
result += css.slice(i);
|
|
6693
|
+
break;
|
|
6694
|
+
}
|
|
6695
|
+
let depth = 1;
|
|
6696
|
+
let j = open + 1;
|
|
6697
|
+
while (j < css.length && depth > 0) {
|
|
6698
|
+
if (css[j] === "{") depth++;
|
|
6699
|
+
else if (css[j] === "}") depth--;
|
|
6700
|
+
j++;
|
|
6701
|
+
}
|
|
6702
|
+
const body = css.slice(open + 1, depth === 0 ? j - 1 : j);
|
|
6703
|
+
const rawPrelude = css.slice(i, open);
|
|
6704
|
+
const semicolon = rawPrelude.lastIndexOf(";");
|
|
6705
|
+
const lead = semicolon === -1 ? "" : rawPrelude.slice(0, semicolon + 1);
|
|
6706
|
+
const prelude = semicolon === -1 ? rawPrelude : rawPrelude.slice(semicolon + 1);
|
|
6707
|
+
const trimmed = prelude.trim();
|
|
6708
|
+
if (trimmed.startsWith("@")) {
|
|
6709
|
+
const name = trimmed.slice(1).split(/[\s({]/)[0].toLowerCase();
|
|
6710
|
+
const inner = GROUPING_AT_RULES.has(name) ? scopeCSS(body, scopeId) : body;
|
|
6711
|
+
result += `${lead}${prelude}{${inner}}`;
|
|
6712
|
+
} else {
|
|
6713
|
+
const leading = prelude.match(/^\s*/)[0];
|
|
6714
|
+
const scoped = splitSelectorList(prelude).map((part) => scopeSelector(part, scopeId)).join(", ");
|
|
6715
|
+
result += `${lead}${leading}${scoped} {${body}}`;
|
|
6716
|
+
}
|
|
6717
|
+
i = j;
|
|
6718
|
+
}
|
|
6719
|
+
return result;
|
|
6392
6720
|
}
|
|
6393
6721
|
function applyScopeToElement(element, scopeId) {
|
|
6394
6722
|
if (typeof element === "string" || typeof element === "number" || !element) {
|
|
@@ -6416,10 +6744,7 @@ function applyScopeToElement(element, scopeId) {
|
|
|
6416
6744
|
return element;
|
|
6417
6745
|
}
|
|
6418
6746
|
function dangerouslySetInnerContent(content) {
|
|
6419
|
-
return
|
|
6420
|
-
__html: content,
|
|
6421
|
-
__trusted: true
|
|
6422
|
-
};
|
|
6747
|
+
return createTrustedContent(content);
|
|
6423
6748
|
}
|
|
6424
6749
|
function injectHydrationAttributes(component, options) {
|
|
6425
6750
|
if (!component || typeof component !== "object" || Array.isArray(component)) {
|
|
@@ -6451,20 +6776,42 @@ function Island(componentFn) {
|
|
|
6451
6776
|
});
|
|
6452
6777
|
};
|
|
6453
6778
|
}
|
|
6454
|
-
function
|
|
6779
|
+
function prepareRender(obj, options) {
|
|
6455
6780
|
const scoped = options.scoped ?? options.encapsulate ?? false;
|
|
6456
6781
|
const { scoped: _scoped, encapsulate: _encapsulate, hydratable: _hydratable, island: _island, ...rendererOptions } = options;
|
|
6457
|
-
let component =
|
|
6458
|
-
if (
|
|
6459
|
-
component = component
|
|
6782
|
+
let component = typeof obj === "function" ? obj(options) : obj;
|
|
6783
|
+
if (scoped) {
|
|
6784
|
+
component = renderScopedComponent(component);
|
|
6460
6785
|
}
|
|
6461
6786
|
if (_hydratable || _island) {
|
|
6462
6787
|
component = injectHydrationAttributes(component, { hydratable: _hydratable, island: _island });
|
|
6463
6788
|
}
|
|
6789
|
+
return { component, rendererOptions };
|
|
6790
|
+
}
|
|
6791
|
+
function render2(obj, options = {}) {
|
|
6792
|
+
const { component, rendererOptions } = prepareRender(obj, options);
|
|
6464
6793
|
return render(component, rendererOptions);
|
|
6465
6794
|
}
|
|
6795
|
+
function renderToStream2(obj, options = {}) {
|
|
6796
|
+
const { component, rendererOptions } = prepareRender(obj, options);
|
|
6797
|
+
return renderToStream(component, rendererOptions);
|
|
6798
|
+
}
|
|
6799
|
+
function collectStyleText(element, out = []) {
|
|
6800
|
+
if (Array.isArray(element)) {
|
|
6801
|
+
element.forEach((item) => collectStyleText(item, out));
|
|
6802
|
+
} else if (element && typeof element === "object") {
|
|
6803
|
+
for (const [tagName, props] of Object.entries(element)) {
|
|
6804
|
+
if (tagName === "style" && props && typeof props === "object" && typeof props.text === "string") {
|
|
6805
|
+
out.push(props.text);
|
|
6806
|
+
} else if (props && typeof props === "object" && props.children) {
|
|
6807
|
+
collectStyleText(props.children, out);
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6810
|
+
}
|
|
6811
|
+
return out;
|
|
6812
|
+
}
|
|
6466
6813
|
function renderScopedComponent(component) {
|
|
6467
|
-
const scopeId = generateScopeId();
|
|
6814
|
+
const scopeId = generateScopeId(collectStyleText(component).join("\n"));
|
|
6468
6815
|
function processScopedElement(element) {
|
|
6469
6816
|
if (!element || typeof element !== "object") {
|
|
6470
6817
|
return element;
|
|
@@ -6495,21 +6842,13 @@ function renderScopedComponent(component) {
|
|
|
6495
6842
|
const scopedComponent = applyScopeToElement(processedComponent, scopeId);
|
|
6496
6843
|
return scopedComponent;
|
|
6497
6844
|
}
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
const
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
const result = component(props);
|
|
6506
|
-
memoCache.set(key, result);
|
|
6507
|
-
if (memoCache.size > 100) {
|
|
6508
|
-
const firstKey = memoCache.keys().next().value;
|
|
6509
|
-
memoCache.delete(firstKey);
|
|
6510
|
-
}
|
|
6511
|
-
return result;
|
|
6512
|
-
};
|
|
6845
|
+
function memo2(component, options = {}) {
|
|
6846
|
+
const withDefaultProps = (props = {}, ...rest) => component(props, ...rest);
|
|
6847
|
+
if (typeof options === "function") {
|
|
6848
|
+
const keyGenerator = options;
|
|
6849
|
+
return memo(withDefaultProps, { keyFn: (props = {}, ...rest) => keyGenerator(props, ...rest) });
|
|
6850
|
+
}
|
|
6851
|
+
return memo(withDefaultProps, options);
|
|
6513
6852
|
}
|
|
6514
6853
|
function validateComponent2(obj) {
|
|
6515
6854
|
if (!obj || typeof obj !== "object") {
|
|
@@ -6530,7 +6869,7 @@ function deepClone2(obj) {
|
|
|
6530
6869
|
}
|
|
6531
6870
|
return cloned;
|
|
6532
6871
|
}
|
|
6533
|
-
var VERSION = true ? "
|
|
6872
|
+
var VERSION = true ? "2.0.0-rc.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
6534
6873
|
var fp = {
|
|
6535
6874
|
/**
|
|
6536
6875
|
* Curried map: fp.map(fn)(array)
|
|
@@ -6542,6 +6881,7 @@ var fp = {
|
|
|
6542
6881
|
var coherent = {
|
|
6543
6882
|
// Core rendering
|
|
6544
6883
|
render: render2,
|
|
6884
|
+
renderToStream: renderToStream2,
|
|
6545
6885
|
// Shadow DOM (client-side only)
|
|
6546
6886
|
shadowDOM: shadow_dom_exports,
|
|
6547
6887
|
// Component system
|
|
@@ -6659,6 +6999,7 @@ export {
|
|
|
6659
6999
|
isLazy,
|
|
6660
7000
|
isPeerDependencyAvailable,
|
|
6661
7001
|
isTrustedContent,
|
|
7002
|
+
isValidAttributeName,
|
|
6662
7003
|
isVoidElement,
|
|
6663
7004
|
lazy,
|
|
6664
7005
|
componentUtils as lifecycleUtils,
|
|
@@ -6674,9 +7015,11 @@ export {
|
|
|
6674
7015
|
registerComponent,
|
|
6675
7016
|
render2 as render,
|
|
6676
7017
|
renderComponentFactory,
|
|
7018
|
+
renderToStream2 as renderToStream,
|
|
6677
7019
|
renderWithMonitoring,
|
|
6678
7020
|
renderWithTemplate,
|
|
6679
7021
|
shadow_dom_exports as shadowDOM,
|
|
7022
|
+
streamingUtils,
|
|
6680
7023
|
useHooks,
|
|
6681
7024
|
validateComponent2 as validateComponent,
|
|
6682
7025
|
validateNesting,
|