what-core 0.6.1 → 0.6.3

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.
Files changed (50) hide show
  1. package/README.md +2 -0
  2. package/compiler.d.ts +30 -0
  3. package/devtools.d.ts +2 -0
  4. package/dist/compiler.js +1787 -0
  5. package/dist/compiler.js.map +7 -0
  6. package/dist/compiler.min.js +2 -0
  7. package/dist/compiler.min.js.map +7 -0
  8. package/dist/devtools.js +10 -0
  9. package/dist/devtools.js.map +7 -0
  10. package/dist/devtools.min.js +2 -0
  11. package/dist/devtools.min.js.map +7 -0
  12. package/dist/index.js +331 -382
  13. package/dist/index.js.map +4 -4
  14. package/dist/index.min.js +62 -62
  15. package/dist/index.min.js.map +4 -4
  16. package/dist/render.js +263 -21
  17. package/dist/render.js.map +4 -4
  18. package/dist/render.min.js +58 -1
  19. package/dist/render.min.js.map +4 -4
  20. package/dist/testing.js +3 -0
  21. package/dist/testing.js.map +2 -2
  22. package/dist/testing.min.js +1 -1
  23. package/dist/testing.min.js.map +2 -2
  24. package/index.d.ts +176 -1
  25. package/jsx-runtime.d.ts +622 -0
  26. package/package.json +20 -2
  27. package/src/agent-context.js +1 -1
  28. package/src/compiler.js +18 -0
  29. package/src/components.js +73 -27
  30. package/src/devtools.js +4 -0
  31. package/src/dom.js +7 -0
  32. package/src/guardrails.js +3 -4
  33. package/src/hooks.js +0 -11
  34. package/src/index.js +5 -9
  35. package/src/render.js +94 -24
  36. package/dist/a11y.js +0 -440
  37. package/dist/animation.js +0 -548
  38. package/dist/components.js +0 -229
  39. package/dist/data.js +0 -638
  40. package/dist/dom.js +0 -439
  41. package/dist/form.js +0 -509
  42. package/dist/h.js +0 -152
  43. package/dist/head.js +0 -51
  44. package/dist/helpers.js +0 -140
  45. package/dist/hooks.js +0 -210
  46. package/dist/reactive.js +0 -432
  47. package/dist/scheduler.js +0 -246
  48. package/dist/skeleton.js +0 -363
  49. package/dist/store.js +0 -83
  50. package/dist/what.js +0 -117
package/dist/render.js CHANGED
@@ -600,6 +600,9 @@ function createDOM(vnode, parent, isSvg) {
600
600
  if (isVNode(vnode) && typeof vnode.tag === "function") {
601
601
  return createComponent(vnode, parent, isSvg);
602
602
  }
603
+ if (isVNode(vnode) && (vnode.tag === "__errorBoundary" || vnode.tag === "__suspense" || vnode.tag === "__portal")) {
604
+ return createComponent(vnode, parent, isSvg);
605
+ }
603
606
  if (isVNode(vnode)) {
604
607
  return createElementFromVNode(vnode, parent, isSvg);
605
608
  }
@@ -990,6 +993,185 @@ function setProp(el, key, value, isSvg) {
990
993
  }
991
994
  }
992
995
 
