round-core 0.0.6 → 0.0.8
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 +21 -0
- package/dist/index.d.ts +341 -326
- package/dist/vite-plugin.js +52 -3
- package/package.json +7 -3
- package/.github/workflows/benchmarks.yml +0 -44
- package/Round.png +0 -0
- package/benchmarks/apps/react/index.html +0 -9
- package/benchmarks/apps/react/main.jsx +0 -25
- package/benchmarks/apps/react/vite.config.js +0 -12
- package/benchmarks/apps/round/index.html +0 -11
- package/benchmarks/apps/round/main.jsx +0 -22
- package/benchmarks/apps/round/vite.config.js +0 -15
- package/benchmarks/bun.lock +0 -497
- package/benchmarks/dist-bench/react/assets/index-9KGqIPOU.js +0 -8
- package/benchmarks/dist-bench/react/index.html +0 -10
- package/benchmarks/dist-bench/round/assets/index-CBBIRhox.js +0 -52
- package/benchmarks/dist-bench/round/index.html +0 -8
- package/benchmarks/package.json +0 -22
- package/benchmarks/scripts/measure-build.js +0 -64
- package/benchmarks/tests/runtime.bench.js +0 -51
- package/benchmarks/vitest.config.js +0 -8
- package/bun.lock +0 -425
- package/cli.js +0 -2
- package/index.js +0 -2
- package/logo.svg +0 -10
- package/src/cli.js +0 -608
- package/src/compiler/index.js +0 -2
- package/src/compiler/transformer.js +0 -443
- package/src/compiler/vite-plugin.js +0 -472
- package/src/index.d.ts +0 -326
- package/src/index.js +0 -45
- package/src/runtime/context.js +0 -101
- package/src/runtime/dom.js +0 -403
- package/src/runtime/error-boundary.js +0 -48
- package/src/runtime/error-reporter.js +0 -13
- package/src/runtime/error-store.js +0 -85
- package/src/runtime/errors.js +0 -152
- package/src/runtime/lifecycle.js +0 -142
- package/src/runtime/markdown.js +0 -72
- package/src/runtime/router.js +0 -468
- package/src/runtime/signals.js +0 -548
- package/src/runtime/store.js +0 -215
- package/src/runtime/suspense.js +0 -128
- package/vite.config.build.js +0 -48
- package/vite.config.js +0 -10
- package/vitest.config.js +0 -8
package/src/runtime/dom.js
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
import { effect, untrack } from './signals.js';
|
|
2
|
-
import { runInLifecycle, createComponentInstance, mountComponent, initLifecycleRoot } from './lifecycle.js';
|
|
3
|
-
import { reportErrorSafe } from './error-reporter.js';
|
|
4
|
-
import { captureContext, runInContext, readContext } from './context.js';
|
|
5
|
-
import { SuspenseContext } from './suspense.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let isObserverInitialized = false;
|
|
9
|
-
|
|
10
|
-
const warnedSignals = new Set();
|
|
11
|
-
|
|
12
|
-
function isPromiseLike(v) {
|
|
13
|
-
return v && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function warnSignalDirectUsage(fn, kind) {
|
|
17
|
-
try {
|
|
18
|
-
if (typeof fn !== 'function') return;
|
|
19
|
-
if (typeof fn.peek !== 'function') return;
|
|
20
|
-
if (!('value' in fn)) return;
|
|
21
|
-
// Using signals as dynamic children/props is a supported pattern.
|
|
22
|
-
if (kind === 'child') return;
|
|
23
|
-
if (typeof kind === 'string' && kind.startsWith('prop:')) return;
|
|
24
|
-
const key = `${kind}:${fn.name ?? 'signal'}`;
|
|
25
|
-
if (warnedSignals.has(key)) return;
|
|
26
|
-
warnedSignals.add(key);
|
|
27
|
-
console.warn(`[round] Prefer {signal()} (reactive) or {signal.value} (static). Direct {signal} usage is allowed but discouraged.`);
|
|
28
|
-
} catch {
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create a DOM element or instance a component.
|
|
34
|
-
* @param {string | Function} tag HTML tag name or Component function.
|
|
35
|
-
* @param {object} [props] Element attributes or component props.
|
|
36
|
-
* @param {...any} children Child nodes.
|
|
37
|
-
* @returns {Node} The resulting DOM node.
|
|
38
|
-
*/
|
|
39
|
-
export function createElement(tag, props = {}, ...children) {
|
|
40
|
-
if (typeof tag === 'function') {
|
|
41
|
-
const componentInstance = createComponentInstance();
|
|
42
|
-
const componentName = tag?.name ?? 'Anonymous';
|
|
43
|
-
componentInstance.name = componentName;
|
|
44
|
-
|
|
45
|
-
let node = runInLifecycle(componentInstance, () => {
|
|
46
|
-
const componentProps = { ...props, children };
|
|
47
|
-
try {
|
|
48
|
-
const res = untrack(() => tag(componentProps));
|
|
49
|
-
if (isPromiseLike(res)) throw res;
|
|
50
|
-
return res;
|
|
51
|
-
} catch (e) {
|
|
52
|
-
if (isPromiseLike(e)) {
|
|
53
|
-
const suspense = readContext(SuspenseContext);
|
|
54
|
-
if (!suspense) {
|
|
55
|
-
throw new Error("cannot instance a lazy component outside a suspense");
|
|
56
|
-
}
|
|
57
|
-
throw e;
|
|
58
|
-
}
|
|
59
|
-
reportErrorSafe(e, { phase: 'component.render', component: componentName });
|
|
60
|
-
return createElement('div', { style: { padding: '16px' } }, `Error in ${componentName}`);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (Array.isArray(node)) {
|
|
65
|
-
const wrapper = document.createElement('span');
|
|
66
|
-
wrapper.style.display = 'contents';
|
|
67
|
-
node.forEach(n => appendChild(wrapper, n));
|
|
68
|
-
node = wrapper;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (node instanceof Node) {
|
|
72
|
-
node._componentInstance = componentInstance;
|
|
73
|
-
componentInstance.nodes.push(node);
|
|
74
|
-
|
|
75
|
-
componentInstance.mountTimerId = setTimeout(() => {
|
|
76
|
-
componentInstance.mountTimerId = null;
|
|
77
|
-
mountComponent(componentInstance);
|
|
78
|
-
}, 0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return node;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (typeof tag === 'string') {
|
|
85
|
-
const isCustomElement = tag.includes('-');
|
|
86
|
-
|
|
87
|
-
const isStandard = /^(a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|label|legend|li|link|main|map|mark|meta|meter|nav|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|search|section|select|slot|small|source|span|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|video|wbr|menu|animate|animateMotion|animateTransform|circle|clipPath|defs|desc|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|foreignObject|g|image|line|linearGradient|marker|mask|metadata|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|stop|switch|symbol|text|textPath|tspan|use|view)$/.test(tag);
|
|
88
|
-
|
|
89
|
-
// __ROUND_CUSTOM_TAGS__ is injected by the vite plugin from round.config.json
|
|
90
|
-
const isCustomConfigured = typeof __ROUND_CUSTOM_TAGS__ !== 'undefined' && __ROUND_CUSTOM_TAGS__.includes(tag);
|
|
91
|
-
|
|
92
|
-
if (!isCustomElement && !isStandard && !isCustomConfigured && /^[a-z]/.test(tag)) {
|
|
93
|
-
throw new Error(`Component names must start with an uppercase letter: <${tag} />`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const element = document.createElement(tag);
|
|
98
|
-
|
|
99
|
-
if (props) {
|
|
100
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
101
|
-
if (key === 'bind:value' || key === 'bind:checked') {
|
|
102
|
-
const isSignalLike = typeof value === 'function' && typeof value.peek === 'function' && ('value' in value);
|
|
103
|
-
const isBindable = isSignalLike && value.bind === true;
|
|
104
|
-
|
|
105
|
-
if (!isSignalLike) {
|
|
106
|
-
try {
|
|
107
|
-
console.warn('[round] bind:* expects a signal/bindable. Example: const name = bindable(\'\'); <input bind:value={name} />');
|
|
108
|
-
} catch {
|
|
109
|
-
}
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!isBindable) {
|
|
114
|
-
try {
|
|
115
|
-
console.warn('[round] bind:* is intended to be used with bindable(). Plain signal() is accepted but discouraged.');
|
|
116
|
-
} catch {
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const isValueBinding = key === 'bind:value';
|
|
121
|
-
const isCheckedBinding = key === 'bind:checked';
|
|
122
|
-
const el = element;
|
|
123
|
-
const tagName = String(el.tagName ?? '').toLowerCase();
|
|
124
|
-
const type = String(el.getAttribute?.('type') ?? '').toLowerCase();
|
|
125
|
-
|
|
126
|
-
const isInput = tagName === 'input';
|
|
127
|
-
const isTextarea = tagName === 'textarea';
|
|
128
|
-
const isSelect = tagName === 'select';
|
|
129
|
-
|
|
130
|
-
if (isCheckedBinding && !(isInput && (type === 'checkbox' || type === 'radio'))) {
|
|
131
|
-
try {
|
|
132
|
-
console.warn(`[round] bind:checked is only supported on <input type="checkbox|radio">. Got <${tagName}${type ? ` type=\"${type}\"` : ''}>.`);
|
|
133
|
-
} catch {
|
|
134
|
-
}
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (isValueBinding && !(isInput || isTextarea || isSelect)) {
|
|
139
|
-
try {
|
|
140
|
-
console.warn(`[round] bind:value is only supported on <input>, <textarea>, and <select>. Got <${tagName}>.`);
|
|
141
|
-
} catch {
|
|
142
|
-
}
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const coerceFromDom = () => {
|
|
147
|
-
if (isCheckedBinding) {
|
|
148
|
-
if (type === 'radio') {
|
|
149
|
-
return Boolean(el.checked);
|
|
150
|
-
}
|
|
151
|
-
return Boolean(el.checked);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (isInput && type === 'number') {
|
|
155
|
-
const raw = el.value;
|
|
156
|
-
if (raw === '') return '';
|
|
157
|
-
const n = Number(raw);
|
|
158
|
-
return Number.isFinite(n) ? n : raw;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (isSelect && el.multiple) {
|
|
162
|
-
try {
|
|
163
|
-
return Array.from(el.selectedOptions ?? []).map(o => o.value);
|
|
164
|
-
} catch {
|
|
165
|
-
return [];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return el.value;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const writeToDom = (v) => {
|
|
173
|
-
if (isCheckedBinding) {
|
|
174
|
-
const b = Boolean(v);
|
|
175
|
-
if (type === 'radio') {
|
|
176
|
-
el.checked = b;
|
|
177
|
-
} else {
|
|
178
|
-
el.checked = b;
|
|
179
|
-
}
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (isSelect && el.multiple) {
|
|
184
|
-
const arr = Array.isArray(v) ? v.map(x => String(x)) : [];
|
|
185
|
-
try {
|
|
186
|
-
Array.from(el.options ?? []).forEach(opt => {
|
|
187
|
-
opt.selected = arr.includes(opt.value);
|
|
188
|
-
});
|
|
189
|
-
} catch {
|
|
190
|
-
}
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
el.value = v ?? '';
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const warnTypeMismatch = (next) => {
|
|
198
|
-
try {
|
|
199
|
-
if (isCheckedBinding && typeof next !== 'boolean') {
|
|
200
|
-
console.warn('[round] bind:checked expects a boolean signal value.');
|
|
201
|
-
}
|
|
202
|
-
if (isValueBinding && isSelect && el.multiple && !Array.isArray(next)) {
|
|
203
|
-
console.warn('[round] bind:value on <select multiple> expects an array signal value.');
|
|
204
|
-
}
|
|
205
|
-
if (isValueBinding && isInput && type === 'number' && !(typeof next === 'number' || typeof next === 'string')) {
|
|
206
|
-
console.warn('[round] bind:value on <input type="number"> expects number|string (empty string allowed).');
|
|
207
|
-
}
|
|
208
|
-
} catch {
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
effect(() => {
|
|
213
|
-
const v = value();
|
|
214
|
-
warnTypeMismatch(v);
|
|
215
|
-
writeToDom(v);
|
|
216
|
-
}, { onLoad: false });
|
|
217
|
-
|
|
218
|
-
const validateOn = isValueBinding && value && typeof value === 'function'
|
|
219
|
-
? value.__round_validateOn
|
|
220
|
-
: null;
|
|
221
|
-
const valueEvent = (validateOn === 'blur') ? 'blur' : (isSelect ? 'change' : 'input');
|
|
222
|
-
const eventName = isCheckedBinding ? 'change' : valueEvent;
|
|
223
|
-
el.addEventListener(eventName, (e) => {
|
|
224
|
-
try {
|
|
225
|
-
const target = e.currentTarget;
|
|
226
|
-
if (!target) return;
|
|
227
|
-
const next = coerceFromDom();
|
|
228
|
-
value(next);
|
|
229
|
-
} catch {
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (key.startsWith('on') && typeof value === 'function') {
|
|
236
|
-
element.addEventListener(key.toLowerCase().substring(2), value);
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (key === 'dangerouslySetInnerHTML') {
|
|
241
|
-
if (typeof value === 'function') {
|
|
242
|
-
effect(() => {
|
|
243
|
-
const v = value();
|
|
244
|
-
if (v && typeof v === 'object' && '__html' in v) {
|
|
245
|
-
element.innerHTML = v.__html ?? '';
|
|
246
|
-
}
|
|
247
|
-
}, { onLoad: false });
|
|
248
|
-
} else if (value && typeof value === 'object' && '__html' in value) {
|
|
249
|
-
element.innerHTML = value.__html ?? '';
|
|
250
|
-
}
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (key === 'style') {
|
|
255
|
-
if (typeof value === 'function') {
|
|
256
|
-
effect(() => {
|
|
257
|
-
const v = value();
|
|
258
|
-
if (v && typeof v === 'object') {
|
|
259
|
-
Object.assign(element.style, v);
|
|
260
|
-
}
|
|
261
|
-
}, { onLoad: false });
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (value && typeof value === 'object') {
|
|
265
|
-
Object.assign(element.style, value);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (typeof value === 'function') {
|
|
271
|
-
warnSignalDirectUsage(value, `prop:${key}`);
|
|
272
|
-
effect(() => {
|
|
273
|
-
const val = value();
|
|
274
|
-
if (key === 'className') element.className = val;
|
|
275
|
-
else if (key === 'value') element.value = val;
|
|
276
|
-
else if (key === 'checked') element.checked = Boolean(val);
|
|
277
|
-
else element.setAttribute(key, val);
|
|
278
|
-
}, { onLoad: false });
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (key === 'classList') {
|
|
283
|
-
if (value && typeof value === 'object') {
|
|
284
|
-
Object.entries(value).forEach(([className, condition]) => {
|
|
285
|
-
if (typeof condition === 'function') {
|
|
286
|
-
effect(() => {
|
|
287
|
-
element.classList.toggle(className, !!condition());
|
|
288
|
-
}, { onLoad: false });
|
|
289
|
-
} else {
|
|
290
|
-
element.classList.toggle(className, !!condition);
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (key === 'className') element.className = value;
|
|
298
|
-
else if (key === 'value') element.value = value;
|
|
299
|
-
else if (key === 'checked') element.checked = Boolean(value);
|
|
300
|
-
else element.setAttribute(key, value);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
children.forEach(child => appendChild(element, child));
|
|
305
|
-
|
|
306
|
-
return element;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function appendChild(parent, child) {
|
|
310
|
-
if (child === null || child === undefined) return;
|
|
311
|
-
|
|
312
|
-
if (Array.isArray(child)) {
|
|
313
|
-
child.forEach(c => appendChild(parent, c));
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (typeof child === 'string' || typeof child === 'number') {
|
|
318
|
-
parent.appendChild(document.createTextNode(child));
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (typeof child === 'function') {
|
|
323
|
-
warnSignalDirectUsage(child, 'child');
|
|
324
|
-
const placeholder = document.createTextNode('');
|
|
325
|
-
parent.appendChild(placeholder);
|
|
326
|
-
|
|
327
|
-
let currentNode = placeholder;
|
|
328
|
-
|
|
329
|
-
const ctxSnapshot = captureContext();
|
|
330
|
-
|
|
331
|
-
effect(() => {
|
|
332
|
-
runInContext(ctxSnapshot, () => {
|
|
333
|
-
let val;
|
|
334
|
-
try {
|
|
335
|
-
val = child();
|
|
336
|
-
if (isPromiseLike(val)) throw val;
|
|
337
|
-
} catch (e) {
|
|
338
|
-
if (isPromiseLike(e)) {
|
|
339
|
-
const suspense = readContext(SuspenseContext);
|
|
340
|
-
if (suspense && typeof suspense.register === 'function') {
|
|
341
|
-
suspense.register(e);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
throw new Error("cannot instance a lazy component outside a suspense");
|
|
345
|
-
}
|
|
346
|
-
reportErrorSafe(e, { phase: 'child.dynamic' });
|
|
347
|
-
val = createElement('div', { style: { padding: '16px' } }, 'Error');
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (Array.isArray(val)) {
|
|
351
|
-
if (!(currentNode instanceof Element) || !currentNode._roundArrayWrapper) {
|
|
352
|
-
const wrapper = document.createElement('span');
|
|
353
|
-
wrapper.style.display = 'contents';
|
|
354
|
-
wrapper._roundArrayWrapper = true;
|
|
355
|
-
if (currentNode.parentNode) {
|
|
356
|
-
currentNode.parentNode.replaceChild(wrapper, currentNode);
|
|
357
|
-
currentNode = wrapper;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
while (currentNode.firstChild) currentNode.removeChild(currentNode.firstChild);
|
|
362
|
-
val.forEach(v => appendChild(currentNode, v));
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (val instanceof Node) {
|
|
367
|
-
if (currentNode !== val) {
|
|
368
|
-
if (currentNode.parentNode) {
|
|
369
|
-
currentNode.parentNode.replaceChild(val, currentNode);
|
|
370
|
-
currentNode = val;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
const textContent = (val === null || val === undefined) ? '' : val;
|
|
376
|
-
|
|
377
|
-
if (currentNode instanceof Element) {
|
|
378
|
-
const newText = document.createTextNode(textContent);
|
|
379
|
-
if (currentNode.parentNode) {
|
|
380
|
-
currentNode.parentNode.replaceChild(newText, currentNode);
|
|
381
|
-
currentNode = newText;
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
currentNode.textContent = textContent;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
}, { onLoad: false });
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (child instanceof Node) {
|
|
393
|
-
parent.appendChild(child);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* A grouping component that returns its children without a wrapper element.
|
|
400
|
-
*/
|
|
401
|
-
export function Fragment(props) {
|
|
402
|
-
return props.children;
|
|
403
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { signal } from './signals.js';
|
|
2
|
-
import { createElement } from './dom.js';
|
|
3
|
-
import { reportError } from './error-store.js';
|
|
4
|
-
|
|
5
|
-
export function ErrorBoundary(props = {}) {
|
|
6
|
-
const error = signal(null);
|
|
7
|
-
|
|
8
|
-
const name = props.name ?? 'ErrorBoundary';
|
|
9
|
-
const fallback = props.fallback;
|
|
10
|
-
const resetKey = props.resetKey;
|
|
11
|
-
|
|
12
|
-
let lastResetKey = resetKey;
|
|
13
|
-
|
|
14
|
-
return createElement('span', { style: { display: 'contents' } }, () => {
|
|
15
|
-
if (resetKey !== undefined && resetKey !== lastResetKey) {
|
|
16
|
-
lastResetKey = resetKey;
|
|
17
|
-
if (error()) error(null);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const err = error();
|
|
21
|
-
if (err) {
|
|
22
|
-
if (typeof fallback === 'function') {
|
|
23
|
-
try {
|
|
24
|
-
return fallback({ error: err });
|
|
25
|
-
} catch (e) {
|
|
26
|
-
reportError(e, { phase: 'ErrorBoundary.fallback', component: name });
|
|
27
|
-
return createElement('div', { style: { padding: '16px' } }, 'ErrorBoundary fallback crashed');
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (fallback !== undefined) return fallback;
|
|
31
|
-
return createElement('div', { style: { padding: '16px' } }, 'Something went wrong.');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const renderFn = (typeof props.render === 'function')
|
|
35
|
-
? props.render
|
|
36
|
-
: (typeof props.children === 'function' ? props.children : null);
|
|
37
|
-
|
|
38
|
-
if (typeof renderFn !== 'function') return props.children ?? null;
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
return renderFn();
|
|
42
|
-
} catch (e) {
|
|
43
|
-
if (!error() || error() !== e) error(e);
|
|
44
|
-
reportError(e, { phase: 'ErrorBoundary.render', component: name });
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { signal } from './signals.js';
|
|
2
|
-
import { setErrorReporter } from './error-reporter.js';
|
|
3
|
-
|
|
4
|
-
const errors = signal([]);
|
|
5
|
-
|
|
6
|
-
let lastSentKey = null;
|
|
7
|
-
let lastSentAt = 0;
|
|
8
|
-
|
|
9
|
-
let lastStoredKey = null;
|
|
10
|
-
let lastStoredAt = 0;
|
|
11
|
-
|
|
12
|
-
export function reportError(error, info = {}) {
|
|
13
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
14
|
-
const stack = err.stack ? String(err.stack) : '';
|
|
15
|
-
const message = err.message;
|
|
16
|
-
const phase = info.phase ?? null;
|
|
17
|
-
const component = info.component ?? null;
|
|
18
|
-
const key = `${message}|${component ?? ''}|${phase ?? ''}|${stack}`;
|
|
19
|
-
const now = Date.now();
|
|
20
|
-
|
|
21
|
-
if (lastStoredKey === key && (now - lastStoredAt) < 1500) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
lastStoredKey = key;
|
|
25
|
-
lastStoredAt = now;
|
|
26
|
-
|
|
27
|
-
const entry = {
|
|
28
|
-
error: err,
|
|
29
|
-
message,
|
|
30
|
-
stack,
|
|
31
|
-
phase,
|
|
32
|
-
component,
|
|
33
|
-
time: now
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const current = typeof errors.peek === 'function' ? errors.peek() : errors();
|
|
37
|
-
errors([entry, ...(Array.isArray(current) ? current : [])]);
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const where = entry.component ? ` in ${entry.component}` : '';
|
|
41
|
-
const phase = entry.phase ? ` (${entry.phase})` : '';
|
|
42
|
-
const label = `[round] Runtime error${where}${phase}`;
|
|
43
|
-
|
|
44
|
-
if (typeof console.groupCollapsed === 'function') {
|
|
45
|
-
console.groupCollapsed(label);
|
|
46
|
-
console.error(entry.error);
|
|
47
|
-
if (entry.stack) console.log(entry.stack);
|
|
48
|
-
if (info && Object.keys(info).length) console.log('info:', info);
|
|
49
|
-
console.groupEnd();
|
|
50
|
-
} else {
|
|
51
|
-
console.error(label);
|
|
52
|
-
console.error(entry.error);
|
|
53
|
-
if (entry.stack) console.log(entry.stack);
|
|
54
|
-
if (info && Object.keys(info).length) console.log('info:', info);
|
|
55
|
-
}
|
|
56
|
-
} catch {
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
if (import.meta?.hot && typeof import.meta.hot.send === 'function') {
|
|
61
|
-
if (lastSentKey !== key || (now - lastSentAt) > 1500) {
|
|
62
|
-
lastSentKey = key;
|
|
63
|
-
lastSentAt = now;
|
|
64
|
-
import.meta.hot.send('round:runtime-error', {
|
|
65
|
-
message: entry.message,
|
|
66
|
-
stack: entry.stack ? String(entry.stack) : '',
|
|
67
|
-
phase: entry.phase,
|
|
68
|
-
component: entry.component,
|
|
69
|
-
time: entry.time
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function clearErrors() {
|
|
78
|
-
errors([]);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function useErrors() {
|
|
82
|
-
return errors;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
setErrorReporter(reportError);
|