what-core 0.6.2 → 0.6.5

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 (52) 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 +330 -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 +262 -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/render.d.ts +1 -1
  28. package/src/agent-context.js +1 -1
  29. package/src/compiler.js +18 -0
  30. package/src/components.js +73 -27
  31. package/src/devtools.js +4 -0
  32. package/src/dom.js +7 -0
  33. package/src/guardrails.js +3 -4
  34. package/src/hooks.js +0 -11
  35. package/src/index.js +5 -9
  36. package/src/render.js +91 -24
  37. package/testing.d.ts +1 -1
  38. package/dist/a11y.js +0 -440
  39. package/dist/animation.js +0 -548
  40. package/dist/components.js +0 -229
  41. package/dist/data.js +0 -638
  42. package/dist/dom.js +0 -439
  43. package/dist/form.js +0 -509
  44. package/dist/h.js +0 -152
  45. package/dist/head.js +0 -51
  46. package/dist/helpers.js +0 -140
  47. package/dist/hooks.js +0 -210
  48. package/dist/reactive.js +0 -432
  49. package/dist/scheduler.js +0 -246
  50. package/dist/skeleton.js +0 -363
  51. package/dist/store.js +0 -83
  52. 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) {
@@ -1777,8 +1959,14 @@ function isHydrating() {
1777
1959
  function hydrate(vnode, container) {
1778
1960
  _isHydrating = true;
1779
1961
  _hydrationCursor = { parent: container, index: 0 };
1962
+ _hydrationMismatchCount = 0;
1780
1963
  try {
1781
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
+ }
1782
1970
  return result;
1783
1971
  } finally {
1784
1972
  _isHydrating = false;
@@ -1801,10 +1989,37 @@ function claimNode(parent) {
1801
1989
  }
1802
1990
  return null;
1803
1991
  }
1804
- function isDevMode() {
1805
- 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
+ }
1806
2021
  }
1807
- function hydrateNode(vnode, parent) {
2022
+ function hydrateNode(vnode, parent, _componentName) {
1808
2023
  if (vnode == null || typeof vnode === "boolean") {
1809
2024
  return null;
1810
2025
  }
@@ -1812,19 +2027,21 @@ function hydrateNode(vnode, parent) {
1812
2027
  const existing = claimNode(parent);
1813
2028
  const text = String(vnode);
1814
2029
  if (existing && existing.nodeType === 3) {
1815
- if (isDevMode() && existing.textContent !== text) {
1816
- console.warn(
1817
- `[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
1818
2035
  );
1819
2036
  existing.textContent = text;
1820
2037
  }
1821
2038
  return existing;
1822
2039
  }
1823
- if (isDevMode()) {
1824
- console.warn(
1825
- `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
1826
- );
1827
- }
2040
+ reportHydrationMismatch(
2041
+ `text node "${text}"`,
2042
+ existing ? existing.nodeName : "nothing",
2043
+ _componentName
2044
+ );
1828
2045
  const textNode2 = document.createTextNode(text);
1829
2046
  if (existing) {
1830
2047
  parent.replaceChild(textNode2, existing);
@@ -1835,7 +2052,7 @@ function hydrateNode(vnode, parent) {
1835
2052
  }
1836
2053
  if (typeof vnode === "function") {
1837
2054
  const initialValue = vnode();
1838
- let current = hydrateNode(initialValue, parent);
2055
+ let current = hydrateNode(initialValue, parent, _componentName);
1839
2056
  effect(() => {
1840
2057
  const value = vnode();
1841
2058
  if (!_isHydrating) {
@@ -1847,7 +2064,7 @@ function hydrateNode(vnode, parent) {
1847
2064
  if (Array.isArray(vnode)) {
1848
2065
  const nodes = [];
1849
2066
  for (const child of vnode) {
1850
- const node = hydrateNode(child, parent);
2067
+ const node = hydrateNode(child, parent, _componentName);
1851
2068
  if (node) nodes.push(node);
1852
2069
  }
1853
2070
  return nodes.length === 1 ? nodes[0] : nodes;
@@ -1856,6 +2073,7 @@ function hydrateNode(vnode, parent) {
1856
2073
  if (typeof vnode.tag === "function") {
1857
2074
  const componentStack2 = getComponentStack();
1858
2075
  const Component = vnode.tag;
2076
+ const compName = Component.displayName || Component.name || "Anonymous";
1859
2077
  const props = vnode.props || {};
1860
2078
  const children = vnode.children || [];
1861
2079
  const ctx = {
@@ -1876,7 +2094,9 @@ function hydrateNode(vnode, parent) {
1876
2094
  result = Component({ ...props, children: propsChildren });
1877
2095
  } catch (error) {
1878
2096
  componentStack2.pop();
1879
- 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
+ }
1880
2100
  return null;
1881
2101
  }
1882
2102
  componentStack2.pop();
@@ -1893,28 +2113,36 @@ function hydrateNode(vnode, parent) {
1893
2113
  }
1894
2114
  });
1895
2115
  }
1896
- return hydrateNode(result, parent);
2116
+ return hydrateNode(result, parent, compName);
1897
2117
  }
1898
2118
  const existing = claimNode(parent);
1899
2119
  const expectedTag = vnode.tag.toUpperCase();
1900
2120
  if (existing && existing.nodeType === 1 && existing.nodeName === expectedTag) {
1901
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
+ }
1902
2130
  const savedCursor = _hydrationCursor;
1903
2131
  _hydrationCursor = { parent: existing, index: 0 };
1904
2132
  const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
1905
2133
  if (rawInner == null) {
1906
2134
  for (const child of vnode.children) {
1907
- hydrateNode(child, existing);
2135
+ hydrateNode(child, existing, _componentName);
1908
2136
  }
1909
2137
  }
1910
2138
  _hydrationCursor = savedCursor;
1911
2139
  return existing;
1912
2140
  }
1913
- if (isDevMode()) {
1914
- console.warn(
1915
- `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
1916
- );
1917
- }
2141
+ reportHydrationMismatch(
2142
+ `<${vnode.tag}>`,
2143
+ existing ? existing.nodeName : "nothing",
2144
+ _componentName
2145
+ );
1918
2146
  const newEl = document.createElement(vnode.tag);
1919
2147
  for (const key in vnode.props || {}) {
1920
2148
  if (key === "children" || key === "key") continue;
@@ -1971,6 +2199,18 @@ function hydrateElementProps(el, props) {
1971
2199
  continue;
1972
2200
  }
1973
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
+ }
1974
2214
  }
1975
2215
  }
1976
2216
  export {
@@ -1980,6 +2220,7 @@ export {
1980
2220
  classList,
1981
2221
  delegateEvents,
1982
2222
  effect,
2223
+ getHydrationMismatchCount,
1983
2224
  hydrate,
1984
2225
  insert,
1985
2226
  isHydrating,