996
+ // packages/core/src/errors.js
997
+ var ERROR_CODES = {
998
+ INFINITE_EFFECT: {
999
+ code: "ERR_INFINITE_EFFECT",
1000
+ severity: "error",
1001
+ template: 'Effect "{{effectName}}" exceeded 25 flush iterations \u2014 likely an infinite loop.',
1002
+ suggestion: "An effect is writing to a signal it also reads, creating a cycle. Use untrack() to read the signal without subscribing, or restructure so the write and read are in separate effects.",
1003
+ codeExample: `// Bad \u2014 reads and writes count, creating a cycle:
1004
+ effect(() => { count(count() + 1); });
1005
+
1006
+ // Good \u2014 use untrack() so the read doesn't subscribe:
1007
+ effect(() => { count(untrack(count) + 1); });
1008
+
1009
+ // Better \u2014 split into separate logic:
1010
+ const doubled = computed(() => count() * 2);`
1011
+ },
1012
+ MISSING_SIGNAL_READ: {
1013
+ code: "ERR_MISSING_SIGNAL_READ",
1014
+ severity: "warning",
1015
+ template: 'Signal "{{signalName}}" used without calling () \u2014 renders as "[Function]" instead of its value.',
1016
+ suggestion: "Signals are functions. Call them to read: count() not count. In JSX: {count()} not {count}.",
1017
+ codeExample: `// Bad \u2014 signal reference, not value:
1018
+ <span>{count}</span> // renders "[Function]"
1019
+
1020
+ // Good \u2014 call the signal:
1021
+ <span>{count()}</span> // renders the actual value`
1022
+ },
1023
+ HYDRATION_MISMATCH: {
1024
+ code: "ERR_HYDRATION_MISMATCH",
1025
+ severity: "error",
1026
+ template: 'Hydration mismatch in component "{{component}}": server rendered "{{serverHTML}}" but client expects "{{clientHTML}}".',
1027
+ suggestion: "Ensure server and client render identical initial HTML. Avoid reading browser-only APIs (window, localStorage) during the initial render. Use onMount() for client-only logic.",
1028
+ codeExample: `// Bad \u2014 different on server vs client:
1029
+ function App() {
1030
+ return <p>{window.innerWidth}</p>;
1031
+ }
1032
+
1033
+ // Good \u2014 use onMount for client-only values:
1034
+ function App() {
1035
+ const width = signal(0);
1036
+ onMount(() => width(window.innerWidth));
1037
+ return <p>{width()}</p>;
1038
+ }`
1039
+ },
1040
+ ORPHAN_EFFECT: {
1041
+ code: "ERR_ORPHAN_EFFECT",
1042
+ severity: "warning",
1043
+ template: 'Effect "{{effectName}}" was created outside a reactive root \u2014 it will never be cleaned up.',
1044
+ suggestion: "Wrap effect creation in createRoot() or create effects inside component functions where they are automatically tracked.",
1045
+ codeExample: `// Bad \u2014 orphaned, leaks memory:
1046
+ effect(() => console.log(count()));
1047
+
1048
+ // Good \u2014 inside a root with cleanup:
1049
+ createRoot(dispose => {
1050
+ effect(() => console.log(count()));
1051
+ // later: dispose() cleans up
1052
+ });`
1053
+ },
1054
+ SIGNAL_WRITE_IN_RENDER: {
1055
+ code: "ERR_SIGNAL_WRITE_IN_RENDER",
1056
+ severity: "error",
1057
+ template: 'Signal "{{signalName}}" written during render of component "{{component}}". This triggers re-execution.',
1058
+ suggestion: "Move signal writes into event handlers, effects, or onMount(). The component body should only read signals, not write them.",
1059
+ codeExample: `// Bad \u2014 write during render:
1060
+ function Counter() {
1061
+ count(count() + 1); // triggers infinite loop
1062
+ return <span>{count()}</span>;
1063
+ }
1064
+
1065
+ // Good \u2014 write in event handler:
1066
+ function Counter() {
1067
+ return <button onclick={() => count(c => c + 1)}>{count()}</button>;
1068
+ }`
1069
+ },
1070
+ MISSING_CLEANUP: {
1071
+ code: "ERR_MISSING_CLEANUP",
1072
+ severity: "warning",
1073
+ template: 'Effect sets up "{{resource}}" but does not return a cleanup function.',
1074
+ suggestion: "Effects that add event listeners, set timers, or open connections should return a cleanup function to prevent memory leaks.",
1075
+ codeExample: `// Bad \u2014 no cleanup:
1076
+ effect(() => {
1077
+ window.addEventListener('resize', handler);
1078
+ });
1079
+
1080
+ // Good \u2014 return cleanup:
1081
+ effect(() => {
1082
+ window.addEventListener('resize', handler);
1083
+ return () => window.removeEventListener('resize', handler);
1084
+ });`
1085
+ },
1086
+ UNSAFE_INNERHTML: {
1087
+ code: "ERR_UNSAFE_INNERHTML",
1088
+ severity: "warning",
1089
+ template: "innerHTML set on element without using the __html safety marker.",
1090
+ suggestion: "Use the html tagged template literal or pass { __html: content } to mark innerHTML as intentional and reviewed.",
1091
+ codeExample: `// Bad \u2014 raw innerHTML (XSS risk):
1092
+ <div innerHTML={userInput} />
1093
+
1094
+ // Good \u2014 explicit opt-in:
1095
+ <div innerHTML={{ __html: sanitizedContent }} />
1096
+
1097
+ // Better \u2014 use the html template literal:
1098
+ html\`<div>\${sanitizedContent}</div>\``
1099
+ },
1100
+ MISSING_KEY: {
1101
+ code: "ERR_MISSING_KEY",
1102
+ severity: "warning",
1103
+ template: 'List rendered without key prop in component "{{component}}". Items may re-order incorrectly.',
1104
+ suggestion: "Add a unique key prop to each item in a list. Use a stable identifier (like an ID), not the array index.",
1105
+ codeExample: `// Bad \u2014 no key:
1106
+ <For each={items()}>{item => <li>{item.name}</li>}</For>
1107
+
1108
+ // Good \u2014 stable key:
1109
+ <For each={items()}>{item => <li key={item.id}>{item.name}</li>}</For>`
1110
+ }
1111
+ };
1112
+ var WhatError = class extends Error {
1113
+ constructor({ code, message, suggestion, file, line, component, signal: signal2, effect: effect2 }) {
1114
+ super(message);
1115
+ this.name = "WhatError";
1116
+ this.code = code;
1117
+ this.suggestion = suggestion;
1118
+ this.file = file;
1119
+ this.line = line;
1120
+ this.component = component;
1121
+ this.signal = signal2;
1122
+ this.effect = effect2;
1123
+ }
1124
+ toJSON() {
1125
+ return {
1126
+ code: this.code,
1127
+ message: this.message,
1128
+ suggestion: this.suggestion,
1129
+ file: this.file,
1130
+ line: this.line,
1131
+ component: this.component,
1132
+ signal: this.signal,
1133
+ effect: this.effect
1134
+ };
1135
+ }
1136
+ };
1137
+ function createWhatError(errorCode, context = {}) {
1138
+ const def = typeof errorCode === "string" ? ERROR_CODES[errorCode] : errorCode;
1139
+ if (!def) {
1140
+ return new WhatError({
1141
+ code: "ERR_UNKNOWN",
1142
+ message: `Unknown error: ${errorCode}`,
1143
+ suggestion: "Check the error code and try again."
1144
+ });
1145
+ }
1146
+ let message = def.template;
1147
+ for (const [key, val] of Object.entries(context)) {
1148
+ message = message.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(val));
1149
+ }
1150
+ message = message.replace(/\{\{[^}]+\}\}/g, "(unknown)");
1151
+ return new WhatError({
1152
+ code: def.code,
1153
+ message,
1154
+ suggestion: def.suggestion,
1155
+ file: context.file,
1156
+ line: context.line,
1157
+ component: context.component,
1158
+ signal: context.signal || context.signalName,
1159
+ effect: context.effect || context.effectName
1160
+ });
1161
+ }
1162
+ var collectedErrors = [];
1163
+ var MAX_COLLECTED = 200;
1164
+ function collectError(whatError) {
1165
+ if (!__DEV__) return;
1166
+ collectedErrors.push({
1167
+ ...whatError.toJSON(),
1168
+ timestamp: Date.now()
1169
+ });
1170
+ if (collectedErrors.length > MAX_COLLECTED) {
1171
+ collectedErrors = collectedErrors.slice(-MAX_COLLECTED);
1172
+ }
1173
+ }
1174
+
993
1175
  // packages/core/src/render.js
