@reckona/mreact-compat 0.0.138 → 0.0.140
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/dist/dom-props.d.ts.map +1 -1
- package/dist/dom-props.js +111 -44
- package/dist/dom-props.js.map +1 -1
- package/dist/element.d.ts +3 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +32 -2
- package/dist/element.js.map +1 -1
- package/dist/event-listeners.d.ts +2 -4
- package/dist/event-listeners.d.ts.map +1 -1
- package/dist/event-listeners.js +2 -1
- package/dist/event-listeners.js.map +1 -1
- package/dist/fiber-child.d.ts.map +1 -1
- package/dist/fiber-child.js +44 -0
- package/dist/fiber-child.js.map +1 -1
- package/dist/fiber-commit.d.ts.map +1 -1
- package/dist/fiber-commit.js +70 -0
- package/dist/fiber-commit.js.map +1 -1
- package/dist/hooks.d.ts +4 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +64 -6
- package/dist/hooks.js.map +1 -1
- package/dist/host-reconciler.d.ts.map +1 -1
- package/dist/host-reconciler.js +2 -2
- package/dist/host-reconciler.js.map +1 -1
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +2 -11
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/reconciler.d.ts.map +1 -1
- package/dist/reconciler.js +2 -2
- package/dist/reconciler.js.map +1 -1
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +29 -4
- package/dist/server-render.js.map +1 -1
- package/dist/url-safety.d.ts +1 -1
- package/dist/url-safety.d.ts.map +1 -1
- package/dist/url-safety.js +1 -1
- package/dist/url-safety.js.map +1 -1
- package/package.json +3 -3
- package/src/dom-props.ts +189 -49
- package/src/element.ts +45 -1
- package/src/event-listeners.ts +4 -5
- package/src/fiber-child.ts +67 -0
- package/src/fiber-commit.ts +92 -0
- package/src/hooks.ts +88 -8
- package/src/host-reconciler.ts +2 -3
- package/src/jsx-runtime.ts +2 -18
- package/src/reconciler.ts +2 -3
- package/src/server-render.ts +43 -4
- package/src/url-safety.ts +1 -0
package/src/hooks.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { isThenable } from "./thenable.js";
|
|
|
16
16
|
export interface RootRuntime {
|
|
17
17
|
currentElement?: unknown;
|
|
18
18
|
instances: Map<string, ComponentInstance>;
|
|
19
|
+
instanceKeysByPrefix: Map<string, Set<string>>;
|
|
19
20
|
activeInstanceKeys: Set<string> | undefined;
|
|
20
21
|
activeProfilerPaths: Set<string> | undefined;
|
|
21
22
|
mountedProfilerPaths: Set<string>;
|
|
@@ -55,8 +56,8 @@ interface ComponentInstance {
|
|
|
55
56
|
dirty: boolean;
|
|
56
57
|
disposed?: boolean;
|
|
57
58
|
contextDependencies?: Map<ReactCompatContextLike<unknown>, unknown>;
|
|
58
|
-
devToolsHooks
|
|
59
|
-
devToolsHookTypes
|
|
59
|
+
devToolsHooks?: DevToolsHookValue[];
|
|
60
|
+
devToolsHookTypes?: string[];
|
|
60
61
|
devToolsHookSuppressionDepth: number;
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -297,6 +298,7 @@ export function createRootRuntime(
|
|
|
297
298
|
): RootRuntime {
|
|
298
299
|
return {
|
|
299
300
|
instances: new Map(),
|
|
301
|
+
instanceKeysByPrefix: new Map(),
|
|
300
302
|
activeInstanceKeys: undefined,
|
|
301
303
|
activeProfilerPaths: undefined,
|
|
302
304
|
mountedProfilerPaths: new Set(),
|
|
@@ -491,20 +493,24 @@ export function renderWithRootRuntime<T>(
|
|
|
491
493
|
hooks: [],
|
|
492
494
|
hookIndex: 0,
|
|
493
495
|
dirty: false,
|
|
494
|
-
devToolsHooks: [],
|
|
495
|
-
devToolsHookTypes: [],
|
|
496
496
|
devToolsHookSuppressionDepth: 0,
|
|
497
497
|
};
|
|
498
498
|
instance.owner = owner;
|
|
499
499
|
instance.path = path;
|
|
500
500
|
runtime.instances.set(path, instance);
|
|
501
|
+
indexInstanceKey(runtime, path);
|
|
501
502
|
runtime.activeInstanceKeys?.add(path);
|
|
502
503
|
instance.hookIndex = 0;
|
|
503
504
|
instance.dirty = false;
|
|
504
505
|
instance.disposed = false;
|
|
505
506
|
delete instance.contextDependencies;
|
|
506
|
-
|
|
507
|
-
|
|
507
|
+
if (hasInstalledDevToolsHook()) {
|
|
508
|
+
instance.devToolsHooks = [];
|
|
509
|
+
instance.devToolsHookTypes = [];
|
|
510
|
+
} else {
|
|
511
|
+
delete instance.devToolsHooks;
|
|
512
|
+
delete instance.devToolsHookTypes;
|
|
513
|
+
}
|
|
508
514
|
instance.devToolsHookSuppressionDepth = 0;
|
|
509
515
|
hookRenderState.currentRuntime = runtime;
|
|
510
516
|
hookRenderState.currentInstance = instance;
|
|
@@ -547,13 +553,35 @@ export function hasContextDependency(
|
|
|
547
553
|
return keys.some((key) => runtime.instances.get(key)?.contextDependencies !== undefined);
|
|
548
554
|
}
|
|
549
555
|
|
|
556
|
+
export function collectRuntimeInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
|
|
557
|
+
const keys = runtime.instanceKeysByPrefix.get(prefix);
|
|
558
|
+
|
|
559
|
+
if (keys === undefined) {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const activeKeys: string[] = [];
|
|
564
|
+
|
|
565
|
+
for (const key of keys) {
|
|
566
|
+
if (runtime.instances.has(key)) {
|
|
567
|
+
activeKeys.push(key);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return activeKeys;
|
|
572
|
+
}
|
|
573
|
+
|
|
550
574
|
export function getDevToolsHookState(
|
|
551
575
|
runtime: RootRuntime,
|
|
552
576
|
path: string,
|
|
553
577
|
): DevToolsHookState | undefined {
|
|
554
578
|
const instance = runtime.instances.get(path);
|
|
555
579
|
|
|
556
|
-
if (
|
|
580
|
+
if (
|
|
581
|
+
instance === undefined ||
|
|
582
|
+
instance.devToolsHooks === undefined ||
|
|
583
|
+
instance.devToolsHookTypes === undefined
|
|
584
|
+
) {
|
|
557
585
|
return undefined;
|
|
558
586
|
}
|
|
559
587
|
|
|
@@ -966,7 +994,12 @@ function assignRef<T>(ref: unknown, value: T | null): void {
|
|
|
966
994
|
function recordDevToolsHook(type: string, value: DevToolsHookValue): void {
|
|
967
995
|
const instance = hookRenderState.currentInstance;
|
|
968
996
|
|
|
969
|
-
if (
|
|
997
|
+
if (
|
|
998
|
+
instance === undefined ||
|
|
999
|
+
instance.devToolsHookSuppressionDepth > 0 ||
|
|
1000
|
+
instance.devToolsHooks === undefined ||
|
|
1001
|
+
instance.devToolsHookTypes === undefined
|
|
1002
|
+
) {
|
|
970
1003
|
return;
|
|
971
1004
|
}
|
|
972
1005
|
|
|
@@ -974,6 +1007,12 @@ function recordDevToolsHook(type: string, value: DevToolsHookValue): void {
|
|
|
974
1007
|
instance.devToolsHooks.push(value);
|
|
975
1008
|
}
|
|
976
1009
|
|
|
1010
|
+
function hasInstalledDevToolsHook(): boolean {
|
|
1011
|
+
return typeof (globalThis as {
|
|
1012
|
+
__REACT_DEVTOOLS_GLOBAL_HOOK__?: { inject?: unknown } | undefined;
|
|
1013
|
+
}).__REACT_DEVTOOLS_GLOBAL_HOOK__?.inject === "function";
|
|
1014
|
+
}
|
|
1015
|
+
|
|
977
1016
|
function runWithoutDevToolsHookTracking<T>(callback: () => T): T {
|
|
978
1017
|
const instance = requireInstance();
|
|
979
1018
|
instance.devToolsHookSuppressionDepth += 1;
|
|
@@ -1941,10 +1980,51 @@ function cleanupInactiveInstances(runtime: RootRuntime): void {
|
|
|
1941
1980
|
if (!activeInstanceKeys.has(key)) {
|
|
1942
1981
|
cleanupInstance(instance);
|
|
1943
1982
|
runtime.instances.delete(key);
|
|
1983
|
+
removeInstanceKeyFromIndex(runtime, key);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
function indexInstanceKey(runtime: RootRuntime, key: string): void {
|
|
1989
|
+
for (const prefix of instanceKeyPrefixes(key)) {
|
|
1990
|
+
let keys = runtime.instanceKeysByPrefix.get(prefix);
|
|
1991
|
+
|
|
1992
|
+
if (keys === undefined) {
|
|
1993
|
+
keys = new Set();
|
|
1994
|
+
runtime.instanceKeysByPrefix.set(prefix, keys);
|
|
1944
1995
|
}
|
|
1996
|
+
|
|
1997
|
+
keys.add(key);
|
|
1945
1998
|
}
|
|
1946
1999
|
}
|
|
1947
2000
|
|
|
2001
|
+
function removeInstanceKeyFromIndex(runtime: RootRuntime, key: string): void {
|
|
2002
|
+
for (const prefix of instanceKeyPrefixes(key)) {
|
|
2003
|
+
const keys = runtime.instanceKeysByPrefix.get(prefix);
|
|
2004
|
+
|
|
2005
|
+
if (keys === undefined) {
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
keys.delete(key);
|
|
2010
|
+
|
|
2011
|
+
if (keys.size === 0) {
|
|
2012
|
+
runtime.instanceKeysByPrefix.delete(prefix);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
function instanceKeyPrefixes(key: string): string[] {
|
|
2018
|
+
const parts = key.split(".");
|
|
2019
|
+
const prefixes: string[] = [];
|
|
2020
|
+
|
|
2021
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
2022
|
+
prefixes.push(parts.slice(0, index).join("."));
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
return prefixes;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1948
2028
|
function cleanupInstance(instance: ComponentInstance): void {
|
|
1949
2029
|
instance.disposed = true;
|
|
1950
2030
|
for (const slot of instance.hooks) {
|
package/src/host-reconciler.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
restoreRuntimeSnapshot,
|
|
46
46
|
takeRuntimeSnapshot,
|
|
47
47
|
getDevToolsHookState,
|
|
48
|
+
collectRuntimeInstanceKeys,
|
|
48
49
|
hasContextDependency,
|
|
49
50
|
hasChangedContextDependency,
|
|
50
51
|
type RootRuntime,
|
|
@@ -3074,9 +3075,7 @@ function isLazyType(
|
|
|
3074
3075
|
}
|
|
3075
3076
|
|
|
3076
3077
|
function collectInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
|
|
3077
|
-
return
|
|
3078
|
-
(key) => key === prefix || key.startsWith(`${prefix}.`),
|
|
3079
|
-
);
|
|
3078
|
+
return collectRuntimeInstanceKeys(runtime, prefix);
|
|
3080
3079
|
}
|
|
3081
3080
|
|
|
3082
3081
|
function markActiveInstanceKeys(runtime: RootRuntime, keys: readonly string[]): void {
|
package/src/jsx-runtime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createElementFromJsxConfig, Fragment } from "./element.js";
|
|
2
2
|
import type {
|
|
3
3
|
ElementType,
|
|
4
4
|
ReactCompatElement,
|
|
@@ -99,21 +99,5 @@ function createElementFromJsx<P extends Record<string, unknown>>(
|
|
|
99
99
|
props: (P & { children?: ReactCompatNode; key?: unknown; ref?: unknown }) | null,
|
|
100
100
|
key: unknown,
|
|
101
101
|
): ReactCompatElement<P> {
|
|
102
|
-
|
|
103
|
-
children?: ReactCompatNode;
|
|
104
|
-
key?: unknown;
|
|
105
|
-
ref?: unknown;
|
|
106
|
-
};
|
|
107
|
-
const hasChildren = Object.hasOwn(config, "children");
|
|
108
|
-
const children = config.children;
|
|
109
|
-
|
|
110
|
-
if (key !== undefined) {
|
|
111
|
-
config.key = key;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
delete config.children;
|
|
115
|
-
|
|
116
|
-
return hasChildren
|
|
117
|
-
? createElement(type, config, children)
|
|
118
|
-
: createElement(type, config);
|
|
102
|
+
return createElementFromJsxConfig(type, props, key);
|
|
119
103
|
}
|
package/src/reconciler.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "./context.js";
|
|
25
25
|
import {
|
|
26
26
|
clearRuntimePortalNodes,
|
|
27
|
+
collectRuntimeInstanceKeys,
|
|
27
28
|
hasStableExternalStores,
|
|
28
29
|
restoreRuntimeSnapshot,
|
|
29
30
|
renderWithProfiler,
|
|
@@ -684,9 +685,7 @@ function getMemoRenderStates(runtime: RootRuntime): Map<string, MemoRenderState>
|
|
|
684
685
|
}
|
|
685
686
|
|
|
686
687
|
function collectInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
|
|
687
|
-
return
|
|
688
|
-
key === prefix || key.startsWith(`${prefix}.`),
|
|
689
|
-
);
|
|
688
|
+
return collectRuntimeInstanceKeys(runtime, prefix);
|
|
690
689
|
}
|
|
691
690
|
|
|
692
691
|
function markActiveInstanceKeys(runtime: RootRuntime, keys: readonly string[]): void {
|
package/src/server-render.ts
CHANGED
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
type RootRuntime,
|
|
26
26
|
type RootRuntimeOptions,
|
|
27
27
|
} from "./hooks.js";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
isDangerousHtmlAttribute,
|
|
30
|
+
isDangerousHtmlOptIn,
|
|
31
|
+
isUnsafeMetaRefreshContent,
|
|
32
|
+
isUnsafeUrlAttribute,
|
|
33
|
+
} from "./url-safety.js";
|
|
29
34
|
import { escapeHtmlAttribute as escapeHtml } from "@reckona/mreact-shared/html-escape";
|
|
30
35
|
import { isVoidHtmlElement } from "@reckona/mreact-shared";
|
|
31
36
|
|
|
@@ -195,7 +200,8 @@ function renderElementToString(
|
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
function renderAttributesToString(props: Record<string, unknown>): string {
|
|
198
|
-
const
|
|
203
|
+
const sanitizedProps = sanitizeMetaRefreshProps(props);
|
|
204
|
+
const entries = Object.entries(sanitizedProps);
|
|
199
205
|
if (
|
|
200
206
|
entries.length === 0 ||
|
|
201
207
|
(entries.length === 1 && entries[0]?.[0] === "children")
|
|
@@ -210,6 +216,23 @@ function renderAttributesToString(props: Record<string, unknown>): string {
|
|
|
210
216
|
return attributes;
|
|
211
217
|
}
|
|
212
218
|
|
|
219
|
+
function sanitizeMetaRefreshProps(
|
|
220
|
+
props: Record<string, unknown>,
|
|
221
|
+
): Record<string, unknown> {
|
|
222
|
+
const httpEquiv = props["http-equiv"] ?? props.httpEquiv;
|
|
223
|
+
const content = props.content;
|
|
224
|
+
if (typeof httpEquiv !== "string" || typeof content !== "string") {
|
|
225
|
+
return props;
|
|
226
|
+
}
|
|
227
|
+
if (!isUnsafeMetaRefreshContent(httpEquiv, content)) {
|
|
228
|
+
return props;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const sanitized = { ...props };
|
|
232
|
+
delete sanitized.content;
|
|
233
|
+
return sanitized;
|
|
234
|
+
}
|
|
235
|
+
|
|
213
236
|
function isClassComponentType(
|
|
214
237
|
value: unknown,
|
|
215
238
|
): value is new (props: Record<string, unknown>) => { render(): ReactCompatNode } {
|
|
@@ -305,7 +328,7 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
305
328
|
name === "children" ||
|
|
306
329
|
name === "key" ||
|
|
307
330
|
name === "ref" ||
|
|
308
|
-
/^on
|
|
331
|
+
/^on/i.test(name) ||
|
|
309
332
|
value === null ||
|
|
310
333
|
value === undefined ||
|
|
311
334
|
typeof value === "function"
|
|
@@ -320,6 +343,14 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
320
343
|
|
|
321
344
|
const attributeName = toHtmlAttributeName(name);
|
|
322
345
|
|
|
346
|
+
if (!VALID_ATTRIBUTE_NAME.test(attributeName)) {
|
|
347
|
+
return "";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (/^on/i.test(attributeName)) {
|
|
351
|
+
return "";
|
|
352
|
+
}
|
|
353
|
+
|
|
323
354
|
if (typeof value === "boolean" && isBooleanishStringAttribute(attributeName)) {
|
|
324
355
|
return ` ${attributeName}="${value ? "true" : "false"}"`;
|
|
325
356
|
}
|
|
@@ -346,9 +377,17 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
346
377
|
return ` ${attributeName}=""`;
|
|
347
378
|
}
|
|
348
379
|
|
|
349
|
-
|
|
380
|
+
const stringValue = String(value);
|
|
381
|
+
|
|
382
|
+
if (isUnsafeUrlAttribute(attributeName, stringValue)) {
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return ` ${attributeName}="${escapeHtml(stringValue)}"`;
|
|
350
387
|
}
|
|
351
388
|
|
|
389
|
+
const VALID_ATTRIBUTE_NAME = /^[A-Za-z_][\w.\-:]*$/;
|
|
390
|
+
|
|
352
391
|
function isBooleanishStringAttribute(name: string): boolean {
|
|
353
392
|
const attributeName = toHtmlAttributeName(name).toLowerCase();
|
|
354
393
|
return attributeName.startsWith("aria-") || BOOLEANISH_STRING_ATTRIBUTES.has(attributeName);
|