@reckona/mreact-compat 0.0.182 → 0.0.184

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.
@@ -369,39 +369,61 @@ function isEventHandlerName(name: string): boolean {
369
369
  );
370
370
  }
371
371
 
372
+ type AttributeNameClassification =
373
+ | { kind: "skip" }
374
+ | { kind: "style" }
375
+ | {
376
+ kind: "attribute";
377
+ attributeName: string;
378
+ booleanishString: boolean;
379
+ dataAttribute: boolean;
380
+ dangerousHtml: boolean;
381
+ };
382
+
383
+ const ATTRIBUTE_CLASSIFICATION_CACHE = new Map<string, AttributeNameClassification>();
384
+ const ATTRIBUTE_CLASSIFICATION_CACHE_LIMIT = 1024;
385
+ let attributeClassificationCacheMissCount = 0;
386
+
387
+ export const __serverRenderAttributeCacheForTesting = {
388
+ clear() {
389
+ ATTRIBUTE_CLASSIFICATION_CACHE.clear();
390
+ attributeClassificationCacheMissCount = 0;
391
+ },
392
+ missCount() {
393
+ return attributeClassificationCacheMissCount;
394
+ },
395
+ size() {
396
+ return ATTRIBUTE_CLASSIFICATION_CACHE.size;
397
+ },
398
+ };
399
+
372
400
  function renderHtmlAttribute(name: string, value: unknown): string {
373
401
  if (
374
402
  value === null ||
375
403
  value === undefined ||
376
- typeof value === "function" ||
377
- name === "children" ||
378
- name === "key" ||
379
- name === "ref" ||
380
- isEventHandlerName(name)
404
+ typeof value === "function"
381
405
  ) {
382
406
  return "";
383
407
  }
384
408
 
385
- if (name === "style") {
386
- const style = renderStyleAttribute(value);
387
- return style === "" ? "" : ` style="${escapeHtml(style)}"`;
388
- }
389
-
390
- const attributeName = toHtmlAttributeName(name);
409
+ const classification = classifyAttributeName(name);
391
410
 
392
- if (!VALID_ATTRIBUTE_NAME.test(attributeName)) {
411
+ if (classification.kind === "skip") {
393
412
  return "";
394
413
  }
395
414
 
396
- if (isEventHandlerName(attributeName)) {
397
- return "";
415
+ if (classification.kind === "style") {
416
+ const style = renderStyleAttribute(value);
417
+ return style === "" ? "" : ` style="${escapeHtml(style)}"`;
398
418
  }
399
419
 
400
- if (typeof value === "boolean" && isBooleanishStringAttribute(attributeName)) {
420
+ const { attributeName } = classification;
421
+
422
+ if (typeof value === "boolean" && classification.booleanishString) {
401
423
  return ` ${attributeName}="${value ? "true" : "false"}"`;
402
424
  }
403
425
 
404
- if (typeof value === "boolean" && isDataAttribute(attributeName)) {
426
+ if (typeof value === "boolean" && classification.dataAttribute) {
405
427
  return ` ${attributeName}="${value ? "true" : "false"}"`;
406
428
  }
407
429
 
@@ -409,7 +431,7 @@ function renderHtmlAttribute(name: string, value: unknown): string {
409
431
  return "";
410
432
  }
411
433
 
412
- if (isDangerousHtmlAttribute(attributeName)) {
434
+ if (classification.dangerousHtml) {
413
435
  return isDangerousHtmlOptIn(value)
414
436
  ? ` ${attributeName}="${escapeHtml(value.__html)}"`
415
437
  : "";
@@ -434,6 +456,50 @@ function renderHtmlAttribute(name: string, value: unknown): string {
434
456
 
435
457
  const VALID_ATTRIBUTE_NAME = /^[A-Za-z_][\w.\-:]*$/;
436
458
 
459
+ function classifyAttributeName(name: string): AttributeNameClassification {
460
+ const cached = ATTRIBUTE_CLASSIFICATION_CACHE.get(name);
461
+ if (cached !== undefined) {
462
+ return cached;
463
+ }
464
+
465
+ attributeClassificationCacheMissCount += 1;
466
+
467
+ const classification = createAttributeNameClassification(name);
468
+ if (ATTRIBUTE_CLASSIFICATION_CACHE.size < ATTRIBUTE_CLASSIFICATION_CACHE_LIMIT) {
469
+ ATTRIBUTE_CLASSIFICATION_CACHE.set(name, classification);
470
+ }
471
+ return classification;
472
+ }
473
+
474
+ function createAttributeNameClassification(name: string): AttributeNameClassification {
475
+ if (
476
+ name === "children" ||
477
+ name === "key" ||
478
+ name === "ref" ||
479
+ isEventHandlerName(name)
480
+ ) {
481
+ return { kind: "skip" };
482
+ }
483
+
484
+ if (name === "style") {
485
+ return { kind: "style" };
486
+ }
487
+
488
+ const attributeName = toHtmlAttributeName(name);
489
+
490
+ if (!VALID_ATTRIBUTE_NAME.test(attributeName) || isEventHandlerName(attributeName)) {
491
+ return { kind: "skip" };
492
+ }
493
+
494
+ return {
495
+ kind: "attribute",
496
+ attributeName,
497
+ booleanishString: isBooleanishStringAttribute(attributeName),
498
+ dataAttribute: isDataAttribute(attributeName),
499
+ dangerousHtml: isDangerousHtmlAttribute(attributeName),
500
+ };
501
+ }
502
+
437
503
  function isBooleanishStringAttribute(attributeName: string): boolean {
438
504
  // Callers pass the already-mapped HTML attribute name.
439
505
  const lowerCased = attributeName.toLowerCase();
package/src/server.ts ADDED
@@ -0,0 +1,62 @@
1
+ export {
2
+ Component,
3
+ PureComponent,
4
+ } from "./class-component.js";
5
+ export {
6
+ Fragment,
7
+ Activity,
8
+ Profiler,
9
+ Suspense,
10
+ SuspenseList,
11
+ StrictMode,
12
+ Children,
13
+ cloneElement,
14
+ createElement,
15
+ createErrorBoundary,
16
+ createRef,
17
+ forwardRef,
18
+ isValidElement,
19
+ lazy,
20
+ memo,
21
+ } from "./element.js";
22
+ export type {
23
+ ErrorBoundaryOptions,
24
+ ElementType,
25
+ ReactCompatElement,
26
+ ReactCompatNode,
27
+ ReactCompatRenderableElement,
28
+ } from "./element.js";
29
+ export {
30
+ createContext,
31
+ renderContextConsumerToString,
32
+ renderContextProviderToString,
33
+ useContext,
34
+ } from "./context.js";
35
+ export {
36
+ useCallback,
37
+ useDebugValue,
38
+ useDeferredValue,
39
+ useEffectEvent,
40
+ useEffect,
41
+ useId,
42
+ useImperativeHandle,
43
+ useInsertionEffect,
44
+ useLayoutEffect,
45
+ useMemo,
46
+ useOptimistic,
47
+ useReducer,
48
+ useRef,
49
+ useState,
50
+ useSyncExternalStore,
51
+ use,
52
+ useActionState,
53
+ cache,
54
+ cacheSignal,
55
+ captureOwnerStack,
56
+ startTransition,
57
+ unstable_useCacheRefresh,
58
+ useTransition,
59
+ version,
60
+ } from "./hooks.js";
61
+ export { renderChildToString, renderToString } from "./server-render.js";
62
+ export type { StartTransition, TransitionScope } from "./hooks.js";