994
1176
  function _$createComponent(Component, props, children) {
995
1177
  if (children && children.length > 0) {
@@ -1696,6 +1878,7 @@ function setProp2(el, key, value) {
1696
1878
  else if (value && typeof value === "object") value.current = el;
1697
1879
  return;
1698
1880
  }
1881
+ if (key === "key") return;
1699
1882
  if (URL_ATTRS.has(key) || URL_ATTRS.has(key.toLowerCase())) {
1700
1883
  if (!isSafeUrl(value)) {
1701
1884
  if (typeof console !== "undefined") {
@@ -1776,8 +1959,14 @@ function isHydrating() {
1776
1959
  function hydrate(vnode, container) {
1777
1960
  _isHydrating = true;
1778
1961
  _hydrationCursor = { parent: container, index: 0 };
1962
+ _hydrationMismatchCount = 0;
1779
1963
  try {
1780
1964
  const result = hydrateNode(vnode, container);
1965
+ if (__DEV__ && _hydrationMismatchCount > 0) {
1966
+ console.warn(
1967
+ `[what] Hydration completed with ${_hydrationMismatchCount} mismatch${_hydrationMismatchCount === 1 ? "" : "es"}. See previous warnings for details. This usually means server and client render different initial HTML.`
1968
+ );
1969
+ }
1781
1970
  return result;
1782
1971
  } finally {
1783
1972
  _isHydrating = false;
@@ -1800,10 +1989,37 @@ function claimNode(parent) {
1800
1989
  }
1801
1990
  return null;
1802
1991
  }
1803
- function isDevMode() {
1804
- return typeof process !== "undefined" && true;
1992
+ var _hydrationMismatchCount = 0;
1993
+ function getHydrationMismatchCount() {
1994
+ return _hydrationMismatchCount;
1995
+ }
1996
+ function reportHydrationMismatch(expected, actual, componentName) {
1997
+ _hydrationMismatchCount++;
1998
+ if (__DEV__) {
1999
+ const context = {
2000
+ component: componentName || "unknown",
2001
+ serverHTML: actual,
2002
+ clientHTML: expected
2003
+ };
2004
+ const whatError = createWhatError("HYDRATION_MISMATCH", context);
2005
+ collectError(whatError);
2006
+ if (__devtools?.onHydrationMismatch) {
2007
+ __devtools.onHydrationMismatch({
2008
+ component: componentName || "unknown",
2009
+ expected,
2010
+ actual,
2011
+ mismatchCount: _hydrationMismatchCount
2012
+ });
2013
+ }
2014
+ if (__devtools?.onError) {
2015
+ __devtools.onError(whatError, { type: "hydration", component: componentName });
2016
+ }
2017
+ console.warn(
2018
+ `[what] Hydration mismatch: expected ${expected}, got ${actual}` + (componentName ? ` (in ${componentName})` : "") + ". Falling back to client render."
2019
+ );
2020
+ }
1805
2021
  }
1806
- function hydrateNode(vnode, parent) {
2022
+ function hydrateNode(vnode, parent, _componentName) {
1807
2023
  if (vnode == null || typeof vnode === "boolean") {
1808
2024
  return null;
1809
2025
  }
@@ -1811,19 +2027,21 @@ function hydrateNode(vnode, parent) {
1811
2027
  const existing = claimNode(parent);
1812
2028
  const text = String(vnode);
1813
2029
  if (existing && existing.nodeType === 3) {
1814
- if (isDevMode() && existing.textContent !== text) {
1815
- console.warn(
1816
- `[what] Hydration mismatch: expected text "${text}", got "${existing.textContent}"`
2030
+ if (__DEV__ && existing.textContent !== text) {
2031
+ reportHydrationMismatch(
2032
+ `text "${text}"`,
2033
+ `text "${existing.textContent}"`,
2034
+ _componentName
1817
2035
  );
1818
2036
  existing.textContent = text;
1819
2037
  }
1820
2038
  return existing;
1821
2039
  }
1822
- if (isDevMode()) {
1823
- console.warn(
1824
- `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
1825
- );
1826
- }
2040
+ reportHydrationMismatch(
2041
+ `text node "${text}"`,
2042
+ existing ? existing.nodeName : "nothing",
2043
+ _componentName
2044
+ );
1827
2045
  const textNode2 = document.createTextNode(text);
1828
2046
  if (existing) {
1829
2047
  parent.replaceChild(textNode2, existing);
@@ -1834,7 +2052,7 @@ function hydrateNode(vnode, parent) {
1834
2052
  }
1835
2053
  if (typeof vnode === "function") {
1836
2054
  const initialValue = vnode();
1837
- let current = hydrateNode(initialValue, parent);
2055
+ let current = hydrateNode(initialValue, parent, _componentName);
1838
2056
  effect(() => {
1839
2057
  const value = vnode();
1840
2058
  if (!_isHydrating) {
@@ -1846,7 +2064,7 @@ function hydrateNode(vnode, parent) {
1846
2064
  if (Array.isArray(vnode)) {
1847
2065
  const nodes = [];
1848
2066
  for (const child of vnode) {
1849
- const node = hydrateNode(child, parent);
2067
+ const node = hydrateNode(child, parent, _componentName);
1850
2068
  if (node) nodes.push(node);
1851
2069
  }
1852
2070
  return nodes.length === 1 ? nodes[0] : nodes;
@@ -1855,6 +2073,7 @@ function hydrateNode(vnode, parent) {
1855
2073
  if (typeof vnode.tag === "function") {
1856
2074
  const componentStack2 = getComponentStack();
1857
2075
  const Component = vnode.tag;
2076
+ const compName = Component.displayName || Component.name || "Anonymous";
1858
2077
  const props = vnode.props || {};
1859
2078
  const children = vnode.children || [];
1860
2079
  const ctx = {
@@ -1875,7 +2094,9 @@ function hydrateNode(vnode, parent) {
1875
2094
  result = Component({ ...props, children: propsChildren });
1876
2095
  } catch (error) {
1877
2096
  componentStack2.pop();
1878
- console.error("[what] Error in component during hydration:", Component.name || "Anonymous", error);
2097
+ if (__DEV__) {
2098
+ console.error("[what] Error in component during hydration:", compName, error);
2099
+ }
1879
2100
  return null;
1880
2101
  }
1881
2102
  componentStack2.pop();
@@ -1892,28 +2113,36 @@ function hydrateNode(vnode, parent) {
1892
2113
  }
1893
2114
  });
1894
2115
  }
1895
- return hydrateNode(result, parent);
2116
+ return hydrateNode(result, parent, compName);
1896
2117
  }
1897
2118
  const existing = claimNode(parent);
1898
2119
  const expectedTag = vnode.tag.toUpperCase();
1899
2120
  if (existing && existing.nodeType === 1 && existing.nodeName === expectedTag) {
1900
2121
  hydrateElementProps(existing, vnode.props || {});
2122
+ if (vnode.props?.ref) {
2123
+ const ref = vnode.props.ref;
2124
+ if (typeof ref === "function") {
2125
+ ref(existing);
2126
+ } else if (typeof ref === "object" && ref !== null) {
2127
+ ref.current = existing;
2128
+ }
2129
+ }
1901
2130
  const savedCursor = _hydrationCursor;
1902
2131
  _hydrationCursor = { parent: existing, index: 0 };
1903
2132
  const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
1904
2133
  if (rawInner == null) {
1905
2134
  for (const child of vnode.children) {
1906
- hydrateNode(child, existing);
2135
+ hydrateNode(child, existing, _componentName);
1907
2136
  }
1908
2137
  }
1909
2138
  _hydrationCursor = savedCursor;
1910
2139
  return existing;
1911
2140
  }
1912
- if (isDevMode()) {
1913
- console.warn(
1914
- `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
1915
- );
1916
- }
2141
+ reportHydrationMismatch(
2142
+ `<${vnode.tag}>`,
2143
+ existing ? existing.nodeName : "nothing",
2144
+ _componentName
2145
+ );
1917
2146
  const newEl = document.createElement(vnode.tag);
1918
2147
  for (const key in vnode.props || {}) {
1919
2148
  if (key === "children" || key === "key") continue;
@@ -1970,6 +2199,18 @@ function hydrateElementProps(el, props) {
1970
2199
  continue;
1971
2200
  }
1972
2201
  if (key === "data-hk") continue;
2202
+ if (__DEV__ && typeof value === "string") {
2203
+ const attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key;
2204
+ const serverValue = el.getAttribute(attrName);
2205
+ if (serverValue !== null && serverValue !== value) {
2206
+ reportHydrationMismatch(
2207
+ `${attrName}="${value}"`,
2208
+ `${attrName}="${serverValue}"`,
2209
+ el.tagName.toLowerCase()
2210
+ );
2211
+ setProp2(el, key, value);
2212
+ }
2213
+ }
1973
2214
  }
1974
2215
  }
1975
2216
  export {
@@ -1979,6 +2220,7 @@ export {
1979
2220
  classList,
1980
2221
  delegateEvents,
1981
2222
  effect,
2223
+ getHydrationMismatchCount,
1982
2224
  hydrate,
1983
2225
  insert,
1984
2226
  isHydrating,